干货 | Elasticsearch开发人员最佳实战指南
點擊上方“朱小廝的博客”,選擇“設(shè)為星標(biāo)”
后臺回復(fù)”加群“獲取公眾號專屬群聊入口
幾個月以來,我一直在記錄自己開發(fā)Elasticsearch應(yīng)用程序的最佳實踐。本文梳理的內(nèi)容試圖傳達(dá)Java的某些思想,我相信其同樣適用于其他編程語言。我嘗試盡量避免重復(fù)教程和Elasticsearch官方文檔中已經(jīng)介紹的內(nèi)容。本文梳理的內(nèi)容都是從線上實踐問題和個人總結(jié)的經(jīng)驗匯總得來的。
文章從以下幾個維度展開講解:
映射(Mapping)
設(shè)置(Setting)
查詢方式(Querying)
實戰(zhàn)技巧(Strategy)
1、映射(Mapping)
1.1 避免使用nested類型
每個Elasticsearch文檔都對應(yīng)一個Lucene文檔。
nested類型是個例外,對于nested類型,每個字段都作為單獨的文檔存儲與父Lucene的關(guān)聯(lián)。
其影響是:
nested與父文檔中的字段相比,查詢字段的速度較慢
檢索匹配nested字段會降低檢索速度
一旦更新了包含nested字段的文檔的任何字段(與是否更新嵌套字段無關(guān),則所有基礎(chǔ)Lucene文檔(父級及其所有nested子級)都需要標(biāo)記為已刪除并重寫)。除了降低更新速度外,此類操作還會產(chǎn)生大量垃圾文件,直到通過段合才能進(jìn)行清理。
在某些情況下,你可以將nested字段展平。
例如,給定以下文檔:
{"attributes": [{"key": "color", "val": "green"},{"key": "color", "val": "blue"},{"key": "size", "val": "medium"}] }展平如下:
{"attributes": {"color": ["green", "blue"],"size": "medium"} }1.2 Mapping設(shè)置strict
實際業(yè)務(wù)中,如果不明確設(shè)定字段類型,Elasticsearch有動態(tài)映射機制,會根據(jù)插入數(shù)據(jù)自動匹配對應(yīng)的類型。
假定:本來準(zhǔn)備插入浮點型數(shù)據(jù),但由于第一個插入數(shù)據(jù)為整形,Elasticsearch 自定會判定為long類型,雖然后續(xù)數(shù)據(jù)也能寫入,但很明顯“浮點類型”只閹割保留了整形部分。
銘毅給個demo一探究竟:
POST my_index03/_doc/1 {"tvalue":35 }POST my_index03/_doc/2 {"tvalue":3.1415 }GET my_index03/_mappingGET my_index03/_search {"query": {"term": {"tvalue": {"value": 3.1415}}} }注意:term查詢是不會返回結(jié)果的。
所以,實戰(zhàn)環(huán)境中,Mapping設(shè)定要注意如下節(jié)點:
顯示的指定字段類型
盡量避免使用動態(tài)模板(dynamic-templates)
禁用日期檢測 (date_detection),默認(rèn)情況下處于啟用狀態(tài)。“strict”實踐舉例:
1.3 合理的設(shè)置string類型
Elasticsearch5.X 之后,String 被分成兩種類型,text和keyword。兩者的區(qū)別:
text:適用分詞全文檢索場景
keyword:適用字符串的精準(zhǔn)匹配場景
默認(rèn),如果不顯示指定字段類型,字符串類型自定映射后的Mapping如下所示:
"cont" : {"type" : "text","fields" : {"keyword" : {"type" : "keyword","ignore_above" : 256}}而公司實戰(zhàn)的業(yè)務(wù)場景,通常會面臨:
需不需要分詞,不需要的話僅保留keyword即可。
需要用什么分詞?英文分詞還是中文分詞?
分詞后是否還需要排序和聚合,即fielddata是否需要開啟
是否需要精準(zhǔn)匹配,即是否需要保留keyword
所以,回答了如上幾個問題,再有針對的顯示設(shè)定string類型的Mapping方為上策!
2、設(shè)置(Setting)
在這里,我分享了Elasticsearch集群 設(shè)置 相關(guān)的技巧。
2.1 避免過度分片
分片是Elasticsearch的最大優(yōu)勢之一,即將數(shù)據(jù)分散到多個節(jié)點以實施并行化。關(guān)于這個主題有過很多討論。
但請注意,索引的主分片一旦設(shè)置便無法更改(除非重建索引或者reindex)。
對于新來者來說,過度分片是一個非常普遍的陷阱。在做出任何決定之前,請確保先通讀官方的這篇博文:
我在 Elasticsearch 集群內(nèi)應(yīng)該設(shè)置多少個分片?
https://www.elastic.co/cn/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster
銘毅提示:
主分片數(shù)過多:
批量寫入或者查詢請求被分割成過多的子寫入、子查詢,導(dǎo)致索引的寫入、查詢拒絕率上升。
主分片數(shù)過少:
尤其對于數(shù)據(jù)量非常龐大的索引,若分片數(shù)過少或者就1個分片,會導(dǎo)致無法利用集群多節(jié)點資源(也就是分布式特性),造成資源利用率不高或者不均衡,影響寫入或者查詢效率。
并且,一旦該大的主分片出現(xiàn)問題,恢復(fù)起來耗時會非常長。
2.2 取消學(xué)習(xí)任何段合并的技巧
從本質(zhì)上講,Elasticsearch是另一種分布式 Lucene產(chǎn)品,就像Solr一樣 。在底層,大多數(shù)時候,每個Elasticsearch文檔都對應(yīng)一個Lucene文檔(nested除外,如1.1所述)。在Lucene中,文檔存儲在 segment中。后臺的Elasticsearch通過以下兩種模式連續(xù)維護(hù)這些Lucene段:
在Lucene中,當(dāng)你刪除或更新文檔時,舊文檔被標(biāo)記為已刪除,而新文檔被創(chuàng)建。Elasticsearch會跟蹤這些標(biāo)記為deleted的文檔,適時對其段合并。
新添加的文檔可能會產(chǎn)生大小不平衡的段。Elasticsearch可能會出于優(yōu)化目的而決定將它們合并為更大的段。
實戰(zhàn)中一定要注意:段合并是高度受磁盤I / O和CPU約束的操作。
作為用戶,我們不想讓段合并破壞Elasticsearch的查詢性能。
事實上,在某些情況下可以完全避免使用它們:一次構(gòu)建索引,不再更改它。盡管在許多應(yīng)用場景中可能很難滿足此條件。一旦開始插入新文檔或更新現(xiàn)有文檔,段合并就成為不可避免的一部分。
正在進(jìn)行的段合并可能會嚴(yán)重破壞集群的總體查詢性能。在Google上進(jìn)行隨機搜索,你會發(fā)現(xiàn)許多人發(fā)帖求助求助:“在段合并中減少對性能的影響的配置“,還有許多人共享某些適用于他們的配置。但,很多配置都是早期1.x,2.X版本的設(shè)置,新版本已經(jīng)廢棄。
綜上,我進(jìn)行段合并的經(jīng)驗法則如下:
取消學(xué)習(xí)任何段合并的技巧。早期版本的段合并配置是與Elasticsearch的內(nèi)部緊密耦合的操作,新版本一般不再兼容。幾乎沒有“神秘”的底層配置修改可以使它運行得更快。
找到translog flush 的最優(yōu)配置 。嘗試調(diào)整index.translog.sync_interval和index.translog.flush_threshold_size設(shè)置。
詳見:https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-translog.html
動態(tài)調(diào)整index.refresh_interval以滿足業(yè)務(wù)需求。如果實時性要求不高,可以調(diào)大刷新頻率(默認(rèn)是1s,可以調(diào)到30s甚至更大)。
2.3 注意JVM內(nèi)存設(shè)置
Elasticsearch可以根據(jù)兩個主要內(nèi)存設(shè)置產(chǎn)生引人注目的性能特征:
JVM堆空間——主要用途:緩存(節(jié)點緩存、分片請求緩存、Field data緩存以及索引緩存)
堆外內(nèi)存空間——lucene段文件緩存
提醒你不要根據(jù)過去的非Elasticsearch JVM應(yīng)用程序經(jīng)驗來盲目設(shè)置Elasticsearch JVM堆大小。
詳見官方文檔:
https://www.elastic.co/guide/en/elasticsearch/reference/current/heap-size.html
3、查詢方式(Querying)
下面我收集了一些技巧,你可以在Elasticsearch查詢時使用它們。
3.1 Elasticseach里面多線程修改如何保證數(shù)據(jù)準(zhǔn)確性?
1,用如下兩個參數(shù)校驗沖突
2,用version避免沖突
3.2 嘗試分割復(fù)雜的查詢,并行執(zhí)行提升性能
如果你同時具有過濾器和聚合組件的復(fù)雜查詢,則在大多數(shù)情況下,可以將它們拆分為多個查詢并并行執(zhí)行它們可以提高查詢性能。
也就是說,在第一個查詢中,僅使用過濾器獲取匹配,然后在第二個查詢中,僅獲取聚合結(jié)果而無需再獲取檢索結(jié)果,即size: 0。
3.3 了解你的數(shù)字類型,防止被優(yōu)化導(dǎo)致精度損失
許多JSON解析器可以進(jìn)行各種優(yōu)化,以提供有效的讀/寫性能。但可能造成了精度的損失,所以在選型Jackson json解析器時:優(yōu)先使用BigDecimal和BigInteger。
3.4 不要使用Elasticsearch Transport / Node客戶端
TransportClient可以支持2.x,5.x版本,TransportClient將會在Elasticsearch 7.X版本棄用并在8.X版本中完成刪除.?
官方推薦使用Java High Level REST Client,它使用HTTP請求而不是Java序列化請求。為了安全起見,堅持使用HTTP上的JSON格式,而不使用 SMILE (二進(jìn)制格式)。
3.5 使用官方的Elasticsearch High-level REST客戶端
非官方客戶端一般更新太慢,幾乎無法跟上Elasticsearch新版本的特性,如:Jest客戶端近一年幾乎沒有更新,只支持到6.X版本。
相比之下,官方REST客戶端仍然是你相對最好的選擇。https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html
3.6 不要使用HTTP緩存來緩存Elasticsearch響應(yīng)結(jié)果
由于便利性和低進(jìn)入門檻,許多人陷入了將HTTP緩存(例如Varnish http://varnish-cache.org/)置于Elasticsearch集群前面的陷阱。使用HTTP緩存缺點如下:
在生產(chǎn)環(huán)境中使用Elasticsearch時,由于各種原因如:彈性擴(kuò)展、測試和線上環(huán)境分離、零停機升級等,你很有可能最終會擁有多個集群。
(1)一旦為每個集群提供專用的HTTP緩存,99%的緩存內(nèi)容是重復(fù)的。
(2)如果你決定對所有集群使用單個HTTP緩存,那么很難以編程方式配置HTTP緩存以適應(yīng)不斷變化的集群狀態(tài)的需求。
如何傳達(dá)集群負(fù)載以使緩存平衡流量?
如何配置計劃內(nèi)或手動停機時間?
在維護(hù)時段期間,如何使緩存逐漸從一個集群遷移到另一個集群?
這些都是亟待考慮的問題。
如上所述,HTTP緩存很難以編程方式進(jìn)行實現(xiàn)。當(dāng)你需要手動刪除一個或多個條目時,它并不總是像DELETE FROM cache WHERE keys IN (...)查詢那樣容易。還得通過手動實現(xiàn)。
銘毅提示:這一條我實際沒有用過,有用過的童鞋可以留言討論。
3.7 使用基于_doc排序的slice scroll 遍歷數(shù)據(jù)
Scrolls 是Elasticsearch提供的一種遍歷工具,用來掃描整個數(shù)據(jù)集以獲取大量甚至全量數(shù)據(jù)。它在功能上及內(nèi)部實現(xiàn)上與RDBMS游標(biāo)非常相似。但是,大多數(shù)人在第一次嘗試中都沒有使正確他。以下是一些基本知識:
如果你接觸到scrolls,你可能正在讀取大量數(shù)據(jù)。slicing 很可能會幫助你顯著提高讀取性能。
使用_doc進(jìn)行排序,讀取速度就會提高20%+,而無需進(jìn)行其他任何更改。(_doc是一個偽字段)
scrollId調(diào)用之后會有變化。因此,請確保你始終使用最新檢索的滾動scrollId。
在Reindex的時候使用slicing 也能提升索引數(shù)據(jù)遷移效率。
3.8 單文檔檢索 優(yōu)先使用 GET /index/type/{id}而非POST /index/_search
Elasticsearch使用不同的線程池來處理 GET /index/type/{id}和 POST /index/_search查詢。
使用POST /index/_search與有效載荷{query: {"match": {"_id": "123"}}}(或類似的東西)占據(jù)搜索專用線程池。
在高負(fù)載下,這將同時降低搜索和單個文檔的獲取性能。
所以,單文檔堅持使用:GET /index/type/{id}。
3.9 使用size: 0和includes/ excludes限定字段返回
Elasticsearch在添加size: 0子句前后會帶來顯著的性能差異 。
除非業(yè)務(wù)需要,才返回必要字段,無需返回的字段通過includes和excludes控制。
3.10 提前做好壓力測試,了解系統(tǒng)支持的上限
分享我的個人最佳實踐:
使用應(yīng)用程序的性能基準(zhǔn)( performance benchmarks)測試來估計應(yīng)用程序能提供支持的性能負(fù)載上限。
如基于esrally測試。
避免將線程池與無限制的任務(wù)隊列一起使用。
隊列的過度增長會對內(nèi)存增加壓力。
如果你的應(yīng)用程序是借助第三方引擎中轉(zhuǎn)或?qū)懭霐?shù)據(jù)(例如,從kafka隊列到Elasticsearch集群寫入數(shù)據(jù)),請確保你的生產(chǎn)者對消費者的壓力做出反應(yīng)。
也就是說,如果消費者延遲開始增加,則最好開始降低生產(chǎn)者的速度。
3.11 在查詢中提供明確的超時
幾乎所有的Elasticsearch API都允許用戶指定超時。
找出并擺脫耗時長的操作,節(jié)省相關(guān)資源,建立穩(wěn)定的服務(wù),這將對你的應(yīng)用程序和Elasticsearch集群都有幫助。
3.12 不要使用注入變量的JSON模板
永遠(yuǎn)不要這樣做:
{"query": {"bool": {"filter": [{"term": {"username": {"value": {{username}}}}},{"term": {"password": {"password": {{password}}}}},]}} }防止SQL注入,只要有人通過惡意username 和password輸入,將暴露你的整個數(shù)據(jù)集,這只是時間問題。
我建議使用兩種安全的方法來生成動態(tài)查詢:
使用Elasticsearch官方客戶端提供的查詢模型。(這在Java上效果很好。)
使用JSON庫(例如Jackson)構(gòu)建JSON樹并將其序列化為JSON。
4、實戰(zhàn)技巧(Strategy)
在最后一節(jié)中,我收集了解決上述未解決問題的便捷的實戰(zhàn)技巧。
4.1 ?始終(嘗試)堅持使用最新的JVM和ES版本
Elasticsearch是一個Java應(yīng)用程序。像其他所有Java應(yīng)用程序一樣,它也有hot paths和垃圾回收問題。幾乎每個新的JVM版本都會帶來很多優(yōu)化,你可以不費吹灰之力利用這些優(yōu)化。
Elasticsearch有一個官方頁面,列出了支持的JVM版本和垃圾收集器。在嘗試任何JVM升級之前,請務(wù)必先翻一翻如下文章清單:
https://www.elastic.co/guide/en/elasticsearch/guide/current/_don_8217_t_touch_these_settings.html
https://www.elastic.co/cn/support/matrix#matrix_jvm
注意:Elasticsearch升級也是免費獲得性能提升的來源。
4.2 使用Elasticsearch完整和部分快照進(jìn)行備份
Elasticsearch可以便捷的實現(xiàn)全部索引的全量快照或者部分索引數(shù)據(jù)的增量快照。
根據(jù)你的更新模式和索引大小,找到適合你的用例的快照最佳組合。
也就是說,例如,在00:00時有1個完整快照,在06:00、12:00和18:00時有3個局部增量快照。將它們存儲在第三方存儲也是一種好習(xí)慣。
有一些第三方 插件 可以簡化這些情況。
舉例:https://www.elastic.co/guide/en/elasticsearch/plugins/master/repository.html
與每份備份方案一樣,安全起見,請確保快照可以還原并反復(fù)練習(xí)幾次。
4.3 有一個持續(xù)的性能測試平臺
像任何其他數(shù)據(jù)庫一樣,Elasticsearch在不同條件下顯示不同的性能:
索引,文檔大小;
更新,查詢/檢索模式;
索引,集群設(shè)置;
硬件,操作系統(tǒng),JVM版本等。
很難跟蹤每個設(shè)置的改變以觀察其對整體性能的影響。確保你(至少)進(jìn)行每日性能測試,以幫助縮小范圍,快速定位最近引入的、導(dǎo)致性能下降的可能的原因。
這種性能測試說起來容易做起來難。你需要確保測試環(huán)境:
能有代表性的生產(chǎn)環(huán)境數(shù)據(jù)
配置和生產(chǎn)環(huán)境一致
完全覆蓋用例
考慮包括操作系統(tǒng)緩存的測試的影響。
4.4 使用別名
告訴你一些頗有見地的實操經(jīng)驗:永遠(yuǎn)不要查詢索引,而要查詢 別名。
別名是指向?qū)嶋H索引的指針。你可以將一個或多個索引歸為一個別名。
許多Elasticsearch索引在索引名稱上都有內(nèi)部上下文,例如events-20190515 代表20190515這一天的數(shù)據(jù)。
現(xiàn)在,在查詢events-*索引時,應(yīng)用程序代碼中有兩個選擇:
選擇1:通過特定日期格式即時確定索引名稱:events-YYYYMMDD。
這種方法有兩個主要缺點:
(1)需要回退到特定日期的索引,因此需要對整個代碼庫進(jìn)行相應(yīng)的設(shè)計以支持這種操作。
(2)撇開所有時鐘同步問題,在凌晨,你需要用程序或者腳本控制索引切換,確保數(shù)據(jù)寫入下一天索引。
選擇2:創(chuàng)建一個events別名,指向events-*相關(guān)的索引。負(fù)責(zé)創(chuàng)建新索引的組件如:curator或者ILM(索引生命周期管理)可以自動將別名切換到新索引。
這種方法將帶來兩個明顯的好處:
(1)它沒有以前方法的缺點。
(2)只需指向events 別名,代碼就會更簡潔。
4.5 避免擁有大量同義詞
Elasticsearch支持索引階段和查詢階段指定 同義詞。
沒有同義詞,搜索引擎是不完整的,但實戰(zhàn)使用環(huán)境,注意如下問題:
索引階段同義詞增加了索引大小,并增加了運行時開銷。
查詢階段同義詞不會增加索引的大小,但顧名思義,這會增加運行時開銷。
使用同義詞,很容易在嘗試修復(fù)其他問題時無意間破壞某些其他內(nèi)容。
所以,要持續(xù)監(jiān)視同義詞對性能的影響,并嘗試為添加的每個同義詞編寫測試用例。
同義詞官方文檔:
https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-synonym-tokenfilter.html
4.6 在啟用副本之前強制段合并及增加帶寬
一個非常常見的Elasticsearch用例是:定期(每兩小時一次)創(chuàng)建一個索引。
關(guān)于如何實現(xiàn)最佳性能,SoundCloud上有一篇非常不錯的 文章。從該文中引用,我特別發(fā)現(xiàn)以下幾項“必須”。
在完成索引創(chuàng)建后,務(wù)必啟用副本。
在啟用副本之前,請確保:
(1)通過強制合并來縮小索引大小;
POST /twitter/_forcemerge(2)臨時增加副本傳輸帶寬,直到分配完成為止 indices.recovery.max_bytes_per_sec。默認(rèn):40mb,該屬性允許用戶在恢復(fù)過程中控制網(wǎng)絡(luò)的流量。設(shè)置一個比較大的值會導(dǎo)致網(wǎng)絡(luò)變得繁忙,當(dāng)然恢復(fù)過程也會加快。可以通過如下方式調(diào)整:
PUT /_cluster/settings {"transient": {"indices.recovery.max_bytes_per_sec": "50mb"} }推薦閱讀:
https://developers.soundcloud.com/blog/how-to-reindex-1-billion-documents-in-1-hour-at-soundcloud
4.7 記錄應(yīng)用程序級別指標(biāo)
Kibana對Elasticsearch性能提供了多維監(jiān)控指標(biāo)儀表盤:
indexing,
search latency and throughput,
flush
merge operations
GC pauses
heap size
OS (CPU usage, disk I/O
kernel caches 等......
但,這還不夠。如果由多個應(yīng)用程序使用,Elasticsearch將受到各種訪問模式的影響。
想象一下,你的應(yīng)用程序A試圖刪除1000萬個不太重要的用戶文檔,而另一個組件B試圖更新用戶帳戶詳細(xì)信息。
如果你查看Elasticsearch監(jiān)控指標(biāo),一切都是綠色正常。
但是,此時更新賬戶的用戶可能不滿意他們嘗試更新帳戶時的延遲。
因此,始終為你的Elasticsearch查詢提供額外的應(yīng)用程序級指標(biāo)。
盡管Elasticsearch結(jié)合kibana或者cerbro已經(jīng)為整體集群性能提供了足夠的指標(biāo),但它們?nèi)狈μ囟ㄓ诓僮鞯纳舷挛谋O(jiān)控,需要結(jié)合實際業(yè)務(wù)特事特辦。
4.8 重視CPU的配置選型和使用率監(jiān)控
怎么強調(diào)CPU都不過分。
從我過去的經(jīng)驗來看,無論是寫負(fù)載還是讀負(fù)載場景,CPU一直是我們的瓶頸。
4.9 謹(jǐn)慎編寫自定義的Elasticsearch插件
許多Elasticsearch版本包含重大的內(nèi)部更改。你的插件所基于的公共API很可能會向后不兼容。
你需要調(diào)整部署過程,不能再使用原始的Elasticsearch工作。
由于你的應(yīng)用程序依賴于于插件提供的特定功能,因此在集成測試過程中運行的Elasticsearch實例也需要包含插件。你也就不能再使用原始的Docker鏡像。
5、小結(jié)
本文是基于荷蘭計算機博士:Volkan Yaz?c? ?文章翻譯。翻譯工作得到原作者的同意和許可。原文名稱:Elasticsearch Survival Guide for Developers
原文地址:https://vlkan.com/blog/post/2019/04/25/elasticsearch-survival-guide/#transport-client
文章很多細(xì)節(jié)值得實踐中進(jìn)一步消化吸收。文章沒有直譯,而在原文基礎(chǔ)上,部分內(nèi)容做了增刪,部分內(nèi)容加了實踐和貼圖,以達(dá)到簡潔、通透的目的。
由于語言差異,盡管我翻譯后又修正了2遍,難免部分細(xì)節(jié)還可能有些拗口,歡迎大家留言討論。
想知道更多?掃描下面的二維碼關(guān)注我
后臺回復(fù)”加群“獲取公眾號專屬群聊入口
【精彩推薦】
咱們從頭到尾說一次Java垃圾回收
Netty、Kafka中的零拷貝技術(shù)到底有多牛?
go為什么這么快?
面試前,我們要復(fù)習(xí)多少Redis知識?
Redis性能問題分析
淺談CAP和Paxos共識算法
聊一聊Java中的文件鎖
Kafka為什么這么快?
Paxos、Raft不是一致性算法嘛?
聊聊Java的幾把JVM級鎖
越說越迷糊的CAP
大公司為什么都有API網(wǎng)關(guān)?
面試官居然問我Raft為什么會叫做Raft!
面試官給我挖坑:URI中的//有什么用
網(wǎng)關(guān)Zuul科普
網(wǎng)關(guān)Spring Cloud?Gateway科普
>>>?字節(jié)跳動社招內(nèi)推入口?<<<
>>> 字節(jié)跳動校招內(nèi)推入口 <<<
朕已閱?
總結(jié)
以上是生活随笔為你收集整理的干货 | Elasticsearch开发人员最佳实战指南的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java开发必须掌握的 20+ 种 Sp
- 下一篇: 分布式事务科普(初识篇)