版本 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++;
}