一、api
1、概念
就是批量操作,將多條PUT/POST/DELETE命令合併成一個bulk命令進行操作,節省代碼量也提高性能。
2、語法
PUT /_bulk
{"action":{"metadata"}}
{"data"}
action取值(如下是常用的):
index:普通的PUT操作,可以是創建文檔,也可以是全量替換
create:PUT /index/_doc/id/_create,強制創建
delete:刪除一個文檔,只要1個metadata就可以了,無需{“data”}部分
update:執行的partial update操作
metadata:json,對應的是這部分內容PUT /product/_doc/1
data:具體操作內容的json,比如{ "name": "huawei shouji", "desc": "4G 5G", "tags": ["shouji"] }
3、Demo
3.1、需求一
比如要創建一個index名稱爲test_index1且_id爲1的一個document,document裏面包含如下兩個字段
{ "test_field1" : "test1", "test_field2" : "test2" }
PUT /_bulk
{"index" : {"_index" : "test_index1", "_id" : "1"}}
{"test_field1" : "test1", "test_field2" : "test2"}
查看數據進行驗證
GET /test_index1/_search
{
"query": {
"match_all": {}
}
}
結果:
{
"took" : 5,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "test_index1",
"_type" : "test_type",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"test_field1" : "test1",
"test_field2" : "test2"
}
}
]
}
}
3.2、需求二
綜合應用
POST /_bulk
{"delete":{"_index":"test_index2","_id":"1"}}
{"create":{"_index":"test_index2","_id":"2"}}
{"test_field":"test2"}
{"index":{"_index":"test_index2","_id":"3"}}
{"test_field":"test3"}
{"update":{"_index":"test_index2","_id":"2","retry_on_conflict":3}}
{"doc":{"test_field2":"bulk test"}}
返回結果是對每一條操作都做一個結果,彼此之間互不影響,也就是說比如第一條delete報錯了,不會影響下面的語句,粗糙理解成“沒mysql的事務控制”。但是再返回結果裏,會告訴你異常日誌。具體返回結果如下,比如第一個delete是404了。:
{
"took" : 394,
"errors" : false,
"items" : [
{
"delete" : {
"_index" : "test_index2",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "not_found",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1,
"status" : 404
}
},
{
"create" : {
"_index" : "test_index2",
"_type" : "_doc",
"_id" : "2",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1,
"status" : 201
}
},
{
"index" : {
"_index" : "test_index2",
"_type" : "_doc",
"_id" : "3",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 2,
"_primary_term" : 1,
"status" : 201
}
},
{
"update" : {
"_index" : "test_index2",
"_type" : "_doc",
"_id" : "2",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 3,
"_primary_term" : 1,
"status" : 200
}
}
]
}
二、補充
1、格式
比如嚴格遵守如下格式,多一個回車符號都會報錯
PUT /_bulk
{"action":{"metadata"}}
{"data"}
比如如下會報錯:
PUT /_bulk
{"index" : {"_index" : "test_index1", "_id" : "1"}}
{
"test_field1" : "test1", "test_field2" : "test2"
}
返回結果
{
"error" : {
"root_cause" : [
{
"type" : "illegal_argument_exception",
"reason" : "Malformed action/metadata line [3], expected START_OBJECT but found [VALUE_STRING]"
}
],
"type" : "illegal_argument_exception",
"reason" : "Malformed action/metadata line [3], expected START_OBJECT but found [VALUE_STRING]"
},
"status" : 400
}
2、優化
bulk size建議大小:
bulk request會加載到內存裏,如果太大的話,性能反而會下降,因此需要反覆嘗試一個最佳的bulk size。一般從1000~5000條數據開始,嘗試逐漸增加,另外,如果看大小的話,最好是在515MB之間。
三、底層原理
1、問題
爲什麼bulk api那麼的醜陋不堪?換行、格式化都會報錯,比如那麼醜陋的格式才能執行?
2、答案
因爲bulk中的每組操作(index/create/delete/update)都可能要轉發到不同的node的shard上去執行。
如果採取優雅的json格式,如下:
[
{
"action" : {},
"data" : {}
}
]
首先,整個可讀性非常棒,讀起來很爽,但是ES拿到那種標準格式的JSON串以後,要按照下述流程去進行處理
(1)將JSON數組解析爲JSONArray對象,這個時候,整個數據,就會在內存中出現一份一模一樣的拷貝,一份數據是JSON文本,一份數據是JSONArray對象。
(2)解析JSON數組裏的每個JSON,對每個請求中的document進行路由
(3)爲路由到同一個shard上的多個請求,創建一個請求數組。
(4)將這個請求數組序列化
(5)將序列化後的請求數組發送到對應的節點上去
再看這種醜陋的bulk json格式
{"action" : {"meta"}}
{"data"}
{"action" : {"meta"}}
{"data"}
(1)不用將其轉換爲JSON對象,不會出現內存中的相同數據的拷貝,直接按照換行符切割JSON
(2)對每兩個一組的json,讀取meta,進行document路由
(3)直接將對應的json發送到node上去
兩種格式對比:
(1)優雅格式:
耗費更多的內存,更多的JVM GC開銷
我們之前提到過 bulk size最佳大小的問題,一般建議說在幾千條那樣,然後大小在10MB左右,所以說,可怕的事情來了,假設說現在100個bulk請求發送到了一個節點上去,然後每個請求是10MB,100個請求就是1000MB=1GB。然後每個請求的json都copy一份爲JSONArray對象,此時內存中的佔用就會翻倍,就會佔用2GB內存,甚至還不止,因爲弄成JSONArray後,還可能會多搞一些其他的數據結構,2GB+的內存佔用。
佔用更多的內存可能就會積壓其他請求的內存使用量,比如說最重要的搜索請求,分析請求,等等,此時就可能會導致其他請求的性能急速下降
另外的話,佔用內存更多,就會導致ES的java虛擬機的垃圾回收次數更多,更頻繁,每次要回收的垃圾對象更多,耗費的時間更多,導致ES的java虛擬機停止工作線程的時間更多。
(2)醜陋的JSON格式:
最大的優勢在於,不需要將JSON數組解析爲一個JSONArray對象,形成一份大數據的拷貝,浪費內存空間,儘可能的保證性能。