概要
bulk api有趣的json格式
前面《簡單入門實戰》一節中,有介紹bulk的使用示例,大家一定很奇怪,還有這么有趣的JSON格式,必須嚴格照他的換行來做,我想把JSON搞得美觀可讀性好一點,居然給我報錯!
{"action": {"meta"}}\n
{"data"}\n
{"action": {"meta"}}\n
{"data"}\n
它為什么要這樣規定?
我們想想bulk設計的初衷,批處理的執行效率肯定是第一優先級,此時效率>可讀性,如果我們允許隨意換行,用標准格式的JSON串,會有什么區別?
如果是標准格式的JSON串,處理流程一般會是這樣:
- 將整個json數組全部加載,解析為JSONArray對象,這時內存中同時有json串文本和JSONArray對象。
- 循環遍歷JSONArray對象,獲取每個請求中的document進行路由信息。
- 把路由到同一個shard的請求合在一組,開辟一個新的請求數組,將JSONObject放在數組里。
- 序列化請求數組,發送到對應的節點上去。
- 收集各節點的響應,匯總后返回給Coordinate Node。
- Coordinate Node收到所有的匯總信息,返回給客戶端。
這種方式唯一的缺點就是占用內存多,一份json串,解析為JSONArray對象,內存占用翻番,bulk里面多則幾千條請求,如果JSON報文大一點,這內存耗費不是開玩笑的,如果bulk占用的內存過多,就可能會擠壓其他請求的內存使用量,如搜索請求、數據分析請求等,整體性能會急速下降,嚴重的情況可能會觸發Full GC,會導致整個JVM工作線程暫停。
再看看現有的格式定義:除了delete操作占一行,其他操作都是占兩行的,ES收到bulk請求時,就可以簡單的按行進行切割,也不用轉成json對象了,切割完的JSON讀取里面的meta信息,直接路由到相應的shard,收集完響應返回即可。
這樣的好處切割邏輯更簡單,都是處理小json字符串,內存快拿快放,整個ES避免對內存的大塊占用,盡可能保證性能。
增刪改文檔內部原理
增刪改的過程整體與查詢文檔過程一致,只是多了一個數據同步的步驟,整個過程如圖所示:
相似的步驟不贅述。
步驟3的前提是primary shard操作成功,異步請求,所有的replica都返回成功后,node2響應操作成功的消息給Coordinate Node,最后Coordinate Node向客戶端返回成功消息,此時所有的primary shard和replica shard均已完成數據同步,數據是一致的。
查詢文檔內部原理
當我們使用客戶端(Java或Restful API)向Elasticsearch搜索文檔數據時,可以向任意一個node發送請求,此時接受請求的node就是Coordinate Node,整個過程如圖所示:
- Coordinate Node接收到請求后,根據_id信息或routing信息,確定該document的路由信息,即在哪個shard里,比如說P0。
- Coordinate Node轉發請求,使用round-robin隨機輪詢算法 ,在primary shard或replica shard隨機挑一個,讓讀請求負載均衡,如node-3的R0-1
- 接收請求的node-3搜索完成后,響應結果給Coordinate Node。
- Coordinate Node將響應結果返回給客戶端。
注意一個問題,如果document還在建立索引過程中,可能只有primary shard有,任何一個replica shard都沒有,此時可能會無法讀取到document,但是等document完成索引建立后,primary shard和replica shard就都有了,這個時間間隔,大概1秒左右。
寫一致性要求
Elasticsearch在嘗試執行一個寫操作時,可以帶上consistency參數,聲明我們的寫一致性的級別,正確地使用這個級別,為了避免因分區故障執行寫操作,導致數據不一致,這個參數有三個值供選擇:
- one:只要有一個primary shard是active活躍可用的,就可以執行寫操作
- all:必須所有的primary shard和replica shard都是活躍的,才可以執行這個寫操作
- quorum:默認的值,要求所有的shard中,必須是大部分的shard都是活躍的,可用的,才可以執行這個寫操作
這個大部分,該怎么算呢?
這個大部分,叫規定數量(quorum),有個計算公式:
int( (primary + number_of_replicas) / 2 ) + 1
- primary 即一個索引下的primary shard數量;
- number_of_replicas即每個primary shard擁有的副本數量,注意不是一個索引所有的副本數量。
如果一個索引有3個primary shard,每個shard擁有1個replica shard,共6個shard,這樣number_of_replicas就是1,代入公式計算:
quorum = int ((3 + 1) / 2) + 1 = 3
所以6個shard中必須有3個是活躍的,才讓你寫,如果你只啟用2個node,這樣活躍的replica shard只會有1個,加上primarys shard ,結果最多是2。這樣是達不到quorun的值,因此將無法索引和刪除任何文檔。
此時你必須啟動3個節點,才能滿足quorum寫一致性的要求。
quorum不夠時的超時處理
如果寫操作檢查前,活躍的shard不夠導致無法寫入時,Elasticsearch會等待,希望宕機的node能夠恢復,默認60秒,可以使用timeout參數修改默認值。
單node的寫一致性
照上面的公式算,1個node的,1個索引1個primary shard,number_of_replicas為1的情況,計算公式:
quorum = int ((1 + 1) / 2) + 1 = 2
實際只有一個primary shard是活躍的,豈不是永遠無法寫入?我研發機器只啟動一個node,不照樣增刪改查?
原來是Elasticsearch為了避免單一node的無法寫入問題,加了判斷邏輯:只有number_of_replicas大於1的時候,quorum才會生效。
小結
本篇從性能優先的角度簡單對bulk的設計作了一些補充,並對文檔查詢,寫操作的原理過程,一致性級別,quorum的計算做了一些簡單講解,謝謝。
專注Java高並發、分布式架構,更多技術干貨分享與心得,請關注公眾號:Java架構社區