Linux

為 json 內容索引生成唯一 ID

  • August 3, 2018

我正在尋找使用 bash 腳本為以下內容生成有效且簡單的 ID:

{"name": "John", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"name": "John1", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"name": "John2", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"name": "John3", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}


{"id": "XXX", "name": "John", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"id": "XXX", "name": "John1", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"id": "XXX", "name": "John2", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"id": "XXX", "name": "John3", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}

我將擁有大約 5,000,000 條類似記錄,並且我想生成可重複、可預測的 ID。由於處理以下文件的時間會受到限制,因此我需要在 20 分鐘的視窗內對 Linux 機器上的 sql lite 數據庫執行此操作。

MD5、SHA1 太貴而無法使用,除非我可以在 AMD Ryzen 1900X CPU 上的 16 個執行緒上執行 GNU Parallel 之類的操作,並且可以在幾分鐘內完成?

我嘗試過使用 MD5,在 1 分 45 秒內完成了 28,000 個 ID。使用 SHA1 花了我 2 分 3 秒。

我正在考慮非常簡單地創建 ID:

JohnGatesGermany20180
John1GatesGermany20180
John2GatesGermany20180
John3GatesGermany20180

在必須滿足以下要求的情況下,您有什麼建議:

  • 重擊
  • Linux
  • 5,000,000 條記錄待處理
  • 不到 20 分鐘
  • 對於相同的 json 行,id 必須相同

進行的測試:

#!/usr/local/bin/bash

while IFS= read -r line
do
  uuid=$(uuidgen -s --namespace @dns --name "www.example.com" )
done < testfile1.txt

1,000,000 行的 md5 散列:

$time bash script.sh 

real    13m6.914s
user    10m24.523s
sys 2m56.095s

cksum 在 1,000,000 上執行 crc:

#!/usr/local/bin/bash

while IFS= read -r line
do
#    uuid=$(uuidgen -s --namespace @dns --name "www.example.com" )
   echo "$line $uuid"|cksum >> test3.txt
done < testfile1.txt

$time bash script.sh 

real    12m49.396s
user    12m23.219s
sys 4m1.417s

作為一個思想實驗,我想看看我們可以將 CLI 工具推到多遠來解決這類問題。為此,我想嘗試使用快速散列 CLI 工具xxHash來完成這項工作。

xxHash 是一種速度極快的非加密雜湊算法,其工作速度接近 RAM 限制。它以兩種形式提出,32 位和 64 位。

它在每種程式語言中都可用,但對於這個實驗,我將使用 CLI 風格xxhsum,特別是 32 位模式,所以xxhsum -H0.

正如您所發現的和其他人所說的那樣,一遍又一遍地呼叫散列函式 CLI 工具或任何工具通常是這些類型的方法失敗​​的地方。在這裡呼叫xxhsum5M 次將是使用它的次優方式。它的強項在於文件 I/O,那麼如果我們把 5M 的行,轉換成 5M 的文件呢?

split使用以下命令,該任務在 Linux 中實際上是微不足道的:

split -l 1 afile

以及像這樣散列這些文件的速度有多快,比如 1M,每個文件中都有一行。

範例 1 行文件

$ cat datadir/xzeyw
{"name": "John4000", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"} 

包含 1M 個文件的目錄

$ ls -l datadir | wc -l
1000002

是時候對它們進行雜湊處理了

$ { time xxhsum -H0 * > ../nfile 2>&1 ;} |& awk '/real|user|sys/ {print $1": "$2"\t"}' | tr -d '\n'
real: 0m6.998s  user: 0m5.007s  sys: 0m1.569s

是的,沒錯,花了大約 7 秒!我覺得這相當令人印象深刻。以xxhsum這種方式使用,我們只產生了執行一次的成本,並允許它循環遍歷 1M 文件。

這種方法的缺點

因此,這樣做的缺點之一當然是split. 正如您可以想像的那樣,這將成為我們最昂貴的操作。因為我們必須獲取一個包含 X 行的單個文件並將其作為 X 個文件分解到 HDD 上,其中包含一行。

這是其中的一些數據:

./hashy.bash

make data
---------
real: 0m17.492s user: 0m12.434s sys: 0m4.788s

split data
----------
real: 2m15.180s user: 0m0.700s  sys: 2m4.443s

hash data
---------
real: 0m6.487s  user: 0m5.798s  sys: 0m0.459s

在這裡我們可以看到我們的split操作花費了大約 2 分鐘。**注意:**此輸出中的第一行顯示了建構包含 1M 行 JSON 的文件的時間。

另一個缺點是我們在命令行上處理的文件數量。我*在地方使用,所以這將擴展到 1M 或 5M 文件名,這可能被認為是危險的,它是。請記住,當您增加文件數量時,您會面臨超出分配給命令行參數的空間量的風險。

請參閱有關命令行長度的這些連結:

結論

所以你可以想像,使用 1M 文件或 5M 文件解決這樣的問題看起來幾乎是荒謬的。我不得不同意。但這仍然是一個有趣的實驗,因為它表明如果您以適當的方式利用 CLI 工具,您可以從中獲得出色的性能。

hash.bash 的程式碼

如果有人對程式碼感興趣:

$ cat hashy.bash
#!/bin/bash

echo ""
echo "make data"
echo "---------"
rm -f afile
{ time for i in {0..1000000};do echo "{\"name\": \"John${i}\", \"surname\": \"Gates\", \"country\": \"Germany\", \"age\": \"20\", \"height\": \"180\"}">> afile ;done ;} \
 |& awk '/real|user|sys/ {print $1": "$2"\t"}' | tr -d '\n'
echo ""
echo ""

rm -fr datadir && mkdir datadir && cd datadir

echo "split data"
echo "----------"
{ time split -l 1 ../afile ;} |& awk '/real|user|sys/ {print $1": "$2"\t"}' | tr -d '\n'
echo ""
echo ""

echo "hash data"
echo "---------"
{ time xxhsum -H0 * > ../nfile 2>&1 ;} |& awk '/real|user|sys/ {print $1": "$2"\t"}' | tr -d '\n'

cd - > /dev/null 2>&1
echo ""
echo ""

參考

我敢打賭,你的腳本需要這麼長時間的原因是你每行都在執行uuidgen(或cksum)。僅僅為每個程序啟動程序就浪費了大量時間。

將 5M 行的表單{"name": "John%d", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}放入 tmpfs 文件系統上的文件中,以下 Python 腳本在幾秒鐘內完成:

#! /usr/bin/env python3

import hashlib
import sys
for line in sys.stdin:
   print(hashlib.md5(line.rstrip('\n').encode('utf-8')).hexdigest())

執行:

$ time ./foo.py < input > output
./foo.py < input > output  6.00s user 0.13s system 99% cpu 6.135 total
% wc -l input output
 5000000 input
 5000000 output
10000000 total

由於這是 Python,您還可以對這些行進行 JSON 解碼並在每個行中插入一個 ID。即使是低效的程式碼,例如:

#! /usr/bin/env python3

import hashlib
import json
import sys
for line in sys.stdin:
   l = line.rstrip('\n').encode('utf-8')
   o = json.loads(line)
   o["id"] = hashlib.md5(l).hexdigest()
   print(json.dumps(o))

不到一分鐘完成:

% time ./foo.py < input > output
./foo.py < input > output  42.11s user 0.42s system 99% cpu 42.600 total

% head output 
{"name": "John1", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "2dc573ccb15679f58abfc44ec8169e52"}
{"name": "John2", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "ee0583acaf8ad0e502bf5abd29f37edb"}
{"name": "John3", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "a7352ebb79db8c8fc2cc8758eadd9ea3"}
{"name": "John4", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "2062ad1b67ccdce55663bfd523ce1dfb"}
{"name": "John5", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "5f81325c104c01c3e82abd2190f14bcf"}
{"name": "John6", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "493e0c9656f74ec3616e60886ee38e6a"}
{"name": "John7", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "19af9ef2e20466d0fb0efcf03f56d3f6"}
{"name": "John8", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "2348bd47b20ac6445213254c6a8aa80b"}
{"name": "John9", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "090a521b4a858705dc69bf9c8dca6c19"}
{"name": "John10", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "fc3c699323cbe399e210e4a191f04003"}

我的規格:

  • Intel® Core™ i7-8700 CPU @ 3.20GHz × 12
  • 2666MHz DDR4 記憶體

基於你uuidgen的腳本在 4 分鐘內勉強完成了 500k 行。修改為保存輸出:

#!/usr/bin/bash

while IFS= read -r line
do
  uuidgen -s --namespace @dns --name "$line"
done < input > uuid

執行:

% timeout 240 ./foo.sh
% wc -l uuid
522160 uuid

引用自:https://unix.stackexchange.com/questions/460176