1. 程式人生 > >億級 Elasticsearch 性能優化

億級 Elasticsearch 性能優化

eba 路由 方式 use 跟蹤系統 lte 順序 hash 堆內存

技術分享圖片

前言

最近一年使用 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

參考

  • Elasticsearch: 權威指南 -堆內存:大小和交換
  • 關於Elasticsearch性能優化的幾點問題

億級 Elasticsearch 性能優化