前言
最近一年使用 Elasticsearch 完成億級別日志搜索平臺「ELK」权纤,億級別的分布式跟蹤系統(tǒng)。在設(shè)計(jì)這些系統(tǒng)的過程中仆葡,底層都是采用 Elasticsearch 來做數(shù)據(jù)的存儲逻悠,并且數(shù)據(jù)量都超過億級別,甚至達(dá)到百億級別蚯窥。
所以趁著有空掸鹅,就花點(diǎn)時(shí)間整理一下具體怎么做 Elasticsearch 性能優(yōu)化,希望能對 Elasticsearch 感興趣的同學(xué)有所幫助拦赠。
背景
Elasticsearch 是一個(gè)基于 Lucene 的搜索服務(wù)器巍沙。它提供了一個(gè)分布式多用戶能力的全文搜索引擎,基于 RESTful web 接口荷鼠。Elasticsearch 是用 Java 開發(fā)的句携,并作為 Apache 許可條款下的開放源碼發(fā)布,是當(dāng)前流行的企業(yè)級搜索引擎允乐。設(shè)計(jì)用于云計(jì)算中矮嫉,能夠達(dá)到實(shí)時(shí)搜索,穩(wěn)定牍疏,可靠蠢笋,快速,安裝使用方便鳞陨。
作為一個(gè)開箱即用的產(chǎn)品昨寞,在生產(chǎn)環(huán)境上線之后,我們其實(shí)不一定能確保其的性能和穩(wěn)定性厦滤。如何根據(jù)實(shí)際情況提高服務(wù)的性能援岩,其實(shí)有很多技巧。
下面我就從三個(gè)方面分別來講解下優(yōu)化服務(wù)的性能:
- 索引效率優(yōu)化
- 查詢效率優(yōu)化
- JVM 配置優(yōu)化
索引效率優(yōu)化
索引優(yōu)化主要是在 Elasticsearch 插入層面優(yōu)化馁害,如果瓶頸不在這塊窄俏,而是在產(chǎn)生數(shù)據(jù)部分,比如 DB 或者 Hadoop 上碘菜,那么優(yōu)化方向就需要改變下凹蜈。同時(shí),Elasticsearch 本身索引速度其實(shí)還是蠻快的忍啸,具體數(shù)據(jù)仰坦,我們可以參考官方的 benchmark 數(shù)據(jù)。
批量提交
當(dāng)有大量數(shù)據(jù)提交的時(shí)候计雌,建議采用批量提交悄晃。
比如在做 ELK 過程中 ,Logstash indexer 提交數(shù)據(jù)到 Elasticsearch 中 ,batch size 就可以作為一個(gè)優(yōu)化功能點(diǎn)妈橄。但是優(yōu)化 size 大小需要根據(jù)文檔大小和服務(wù)器性能而定庶近。
像 Logstash 中提交文檔大小超過 20MB ,Logstash 會請一個(gè)批量請求切分為多個(gè)批量請求眷蚓。
如果在提交過程中鼻种,遇到 EsRejectedExecutionException 異常的話,則說明集群的索引性能已經(jīng)達(dá)到極限了沙热。這種情況叉钥,要么提高服務(wù)器集群的資源,要么根據(jù)業(yè)務(wù)規(guī)則篙贸,減少數(shù)據(jù)收集速度投队,比如只收集 Warn、Error 級別以上的日志爵川。
優(yōu)化硬件
優(yōu)化硬件設(shè)備一直是最快速有效的手段敷鸦。
- 在經(jīng)濟(jì)壓力能承受的范圍下, 盡量使用固態(tài)硬盤 SSD寝贡。SSD 相對于機(jī)器硬盤轧膘,無論隨機(jī)寫還是順序?qū)懀驾^大的提升兔甘。
- 磁盤備份采用 RAID0。因?yàn)?Elasticsearch 在自身層面通過副本鳞滨,已經(jīng)提供了備份的功能洞焙,所以不需要利用磁盤的備份功能,同時(shí)如果使用磁盤備份功能的話拯啦,對寫入速度有較大的影響澡匪。
增加 Refresh 時(shí)間間隔
為了提高索引性能,Elasticsearch 在寫入數(shù)據(jù)時(shí)候褒链,采用延遲寫入的策略唁情,即數(shù)據(jù)先寫到內(nèi)存中,當(dāng)超過默認(rèn) 1 秒 (index.refresh_interval)會進(jìn)行一次寫入操作甫匹,就是將內(nèi)存中 segment 數(shù)據(jù)刷新到操作系統(tǒng)中甸鸟,此時(shí)我們才能將數(shù)據(jù)搜索出來,所以這就是為什么 Elasticsearch 提供的是近實(shí)時(shí)搜索功能兵迅,而不是實(shí)時(shí)搜索功能抢韭。
當(dāng)然像我們的內(nèi)部系統(tǒng)對數(shù)據(jù)延遲要求不高的話,我們可以通過延長 refresh 時(shí)間間隔恍箭,可以有效的減少 segment 合并壓力刻恭,提供索引速度。在做全鏈路跟蹤的過程中扯夭,我們就將 index.refresh_interval 設(shè)置為 30s鳍贾,減少 refresh 次數(shù)鞍匾。
同時(shí),在進(jìn)行全量索引時(shí)骑科,可以將 refresh 次數(shù)臨時(shí)關(guān)閉橡淑,即 index.refresh_interval 設(shè)置為 -1,數(shù)據(jù)導(dǎo)入成功后再打開到正常模式纵散,比如 30s梳码。
減少副本數(shù)量
Elasticsearch 默認(rèn)副本數(shù)量為 3 個(gè),雖然這樣會提高集群的可用性伍掀,增加搜索的并發(fā)數(shù)掰茶,但是同時(shí)也會影響寫入索引的效率。
在索引過程中蜜笤,需要把更新的文檔發(fā)到副本節(jié)點(diǎn)上濒蒋,等副本節(jié)點(diǎn)生效后在進(jìn)行返回結(jié)束。使用 Elasticsearch 做業(yè)務(wù)搜索的時(shí)候把兔,建議副本數(shù)目還是設(shè)置為 3 個(gè)沪伙,但是像內(nèi)部 ELK 日志系統(tǒng)、分布式跟蹤系統(tǒng)中县好,完全可以將副本數(shù)目設(shè)置為 1 個(gè)围橡。
查詢效率優(yōu)化
路由
當(dāng)我們查詢文檔的時(shí)候,Elasticsearch 如何知道一個(gè)文檔應(yīng)該存放到哪個(gè)分片中呢缕贡?它其實(shí)是通過下面這個(gè)公式來計(jì)算出來
shard = hash(routing) % number_of_primary_shards
routing 默認(rèn)值是文檔的 id翁授,也可以采用自定義值,比如用戶 id晾咪。
不帶 routing 查詢
在查詢的時(shí)候因?yàn)椴恢酪樵兊臄?shù)據(jù)具體在哪個(gè)分片上收擦,所以整個(gè)過程分為 2 個(gè)步驟
- 分發(fā):請求到達(dá)協(xié)調(diào)節(jié)點(diǎn)后,協(xié)調(diào)節(jié)點(diǎn)將查詢請求分發(fā)到每個(gè)分片上谍倦。
- 聚合: 協(xié)調(diào)節(jié)點(diǎn)搜集到每個(gè)分片上查詢結(jié)果塞赂,在將查詢的結(jié)果進(jìn)行排序,之后給用戶返回結(jié)果昼蛀。
帶 routing 查詢
查詢的時(shí)候宴猾,可以直接根據(jù) routing 信息定位到某個(gè)分配查詢,不需要查詢所有的分配叼旋,經(jīng)過協(xié)調(diào)節(jié)點(diǎn)排序鳍置。
向上面自定義的用戶查詢,如果 routing 設(shè)置為 userid 的話送淆,就可以直接查詢出數(shù)據(jù)來税产,效率提升很多。
Filter VS Query
Ebay 曾經(jīng)分享過他們使用 Elasticsearch 的經(jīng)驗(yàn)中說到:
Use filter context instead of query context if possible.
盡可能使用過濾器上下文(Filter)替代查詢上下文(Query
- Query:此文檔與此查詢子句的匹配程度如何?
- Filter:此文檔和查詢子句匹配嗎辟拷?
Elasticsearch 針對 Filter 查詢只需要回答「是」或者「否」撞羽,不需要像 Query 查詢一下計(jì)算相關(guān)性分?jǐn)?shù),同時(shí) Filter 結(jié)果可以緩存衫冻。
大翻頁
在使用 Elasticsearch 過程中诀紊,應(yīng)盡量避免大翻頁的出現(xiàn)。
正常翻頁查詢都是從 From 開始 Size 條數(shù)據(jù)隅俘,這樣就需要在每個(gè)分片中查詢打分排名在前面的 From + Size 條數(shù)據(jù)邻奠。協(xié)同節(jié)點(diǎn)收集每個(gè)分配的前 From + Size 條數(shù)據(jù)。協(xié)同節(jié)點(diǎn)一共會受到 N * ( From + Size )條數(shù)據(jù)为居,然后進(jìn)行排序碌宴,再將其中 From 到 From + Size 條數(shù)據(jù)返回出去。
如果 From 或者 Size 很大的話蒙畴,導(dǎo)致參加排序的數(shù)量會同步擴(kuò)大很多贰镣,最終會導(dǎo)致 CPU 資源消耗增大。
可以通過使用 Elasticsearch scroll 和 scroll-scan 高效滾動的方式來解決這樣的問題膳凝。具體寫法碑隆,可以參考 Elasticsearch: 權(quán)威指南 - scroll 查詢
JVM 設(shè)置
32G 現(xiàn)象
Elasticsearch 默認(rèn)安裝后設(shè)置的堆內(nèi)存是 1 GB。 對于任何一個(gè)業(yè)務(wù)部署來說蹬音, 這個(gè)設(shè)置都太小了上煤。
比如機(jī)器有 64G 內(nèi)存,那么我們是不是設(shè)置的越大越好呢著淆?
其實(shí)不是的楼入。
主要 Elasticsearch 底層使用 Lucene。Lucene 被設(shè)計(jì)為可以利用操作系統(tǒng)底層機(jī)制來緩存內(nèi)存數(shù)據(jù)結(jié)構(gòu)牧抽。 Lucene 的段是分別存儲到單個(gè)文件中的。因?yàn)槎问遣豢勺兊囊W@些文件也都不會變化扬舒,這是對緩存友好的,同時(shí)操作系統(tǒng)也會把這些段文件緩存起來凫佛,以便更快的訪問讲坎。
如果你把所有的內(nèi)存都分配給 Elasticsearch 的堆內(nèi)存,那將不會有剩余的內(nèi)存交給 Lucene愧薛。 這將嚴(yán)重地影響全文檢索的性能晨炕。
標(biāo)準(zhǔn)的建議是把 50% 的可用內(nèi)存作為 Elasticsearch 的堆內(nèi)存,保留剩下的 50%毫炉。當(dāng)然它也不會被浪費(fèi)瓮栗,Lucene 會很樂意利用起余下的內(nèi)存。
同時(shí)了解過 ES 的同學(xué)都聽過過「不要超過 32G」的說法吧。
其實(shí)主要原因是 :JVM 在內(nèi)存小于 32 GB 的時(shí)候會采用一個(gè)內(nèi)存對象指針壓縮技術(shù)费奸。
在 Java 中弥激,所有的對象都分配在堆上,并通過一個(gè)指針進(jìn)行引用愿阐。 普通對象指針(OOP)指向這些對象微服,通常為 CPU 字長 的大小:32 位或 64 位缨历,取決于你的處理器以蕴。指針引用的就是這個(gè) OOP 值的字節(jié)位置。
對于 32 位的系統(tǒng)辛孵,意味著堆內(nèi)存大小最大為 4 GB丛肮。對于 64 位的系統(tǒng), 可以使用更大的內(nèi)存觉吭,但是 64 位的指針意味著更大的浪費(fèi)腾供,因?yàn)槟愕闹羔槺旧泶罅恕8愀獾氖牵?更大的指針在主內(nèi)存和各級緩存(例如 LLC鲜滩,L1 等)之間移動數(shù)據(jù)的時(shí)候伴鳖,會占用更多的帶寬.
所以最終我們都會采用 31 G 設(shè)置
-Xms 31g
-Xmx 31g
假設(shè)你有個(gè)機(jī)器有 128 GB 的內(nèi)存,你可以創(chuàng)建兩個(gè)節(jié)點(diǎn)徙硅,每個(gè)節(jié)點(diǎn)內(nèi)存分配不超過 32 GB榜聂。 也就是說不超過 64 GB 內(nèi)存給 ES 的堆內(nèi)存,剩下的超過 64 GB 的內(nèi)存給 Lucene