elasticsearch index、create和update的源码分析
https://segmentfault.com/a/1190000011272749
社區里面有人問了如下一個問題:
執行 bulk 索引文檔的時候,用 index 或者 create 類型并且自定義 doc id 的情況下,是否會像 update 一樣每次都要去 get 一遍原始文檔? 比如下面的這條命令:
POST _bulk{ "index" : { "_index" : "test", "_type" : "type1", "_id" : "1" } } { "field1" : "value1" } { "create" : { "_index" : "test", "_type" : "type1", "_id" : "3" } } { "field1" : "value3" }問題出現的原因是他們在 bulk 測試的時候遇到了寫性能的問題,而正巧社區里面前幾天有這么一個類似的帖子,說的是 es 5.x 版本里面做 update 操作的性能問題。雖然和這個問題不完全一致,但都涉及到 es 索引數據的部分。
侯捷老師說:“源碼面前,了無秘密”,那我們就來簡單看下 es 這部分的相關代碼,以便回答開篇提出的問題。
準備工作
我是用?IntelliJ IDEA?來閱讀 elasticsearch 源碼的,操作也簡單。操作步驟如下:
下載 es 源碼,由于 es 的commit信息比較多,可以增加?--depth=1?只下載最近的commit,減少下載時間。
git?clone?https://github.com/elastic/elasticsearch.git --depth=1安裝 gradle,確保版本在 3.3 及以上,然后在源碼目錄下執行以下命令準備導入?IntelliJ IDEA?需要的文件
gradle idea大家可以參考?elasticsearch 文檔說明?和?Elasticsearch源碼分析—環境準備?這兩篇文章,細節我這里就不贅述了。
另外我是分析的 5.5.0 分支,大家記得 checkout,防止行數對應不起來。另外由于 es 代碼結構有些復雜,先不在這篇文章里面梳理整個流程了,直接說核心代碼。
Index/Create 源碼分析
es index 和 create 最終都會調用?org/elasticsearch/index/engine/InternalEngine.java?中下面的方法:
457?public IndexResult index(Index index) throws IOException注意這里的 index 中包含有要寫入的 doc, 簡單畫下該方法的執行流程圖,代碼這里就不貼了,剛興趣的自己去看。
?
請結合上面的流程圖來看相應的代碼,整個邏輯應該還是很清晰的,接下來我們看?planIndexingAsPrimary?的邏輯。
558?private IndexingStrategy planIndexingAsPrimary(Index index) throws IOException {這個方法最終返回一個 IndexingStrategy,即一個索引的策略,總共有如下幾個策略:
- optimizedAppendOnly
- skipDueToVersionConflict
- processNormally
- overrideExistingAsIfNotThere
- skipAsStale
不同的策略對應了不同的處理邏輯,前面3個是常用的,我們來看下流程圖。
?
這里的第一步判斷?是否是自定義 doc id?這一步就是 es 對于日志類非自定義 doc id的優化,感興趣的可以自己去看下代碼,簡單講就是在非自定義 id 的情況下,直接將文檔 add ,否則需要 update,而 update 比 add 成本高很多。
而第二個判斷?檢查版本號是否沖突??涉及到是如何根據文檔版本號來確認文檔可寫入,代碼都在index.versionType().isVersionConflictForWrites方法里,邏輯也比較簡單,不展開講了,感興趣的自己去看吧。
上面的流程圖也比較清晰地列出了策略選擇的邏輯,除去 optimizedAppendOnly 策略,其他都需要根據待寫入文檔的版本號來做出決策。接下來我們就看下獲取文檔版本號的方法。
389?private VersionValue resolveDocVersion(final Operation op) throws IOException {該方法邏輯比較簡單,主要分為2步:
看到這里,開篇問題便有了答案。es 在 index 或者 create 的時候并不會 get 整個文檔,而是只會獲取文檔的版本號做對比,而這個開銷不會很大。
Update 源碼分析
es update 的核心代碼在?org/elasticsearch/action/update/UpdateHelper.java?中,具體方法如下:
public Result prepare(UpdateRequest request, IndexShard indexShard, LongSupplier nowInMillis) { final GetResult getResult = indexShard.getService().get(request.type(), request.id(), new String[]{RoutingFieldMapper.NAME, ParentFieldMapper.NAME, TTLFieldMapper.NAME, TimestampFieldMapper.NAME}, true, request.version(), request.versionType(), FetchSourceContext.FETCH_SOURCE); return prepare(indexShard.shardId(), request, getResult, nowInMillis); }代碼邏輯很清晰,分兩步走:
第 1 步最終會調用 InternalEngine 中的 get 方法,如下:
350?public GetResult?get(Get?get, Function<String, Searcher> searcherFactory, LongConsumer onRefresh) throws EngineException {這里就接上開篇提到的社區問題中的源碼分析了。代碼就不展開講了,感興趣的自己去看吧。
update 操作需要先獲取原始文檔的原因也很簡單,因為這里是允許用戶做部分更新的,而 es 底層每次更新時要求必須是完整的文檔(因為 lucene 的更新實際是刪除老文檔,新增新文檔),如果不拿到原始數據的話,就不能組裝出更新后的完整文檔了。
因此,比較看重效率的業務,最好還是不要用 update 這種操作,直接用上面的 index 會更好一些。
總結
本文通過源碼分析的方式解決了開篇提到的問題,答案簡單總結在下面。
es 在 index 和?create?操作的時候,如果沒有自定義 doc?id,那么會使用 append 優化模式,否則會獲取待寫入文檔的版本號,進行版本檢查后再決定是否寫入lucene。所以這里不會去做一個?get?操作,即獲取完整的文檔信息。最后,記住侯捷老師的話:
源碼面前,了無秘密!
轉載于:https://www.cnblogs.com/davidwang456/articles/9923885.html
總結
以上是生活随笔為你收集整理的elasticsearch index、create和update的源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 剖析Elasticsearch集群系列第
- 下一篇: elasticsearch分析系列