文章目錄
- 什么是文檔?
- 文檔元數據
- 文檔的部分更新
- Update 樂觀并發控制
最近日常工作開發過程中使用到了 ES,最近在檢索資料的時候翻閱到了 ES 的官方文檔,里面對 ES 的基礎與案例進行了通俗易懂的解釋,讀下來也有不少收獲,所以打算記錄一下。果真官方文檔才是最好的“菜鳥教程”。
貼上官方文檔:
Elasticsearch:權威指南-基礎入門
什么是文檔?
Elasticsearch 中,術語 文檔 有著特定的含義。它是指最頂層或者根對象, 這個根對象被序列化成 JSON 并存儲到 Elasticsearch 中,指定了唯一 ID。可以簡單理解為我們平時操作存儲的對象在 ES 中通過 JSON 序列化存儲的內容,是 ES 中操作的最小單位。
{"name": "John Smith","age": 42,"confirmed": true,"join_date": "2014-06-01","home": {"lat": 51.5,"lon": 0.1},"accounts": [{"type": "facebook","id": "johnsmith"},{"type": "twitter","id": "johnsmith"}]
}
文檔元數據
一個文檔不僅包含著其本身的數據,也包含文檔有關的信息,可以稱之為 「元數據」。
三個必須的元數據元素如下:
-
_index
文檔在哪存放,就是我們平時建立的索引
-
_type
文檔表示的對象類別,這個平時用的比較少,算是同一索引下的一個更細的類別劃分
-
_id
文檔唯一標識,可以通過它檢索唯一的文檔。當創建一個新文檔的時候,要么自己提供 _id,要么讓 ES 自動生成
文檔的部分更新
ES update API 似乎對文檔直接進行了修改,但是實際上 ES 中文檔是「不能被修改,只能被替換」,詳細的過程如下:
- 從舊文檔構建 JSON
- 更改該 JSON
- 刪除舊文檔
- 索引一個新文檔
注意:其中操作“刪除文檔”也并不會立即將文檔從磁盤中刪除,只是將文檔標記為已刪除狀態,即軟刪。隨著我們不斷索引更多的數據,ES 才會在后臺線程中清理標記為已刪除的文檔。
Update API 整體遵循 檢索-修改-重建索引 的處理過程,并且這三步都是發生在 ES 節點內部的,避免了客戶端和 ES 集群的多次網絡交互開銷。通過減少檢索和重建索引步驟之間的事件,也減少了其他進程的變更帶來沖突的可能性。但是這不能完全消除沖突的可能性,可能還是會有某個進程在 update 設法重建索引前,另一進程請求修改了文檔。
Update 樂觀并發控制
ES 是分布式的,當文檔創建、更新或刪除的時候,新版本的文檔必須復制到集群中的其他節點。ES 也是異步和并發的,這意味著這些復制請求被并行發送,并且到達目的地時也許是「亂序」的,ES 需要一種方法確保文檔的舊版本不會覆蓋新的版本。
ES 中每個文檔都有一個 _version 號,可以當作其元數據的一部分,當文檔被修改時,版本號會遞增。ES 可以使用 _version 來確保變更以正確的順序執行,如果舊版本的文檔在新版本之后到達,可以被簡單的忽略。
并且通過 _version 版本號還可以進行更新的樂觀并發控制,這可以確保應用中相互沖突的變更不會導致數據沖突。我們可以通過指定想要修改的文檔的 _version 來達到這個目的,如果該版本不是當前版本號,我們的請求會失敗。
Update API 樂觀并發控制:
Update API 的 檢索-修改-重建索引 步驟拆解下來就是如下的 Get - Update - Put 請求,三個步驟不是原子的話就會產生沖突,像下面的示意圖所示就會產生更新數據覆蓋的問題
為了避免并發更新時產生的數據丟失,Update API 在「檢索」步驟時檢索得到當前文檔的 _version 號,并傳遞版本號到 「重建索引」的請求上。如果另一個進程修改了處于「檢索」和「重建索引」步驟之間的文檔,那么 _version 號將不匹配,更新請求將會失敗。這種場景就是先到的 request1 由于執行時間過長,反倒被后到的&執行時間更短的 request2 給沖突導致失敗了。
在增量操作無關順序的場景下,兩個進程更新操作發生的順序不太重要(比方說兩個進程都對頁面訪問量進行遞增計數),那么由于沖突導致更新失敗的請求,唯一需要做的就是重試。
這可以通過設置參數 retry_on_conflict 來自動完成,這個參數規定了失敗之前 update 應該重試的次數,它的默認值是 0,也就是默認不重試。下面的例子表示失敗之前需要重試 5 次。
POST /website/pageviews/1/_update?retry_on_conflict=5
{"script" : "ctx._source.views+=1","upsert": {"views": 0}
}
但是在其他情況下變更可能要求是「有序的」,那么在 ES update 的這個角度上來說,就不能開啟重試邏輯,比方在更新賬戶余額場景下(非遞增遞減操作),先到的 request1 被后到的 request2 沖突失敗了,此時 request2 是最新的數據,ES 也是能夠維持「最終一致性的」,但如果讓 request1 重試一次,賬戶余額就會被老的數據覆蓋了,顯然是不能接受的。
再拓展一下思路,上面僅僅是在 ES 系統內保證 update 的有序,如果放在整個分布式系統中去看,比方說消息系統生產者發送消息,消費者消費并更新 ES(canal 就有這種模式),如果要保證分布式系統的全局有序,就得額外考慮 1.生產者到 MQ 2.MQ 到消費者 3.消費者到更新ES 每個環節的「有序性」。