ElasticSearch update api 和 update_by_query哪家強

很久沒有怎麼隨記筆記了,今天這裏是爲了糾正一個一直以來我們使用es的一個誤區,這個誤區很大的可能你會就範。很多童靴會把update_by_query拿mysql的語法特性來用,那你就大錯特錯了,這裏有必要溫習下我之前的一篇update_by_query,理論上講es的準實時的僅限於search,而get id則是實時的。

實踐往往是檢驗真理的唯一標準,看下面演示吧

<!--1.關閉refresh-->
http://xxx:9200/mytest_user/_settings
{
    "index" : {
        "refresh_interval" : -1
    }
}
<!--2.新增-->
http://xxx:9200/mytest_user/_doc/2
{
	"product_type": "test",
  	"product_code": "324049",
	"shop_code": "9N72"
}
<!--3.修改,可以併發但並沒有發生更新丟失-->
http://xxx:9200/mytest_user/_doc/2/_update
{ "doc" : {
        "name" : "new_name"
    }
}
http://xxx:9200/mytest_user/_doc/2/_update
{ "doc" : {
        "name" : "alex",
        "age" : 20
    }
}
...
<!--4.update_by_query,不會有任何效果-->
http://xxx:9200/mytest_user/_update_by_query?conflicts=proceed
{
  
  "query" : {
    "term" : { "product_code": "324049" }
  },
   "script": {
    "source": "ctx._source.en_product_name='cn';ctx._source.plu_code='00';"
  }
}
<!--5.有最新的數據-->
http://xxx:9200/mytest_user/_doc/2

沒錯_update_by_query使用了search,顧沒有任何反應。而update api藉助get API的實時性做到了(即先根據文檔ID做一次GET,然後拿最新文檔修改後寫回去),而get API爲此有個參數可以控制的是爲非實時(http://xxx:9200/mytest_user/_doc/4?realtime=false)。

realtime

官方介紹默認情況下,get API是實時的,並且不受索引刷新率的影響(當數據在搜索中變爲可見時)。如果文檔已更新但尚未刷新,則get API將發出刷新調用以使文檔可見。這還會使上次刷新後的其他文檔可見。爲了禁用realtime GET,可以將realtime參數設置爲false。

update API的文檔和源碼都沒有提供一個“禁用”實時性的參數。update對GET的調用,傳入的realtime是寫死爲true的。

爲何get API會要求實時?

update允許對文檔做部分字段更新。如果有2個請求分別更新了不同的字段, 可能先更新的數據只在writter buffer裏,searcher裏看不到,那後面的更新還是在老版本文檔上做的,造成部分更新丟失。 

上面的結論我們藉助cat的監控可以看到:http://xxx:9200/_cat/segments/mytest_user?v

如果realtime設置爲false,就從searcher裏面拿,而searcher只能訪問refresh過的數據。剛寫入的數據存在於index writter buffer裏,暫時無法搜索到,所以這種方式拿到的數據是準實時的。

在5.x版本以上,實時則能夠訪問到index writter buffer裏的數據,並且還執行了強制刷新(並非refresh_interval),生成了新的segment file。如果短時間反覆大量更新相同doc id的操作,會因爲過於頻繁的refresh短時間生成很多小segment,繼而不斷做短合產生性能損耗。官方認爲,在提升大多數應用場景性能情況下,對於這種較少見的場景下的性能損失是值得的,應該在應用層面解決。

注:update方法更新文檔,如果關閉了Upsert,意味着如果更新的文檔id如果不存在,會拋出doc missing異常,大量拋出和捕獲doc missing異常開銷很高。

在2.4版本中,沒有采用refresh的方式讓數據實時,而是直接訪問的translog來保證GET的實時性。官方在這個變更裏 https://github.com/elastic/elasticsearch/pull/20102   將其更新方式改爲了refresh。理由是之前ES裏有很多地方用translog維護數據的位置,使得很多操作變得很慢,去掉對translog的依賴可以提高性能。

代碼驗證環節

代碼實現中確實有realtime參數和 refresh("realtime_get"); 的函數調用

//源自core/src/main/java/org/elasticsearch/index/engine/InternalEngine.java
public GetResult get(Get get, Function<String, Searcher> searcherFactory, LongConsumer onRefresh) throws EngineException {
        assert Objects.equals(get.uid().field(), uidField) : get.uid().field();
        try (ReleasableLock lock = readLock.acquire()) {
            ensureOpen();
            if (get.realtime()) {
                VersionValue versionValue = versionMap.getUnderLock(get.uid());
                if (versionValue != null) {
                    if (versionValue.isDelete()) {
                        return GetResult.NOT_EXISTS;
                    }
                    if (get.versionType().isVersionConflictForReads(versionValue.getVersion(), get.version())) {
                        throw new VersionConflictEngineException(shardId, get.type(), get.id(),
                            get.versionType().explainConflictForReads(versionValue.getVersion(), get.version()));
                    }
                    long time = System.nanoTime();
                    refresh("realtime_get");
                    onRefresh.accept(System.nanoTime() - time);
                }
            }

            // no version, get the version from the index, we know that we refresh on flush
            return getFromSearcher(get, searcherFactory);
        }

現在足以可見如果對es的更新需求特別多,首先需要考慮藉助get API(依賴 _id),否則使用update_by_query還是你手寫的類似語義(先search,再update)都不得不接受更新丟失的問題。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章