ElasticSearch中的批量導入Bulk

版本 7.7, 官方文檔 https://www.elastic.co/guide/en/elasticsearch/reference/7.x/docs-bulk.html

Bulk API

在單個API調用中執行多個索引或刪除操作。這樣可以減少開銷,並大大提高索引速度。

比如:

POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }

請求

POST /_bulk

POST /<target>/_bulk

說明

Bulk接口提供了在一個請求中執行多種索引/創建/刪除/更新操作的方法.

要求內容(body)部分必須是"newline delimited JSON" (NDJSON, 每行以換行符\n結尾)格式.

action_and_meta_data\n
optional_source\n
action_and_meta_data\n
optional_source\n
....
action_and_meta_data\n
optional_source\n

換行符\n前面可以有回車符\r

每行數據對應兩個json, 佔兩行, 第一行是用來指明操作命令和元數據, 第二行是自定義的數據.

刪除命令(delete)只佔一行, 後面不需要再跟數據

每條數據之間不需要多餘的換行

如果head中指明瞭"<target>", 則內容中不需要再指定"_index". 比如我們只往"test"中插入數據

POST /test/_bulk
{"index":{"_id":1}}
{"id":1,"name":"aben","age":18}
{"index":{"_id":2}}
{"id":2,"name":"sky","age":19}
{"index":{"_id":3}}
{"id":3,"name":"tom","age":20}

curl 命令行下提交數據

如果我們想通過文件方式導入大量數據, 則必須在命令行中使用curl了.

$ cat requests
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
$ curl -s -H "Content-Type: application/x-ndjson" -XPOST localhost:9200/_bulk --data-binary "@requests";

1. 必須在header中指定 "Content-Type" 爲 "application/x-ndjson" 或者 "application/json"

比如:

curl -XPOST localhost:9200/info/_bulk --data-binary "info.json"

報錯:

{"error":"Content-Type header [application/x-www-form-urlencoded] is not supported","status":406}

2. 必須使用參數"--data-binary", 不能使用參數"-d", 否則會忽略換行符

3. 文件名前必須加"@"符號, 比如: "@data_file_name.json"

這個官方文檔中沒有特別說明, 真的很坑

比如下面這個命令, 參數都加完整了, 但是就是缺少一個@符號:

curl -s -H "Content-Type: application/x-ndjson" -XPOST localhost:9200/info2/_bulk --data-binary "info2.json"

報錯:

{"error":{"root_cause":[{"type":"illegal_argument_exception","reason":"The bulk request must be terminated by a newline [\\n]"}],"type":"illegal_argument_exception","reason":"The bulk request must be terminated by a newline [\\n]"},"status":400}

附: 從mysql中導出大量數據到json文件(這裏把每10w筆數據寫入一個文件)

//每次讀取10w筆會超出內存, 所以改成每次1w, 然後把這10次的查詢結果寫入到同一個文件裏面去.
$idFrom = 0;
$ix = 1;
$batchWriteSize = 1000; //批次寫入的行數量, 避免每行寫入一次
while (true) {
    $sql = 'SELECT * FROM `info` WHERE id > :id ORDER BY id ASC LIMIT 0,10000';//每次只取1w行
    $rs = Db::select($sql, ['id' => $idFrom]);
    if (empty($rs)) {
        echo 'no more data ...';
        break;
    }
    echo '取到數據: ' . count($rs) . ' 行 ======' . PHP_EOL;

    $fileName = 'info_' . (ceil($ix / 10) - 1) . '.json';//不能取餘, 否則id會很分散, 用除法再向上進位`ceil`
    echo '從' . $idFrom . ' 開始, 寫入到文件 ' . $fileName . ':' . PHP_EOL;

    $i = 1;
    $str = '';
    $writed = false;//是否已寫入. 解決數量不是 1000 的倍數時最後數據沒寫入的問題
    foreach ($rs AS $row) {
        $idFrom = (int)$row['id'];
        $row_data = '{"index":{"_index":"info","_id":' . $row['id'] . '}}' . "\n" . json_encode($row) . "\n";//注意: 數據裏面必須使用\n
        if ($i <= $batchWriteSize) {// 1 ~ 1000 都寫入
            $str .= $row_data;
        }
        if ($i == $batchWriteSize) {
            echo '寫入一次, 當前id: ' . $idFrom . ', 大小: ' . strlen($str) . PHP_EOL;
            file_put_contents($fileName, $str, FILE_APPEND);
            $writed = true;
            $str = '';
            $i = 1;
        }
        else {
            $i++;
        }

    }
    //把最後不到1000行的數據寫入
    if ($i > 1) {
        echo '把最後不到' . $batchWriteSize . '行的數據(' . ($i - 1) . '個)寫入' . PHP_EOL;
        file_put_contents($fileName, $str, FILE_APPEND);
    }

    $ix++;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章