億級 Elasticsearch 性能優化

億級 Elasticsearch 性能優化

前言

最近一年使用 Elasticsearch 完成億級別日誌搜索平臺「ELK」,億級別的分佈式跟蹤系統。在設計這些系統的過程中,底層都是採用 Elasticsearch 來做數據的存儲,並且數據量都超過億級別,甚至達到百億級別。

所以趁着有空,就花點時間整理一下具體怎麼做 Elasticsearch 性能優化,希望能對 Elasticsearch 感興趣的同學有所幫助。

背景

Elasticsearch 是一個基於 Lucene 的搜索服務器。它提供了一個分佈式多用戶能力的全文搜索引擎,基於 RESTful web 接口。Elasticsearch 是用 Java 開發的,並作爲 Apache 許可條款下的開放源碼發佈,是當前流行的企業級搜索引擎。設計用於雲計算中,能夠達到實時搜索,穩定,可靠,快速,安裝使用方便。

作爲一個開箱即用的產品,在生產環境上線之後,我們其實不一定能確保其的性能和穩定性。如何根據實際情況提高服務的性能,其實有很多技巧。

下面我就從三個方面分別來講解下優化服務的性能:

  1. 索引效率優化
  2. 查詢效率優化
  3. JVM 配置優化

索引效率優化

索引優化主要是在 Elasticsearch 插入層面優化,如果瓶頸不在這塊,而是在產生數據部分,比如 DB 或者 Hadoop 上,那麼優化方向就需要改變下。同時,Elasticsearch 本身索引速度其實還是蠻快的,具體數據,我們可以參考官方的 benchmark 數據。

批量提交

當有大量數據提交的時候,建議採用批量提交。

比如在做 ELK 過程中 ,Logstash indexer 提交數據到 Elasticsearch 中 ,batch size 就可以作爲一個優化功能點。但是優化 size 大小需要根據文檔大小和服務器性能而定。

像 Logstash 中提交文檔大小超過 20MB ,Logstash 會請一個批量請求切分爲多個批量請求。

如果在提交過程中,遇到 EsRejectedExecutionException 異常的話,則說明集羣的索引性能已經達到極限了。這種情況,要麼提高服務器集羣的資源,要麼根據業務規則,減少數據收集速度,比如只收集 Warn、Error 級別以上的日誌。

優化硬件

優化硬件設備一直是最快速有效的手段。

  1. 在經濟壓力能承受的範圍下, 儘量使用固態硬盤 SSD。SSD 相對於機器硬盤,無論隨機寫還是順序寫,都較大的提升。
  2. 磁盤備份採用 RAID0。因爲 Elasticsearch 在自身層面通過副本,已經提供了備份的功能,所以不需要利用磁盤的備份功能,同時如果使用磁盤備份功能的話,對寫入速度有較大的影響。

增加 Refresh 時間間隔

爲了提高索引性能,Elasticsearch 在寫入數據時候,採用延遲寫入的策略,即數據先寫到內存中,當超過默認 1 秒 (index.refresh_interval)會進行一次寫入操作,就是將內存中 segment 數據刷新到操作系統中,此時我們才能將數據搜索出來,所以這就是爲什麼 Elasticsearch 提供的是近實時搜索功能,而不是實時搜索功能。

當然像我們的內部系統對數據延遲要求不高的話,我們可以通過延長 refresh 時間間隔,可以有效的減少 segment 合併壓力,提供索引速度。在做全鏈路跟蹤的過程中,我們就將 index.refresh_interval 設置爲 30s,減少 refresh 次數。

同時,在進行全量索引時,可以將 refresh 次數臨時關閉,即 index.refresh_interval 設置爲 -1,數據導入成功後再打開到正常模式,比如 30s。

減少副本數量

Elasticsearch 默認副本數量爲 3 個,雖然這樣會提高集羣的可用性,增加搜索的併發數,但是同時也會影響寫入索引的效率。

在索引過程中,需要把更新的文檔發到副本節點上,等副本節點生效後在進行返回結束。使用 Elasticsearch 做業務搜索的時候,建議副本數目還是設置爲 3 個,但是像內部 ELK 日誌系統、分佈式跟蹤系統中,完全可以將副本數目設置爲 1 個。

查詢效率優化

路由

當我們查詢文檔的時候,Elasticsearch 如何知道一個文檔應該存放到哪個分片中呢?它其實是通過下面這個公式來計算出來

shard = hash(routing) % number_of_primary_shards

routing 默認值是文檔的 id,也可以採用自定義值,比如用戶 id。

不帶 routing 查詢

在查詢的時候因爲不知道要查詢的數據具體在哪個分片上,所以整個過程分爲 2 個步驟

  • 分發:請求到達協調節點後,協調節點將查詢請求分發到每個分片上。
  • 聚合: 協調節點蒐集到每個分片上查詢結果,在將查詢的結果進行排序,之後給用戶返回結果。

帶 routing 查詢

查詢的時候,可以直接根據 routing 信息定位到某個分配查詢,不需要查詢所有的分配,經過協調節點排序。

向上面自定義的用戶查詢,如果 routing 設置爲 userid 的話,就可以直接查詢出數據來,效率提升很多。

Filter VS Query

Ebay 曾經分享過他們使用 Elasticsearch 的經驗中說到:

Use filter context instead of query context if possible.
儘可能使用過濾器上下文(Filter)替代查詢上下文(Query

  • Query:此文檔與此查詢子句的匹配程度如何?
  • Filter:此文檔和查詢子句匹配嗎?

Elasticsearch 針對 Filter 查詢只需要回答「是」或者「否」,不需要像 Query 查詢一下計算相關性分數,同時 Filter 結果可以緩存。

大翻頁

在使用 Elasticsearch 過程中,應儘量避免大翻頁的出現。

正常翻頁查詢都是從 From 開始 Size 條數據,這樣就需要在每個分片中查詢打分排名在前面的 From + Size 條數據。協同節點收集每個分配的前 From + Size 條數據。協同節點一共會受到 N * ( From + Size )條數據,然後進行排序,再將其中 From 到 From + Size 條數據返回出去。

如果 From 或者 Size 很大的話,導致參加排序的數量會同步擴大很多,最終會導致 CPU 資源消耗增大。

可以通過使用 Elasticsearch scroll 和 scroll-scan 高效滾動的方式來解決這樣的問題。具體寫法,可以參考 Elasticsearch: 權威指南 - scroll 查詢

JVM 設置

32G 現象

Elasticsearch 默認安裝後設置的堆內存是 1 GB。 對於任何一個業務部署來說, 這個設置都太小了。

比如機器有 64G 內存,那麼我們是不是設置的越大越好呢?

其實不是的。

主要 Elasticsearch 底層使用 Lucene。Lucene 被設計爲可以利用操作系統底層機制來緩存內存數據結構。 Lucene 的段是分別存儲到單個文件中的。因爲段是不可變的,這些文件也都不會變化,這是對緩存友好的,同時操作系統也會把這些段文件緩存起來,以便更快的訪問。

如果你把所有的內存都分配給 Elasticsearch 的堆內存,那將不會有剩餘的內存交給 Lucene。 這將嚴重地影響全文檢索的性能。

標準的建議是把 50% 的可用內存作爲 Elasticsearch 的堆內存,保留剩下的 50%。當然它也不會被浪費,Lucene 會很樂意利用起餘下的內存。

同時瞭解過 ES 的同學都聽過過「不要超過 32G」的說法吧。

其實主要原因是 :JVM 在內存小於 32 GB 的時候會採用一個內存對象指針壓縮技術。

在 Java 中,所有的對象都分配在堆上,並通過一個指針進行引用。 普通對象指針(OOP)指向這些對象,通常爲 CPU 字長 的大小:32 位或 64 位,取決於你的處理器。指針引用的就是這個 OOP 值的字節位置。

對於 32 位的系統,意味着堆內存大小最大爲 4 GB。對於 64 位的系統, 可以使用更大的內存,但是 64 位的指針意味着更大的浪費,因爲你的指針本身大了。更糟糕的是, 更大的指針在主內存和各級緩存(例如 LLC,L1 等)之間移動數據的時候,會佔用更多的帶寬.

所以最終我們都會採用 31 G 設置

-Xms 31g
-Xmx 31g

假設你有個機器有 128 GB 的內存,你可以創建兩個節點,每個節點內存分配不超過 32 GB。 也就是說不超過 64 GB 內存給 ES 的堆內存,剩下的超過 64 GB 的內存給 Lucene

參考

題外話

我在51CTO博客開了一個專欄——《帶你玩轉高可用》希望可以幫助有一定分佈式系統架構知識,在架構方面力求進階的分佈式系統架構從業人員,提高架構可用性,實現高可用目標。

《帶你玩轉高可用帶你玩轉高可用》

億級 Elasticsearch 性能優化

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