一場由 ES 分片 routing 引發的問題
ES 結構
{"poroperties": {"joinType": {"type": "join","eager_global_ordinals": true,"relations": {"spu": "sku"}},"id":{"type": "keyword"},"spuGuid": {"type": "keyword"},"skuGuid": {"type": "keyword"},"sellCount": {"type": "long"}}
}
我們使用 ES 存儲商品數據,我們使用父子文檔,字段為 joinType,其值為 sku 時則為子文檔,id 為 skuGuid,其值為 spu 時則為父文檔,id 為 spuGuid。使用父子文檔是為了通過 sku 參數查詢條件去查詢 spu。
ES 在多分片多的情況下,必須將父子文檔放在同一分片中,所以我們以 spuGuid 作為routing id。
問題描述
一段更新銷量的代碼執行完以后,查詢發現銷量未變化。
問題排查
查看 ES 數據
用 GET goods_index/_doc/7277857079027761152 直接查詢該id的文檔
用 POST goods_index/_search 條件為 skuGuid = 7277857079027761152
用 POST goods_index/_search 條件為 id = 7277857079027761152
這時候我還在懷疑是不是因為我們代碼最近把主鍵都改為 long 數值類型,在序列化時變成了字符串類型,是否 es 對這個敏感。所以我又進行了一次查詢,這次使用 id 字段,因為每個文檔都會內置一個 id,然后就發現問題了。
這里居然出現了兩個文檔!,而且他們一個有 routing,一個沒有 routing,我恍然大悟。我們的 ES 設置了分片數為 3,也就是每個文檔都會根據 id 得到不同的 routing 值,從而存入不同的分片中。由于我們為了確保父子文檔能正確查詢,按照官方文檔要求的將 spuGuid 設為 routing,但是在更新銷量的代碼中并沒有指定 routing,沒有指定的話,就會默認使用 id 作為 routing 依據,那就不知道會存入哪個分片了,運氣好就是對的,運氣差就是錯的。這就解釋了為什么一些商品的銷量是正確的,一些是錯誤的問題。而且用 GET _doc 的方式查詢時,它會默認用 id routing 一次再查詢,所以查不到真正的文檔,而查詢到的那個只有一個銷量的文檔是代碼中 upsert 插入的,upsert 是先如果沒有找到對應文檔就會插入文檔,所以就只有一個字段。而在用 _search skuGuid = 7277857079027761152 查詢時查不到是因為本來就沒 skuGuid 這個字段。最后用 _search id = 7277857079027761152 查詢到了,是因為用了文檔本來的 id 查到了。
解決辦法
ES更新的時候因為有分片存在,UpdateRequest 不設置 routing 時默認用 id 路由,如果用了父子文檔(父子文檔現在用的 spuGuid 作為routing id),就會路由錯誤,更新失敗。
所以更新的時候如果用UpdateRequest,就必須指定routing;否則就用UpdateByQueryRequest,無需指定routing。