ElasticSearch 讀寫原理和性能優化

1 寫入原理

  1. ES 每次數據寫入會先存放在內存 buffer 中,然後默認每隔 1s 將數據 refresh 進入 segment 文件內(此時寫入的數據即可被搜索到),並隨之同步到 FileSystem Cache 中。

  2. 爲了持久化數據,每次寫入操作也會同步至磁盤的 translog 中,當 translog 文件大小超過某個閾值(默認 512M)或者達到某個時間週期(默認 30min),系統會將內存 buffer 數據寫入新的 segment,並將 FileSystem Cache 中的緩存數據 flush 進入磁盤,清空 translog 當前數據。

  3. 若服務器宕機,ES 會從磁盤恢復 commit point 指向的 segment 數據,並且將 translog 裏的操作全部重新執行。

注:ES 爲了支持全文檢索,使用倒排索引來存儲數據。寫入磁盤後的倒排索引是不會進行更新和修改的,這樣有以下幾點好處:

  1. 不用擔心併發時鎖的問題。

  2. 由於數據不會變更,將數據讀入操作系統緩存後,可以避免去磁盤請求,大大提升了檢索效率。

  3. 一些附帶的緩存數據都持續生效。

  4. 單獨寫一個大的倒排索引時可以進行數據壓縮。

ES 通過使用多個倒排索引來進行數據的寫入操作,如果有數據新增,則寫入新的倒排索引中,如果有更新或者刪除,將舊數據放入 .del 文件,並通過增加版本號產生一條新數據。

寫請求首先會被打到協調節點,協調節點決定數據寫入的主分片位置,並在節點上執行寫入操作。

成功後通知副分片所在的節點執行,所有分片執行成功後反饋給協調節點,之後返回成功給客戶端。

2 搜索原理

  1. 客戶端發送請求到協調節點

  2. 協調節點將搜索請求轉發到所有分片對應的主分片或副本分片

  3. query phase:每個 shard 將自己的搜索結果(其實就是一些 doc id)返回給協調節點,由協調節點進行數據的合併、排序、分頁等操作,產出最終結果

  4. fetch phase:接着由協調節點根據 doc id 去各個節點上拉取實際的 document 數據,最終返回給客戶端

3 性能優化

讀優化

  1. 搜索性能:term、match  > nested > parent-child

  2. 避免使用腳本。如果一定要用,則應該優先考慮 painless 和 expressions

  3. 爲不再更新的只讀索引執行 force merge,將 Lucene 索引合併爲單個分段,可以提升查詢速度

  4. 利用自適應副本選擇(ARS)提升 ES 響應速度

    爲了充分利用計算資源和負載均衡,協調節點將搜索請求輪詢轉發到分片的每個副本,輪詢策略是負載均衡過程中最簡單的策略,任何一個負載均衡器都具備這種基礎的策略,缺點是不考慮後端實際系統壓力和健康水平。例如,一個分片的三個副本分佈在三個節點上,其中 Node2 可能因爲長時間 GC、磁盤 I/O 過高、網絡帶寬跑滿等原因處於忙碌狀態,如下圖所示。

    如果搜索請求被轉發到副本 2,則會看到相對於其他分片來說,副本 2 有更高的延遲。

    ES 希望這個過程足夠智能,能夠將請求路由到其他數據副本,直到該節點恢復到足以處理更多搜索請求的程度。在 ES 中,此過程稱爲 “自適應副本選擇”,從 ES 7.0 開始默認開啓。

  5. 不關注搜索召回匹配得分時,使用 filter 代替 must

  6. 使用滾動查詢代替深度分頁

  7. 帶用 routing 查詢

寫優化

  1. translog flush 間隔調整

從 ES 2.x 開始,在默認設置下,translog 的持久化策略爲:每個請求都 flush。對應配置項如下:index.translog.durability: request。

這是影響 ES 寫入速度的最大因素。但是隻有這樣,寫操作纔有可能是可靠的。

如果系統可以接受一定概率的數據丟失,則調整 translog 持久化策略爲週期性和一定大小的時候 flush。如下:

##translog的刷盤策略按sync_interval配置指定的時間週期進行
index.translog.durability: async
##加大translog刷盤間隔時間。默認爲5s,不可低於100ms
index.translog.sync_interval: 120s
##超過這個大小會導致refresh操作,產生新的Lucene分段。默認值爲512MB
index.translog.flush_threshold_size: 1024mb
  1. 索引刷新間隔 refresh_interval

默認情況下索引的 refresh_interval 爲 1 秒,這意味着數據寫 1 秒後就可以被搜索到,每次索引的 refresh 會產生一個新的 Lucene 段,這會導致頻繁的 segmentmerge 行爲。

如果不需要這麼高的搜索實時性,應該降低索引 refresh 週期,例如:index.refresh_interval: 120s

  1. 使用 bulk 請求

批量寫比一個索引請求只寫單個文檔的效率高得多,但是要注意 bulk 請求的整體字節數不要太大,太大的請求可能會給集羣帶來內存壓力,因此每個請求最好避免超過幾十兆字節。

  1. 自動生成 doc ID

寫入 doc 時如果外部指定了 id,則 ES 會先嚐試讀取原來 doc 的版本號,以判斷是否需要更新。這會涉及一次讀取磁盤的操作,通過自動生成 doc ID 可以避免這個環節

  1. 調整字段 Mappings

a. 減少字段數量,對於不需要建立索引的字段,不寫入 ES。

b. 將不需要建立索引的字段 index 屬性設置爲 not_analyzed 或 no。對字段不分詞,或者不索引,可以減少很多運算操作,降低 CPU 佔用。尤其是 binary 類型,默認情況下佔用 CPU 非常高,而這種類型進行分詞通常沒有什麼意義。

c. 減少字段內容長度,如果原始數據的大段內容無須全部建立索引,則可以儘量減少不必要的內容。

d. 使用不同的分析器(analyzer),不同的分析器在索引過程中運算複雜度也有較大的差異。

  1. 減少副本數量

ES 索引多個副本可以提高搜索併發度,但是也會影響 ES 的寫入效率,針對日誌等對數據一致性要求不高的業務,可以將副本數減少爲 1。

  1. 使用 reindex 同步數據

存儲優化

  1. 禁用 doc values

所有支持 doc value 的字段都默認啓用了 doc value。如果確定不需要對字段進行排序或聚合,或者從腳本訪問字段值,則可以禁用 doc value 以節省磁盤空間:

  1. 不要使用默認的動態字符串映射

默認的動態字符串映射會把字符串類型的字段同時索引爲 text 和 keyword。如果只需要其中之一,則顯然是一種浪費。通常,id 字段只需作爲 keyword 類型進行索引,而 body 字段只需作爲 text 類型進行索引。

  1. 數值類型長度夠用就好

爲數值類型選擇的字段類型也可能會對磁盤使用空間產生較大影響,整型可以選擇 byte、short、integer 或 long,浮點型可以選擇 scaled_float、float、double、half_float。

每個數據類型的字節長度是不同的,爲業務選擇夠用的最小數據類型,可以節省磁盤空間。

參考文章

  1. https://www.elastic.co/guide/en/elasticsearch/guide

2.《ElasticSearch 源碼解析與優化實戰》

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Mf06R8KFY4jgOCNC9gEG0A