Elasticsearch:從寫入原理談寫入優化


線上實戰問題

問題 1:想要請問一下,我這邊需求是每分鍾利用 sparksteaming 插入按天的索引 150 萬條數據。一般情況下還好,索引 7 個分片,1 副本,但是偶爾會出現延遲很高的情況。比如:一般情況下 1 分鍾插入 150 萬能正常插入,可能突然就出現了需要 5 分鍾才能插入成功,然后又正常了。很頭疼。

請問這種情況我需要怎么去查看一下是否正常。我已經把副本設置成了 0,還把批量插入的參數從 5000 設置成 2 萬。我節點是 12 個 16g 的,但是好像還是沒有改觀。

問題 2:由於使用了多個分詞器的原因造成數據寫入慢,請問有什么優化的方法嗎?

問題 3:求問:現在日志 收集鏈路 kafka-logstash-es,壓力測試 logstash輸出 70M/s,而 Elasticsearch 索引寫入一半不到。這邊的性能損失可能是什么原因呢?需要怎么調優呢?

類似問題還有很多、很多......


問題分析

以上三個問題各有各自特點,但基本都是基於不同的數據源向 Elasticsearch 寫入過程中遇到的問題。

可以簡單歸結為:Elasticsearch 寫入問題或者寫入優化問題。Elasticsearch 寫入問題涉及寫入流程、寫入原理及優化策略。

本文針對如上幾點展開討論。


基於單文檔/批量文檔寫入流程談寫入優化

單個文檔寫入對應 Index 請求,批量寫入對應 Bulk 請求,Index 和 Bulk 是相同的處理邏輯,請求統一封裝到 BulkRequest中。

流程拆解如下:

第一:客戶端向 Node 1 發送寫數據請求。

注意,此時 Node1 便充當協調節點(cooridiniate)的角色。

第二:節點 Node1 使用文檔的 _id 確定文檔屬於分片 0 。請求會被轉發到 Node 3,因為分片 0 的主分片目前被分配在 Node 3 上。

使用文檔的 _id 確定文檔所屬分片的方法是:路由算法。

路由算法計算公式:

shard = hash(routing) % number_of_primary_shards

  •  routing:文檔 _id。

  •  number_of_primary_shards: 主分片個數。

  •  shard: 文檔 _id 所屬分片號。

第三:Node 3 在主分片上面執行寫入操作。如果寫入成功了,它將請求並行轉發到 Node 1 和 Node 2 的副本分片上。一旦所有的副本分片都報告成功, Node 3 將向協調節點報告寫入成功,協調節點向客戶端報告寫入成功。

如上流程拆解后的注意點:

  •  寫操作必須在主分片執行成功后,才能復制到相關的副本分片。

  •  主分片寫入失敗,則整個請求被認為是寫失敗。

  •  如果有部分副本寫失敗(前提:主分片寫入成功),則整個請求被認為是寫成功。

如果設置了副本,數據會先寫入主分片,主分片再同步到副本分片,同步操作會加重磁盤 IO,間接影響寫入性能。

基於以上分析,既然主分片的寫入起到寫入成敗的決定性作用。那么寫入前將:副本分片寫入前置為0,待寫入完成后復原副本,是不是就能提升寫入性能了呢?

是的!

寫入優化一:副本分片寫入前置為0,等完成寫入后復原副本PUT test-0001
"settings": {
"number_of_replicas": 0

寫入優化二:優先使用系統自動生成 id

文檔的_id 的生成有兩種方式,

  • 第一:系統自動生成id。

  • 第二:外部控制自增id。

但,如果使用外部 id,Elasticsearch 會先嘗試讀取原來文檔的版本號,以判斷是否需要更新。

也就是說,使用外部控制 id 比系統自動生成id要多一次讀取磁盤操作。

所以,非特殊場景推薦使用系統自動生成的 id。


基於 Elasticsearch 寫入原理談寫入優化

Elasticsearch 中的 1 個索引由一個或多個分片組成,每個分片包含多個segment(段),每一個段都是一個倒排索引。如下圖所示:

在 lucene 中,為了實現高索引速度,使用了 segment 分段架構存儲。一批寫入數據保存在一個段中,其中每個段最終落地為磁盤中的單個文件。

將文檔插入 Elasticsearch 時,它們會被寫入緩沖區中,然后在刷新時定期從該緩沖區刷新到段中。刷新頻率由 refresh_interval 參數控制,默認每1秒刷新一次。

也就是說,新插入的文檔在刷新到段(內存中)之前,是不能被搜索到的。如下圖所示:

刷新的本質是:寫入數據由內存 buffer 寫入到內存段中,以保證搜索可見。

來看個例子,加深對 refresh_inteval 的理解,注釋部分就是解讀。

PUT test_0001/_doc/1
"title":"just testing"

# 默認一秒的刷新頻率,秒級可見(用戶無感知)

GET test_0001/_search

如下設置后,寫入后 60s 后才可見。

DELETE test_0001
# 設置了60s的刷新頻率
PUT test_0001
"settings": {
"index":{
"refresh_interval":"60s"

PUT test_0001/_doc/1
{
"title":"just testing"
}
# 60s后才可以被搜索到
GET test_0001/_search

關於是否需要實時刷新:

  • 如果新插入的數據需要近乎實時的搜索功能,則需要頻繁刷新。

  • 如果對最新數據的檢索響應沒有實時性要求,則應增加刷新間隔,以提高數據寫入的效率。

所以,自然我們想到的優化是:調整刷新頻率。

寫入優化三:合理調整刷新頻率

調整方法如下:

方法1:寫入前刷新頻率設置為 -1,寫入后設置為業務實際需要值(比如:30s)。

PUT test-008
"settings": {
"refresh_interval": -1

方法2:直接設置為業務實際需要值(比如:30s)

PUT test-008
"settings": {
"refresh_interval": "30s"

寫入優化四:合理調整堆內存中索引緩沖區(index_buffer)大小

堆內存中 index_buffer 用於存儲新索引的文檔。

填滿后,緩沖區中的文檔將最終寫入磁盤上的某個段。

index_buffer_size 默認值如下所示,為堆內存的 10%。

indices.memory.index_buffer_size: 10%

例如,如果給 JVM 31GB的內存,它將為索引緩沖區提供 3.1 GB的內存,一般情況下足以容納大量數據的寫入操作。

但,如果着實數據量非常大,建議調大該默認值。比如:調整為堆內存的 20%。

調整建議:必須在集群中的每個數據節點上進行配置。

緩存區越大,意味着能緩存數據量越大,相同時間段內,寫盤頻次低、磁盤 IO 小,間接提升寫入性能。

寫入優化五:給堆外內存也留夠空間(常規要求)

這其實算不上寫入優化建議,而是通用集群配置的常規配置。

內存分配設置堆內存比例官方建議:機器內存大小一半,但不超過 32 GB。

一般設置建議:

  •  如果內存大小 >= 64 GB,堆內存設置:31 GB。

  •  如果內存大小 < 64 GB,堆內存設置:內存大小一半。

堆內存之外的內存留給:Lucene 使用。

推薦閱讀:干貨 | 吃透Elasticsearch 堆內存

寫入優化六:bulk 批量寫入而非單個文檔寫入

批量寫入自然會比單個寫入性能要好(批量寫入意味着相同時間產生的段會大,段的總個數自然會少),但批量值的設置一般需要慎重,不要盲目一下搞的很大。

一般建議:遞增步長測試,直到達到資源使用上限。

比如:第一次批量值設置:100,第二次:200,第三次:400,以此類推......

批量值 bulk 已經 ok 了,但集群尚有富余資源,資源利用並沒有飽和怎么辦?

上多線程,通過並發提升寫入性能。

寫入優化七:多線程並發寫入

這點,在 logstash 同步數據到 Elasticsearch,基於spark、kafka、Flink 批量寫入 Elasticsearch時,經常會出現:Bulk Rejections 的報錯。

當批量請求到達集群中的某個節點時,整個請求將被放入批量隊列中,並由批量線程池中的線程進行處理。批量線程池處理來自隊列的請求,並將文檔轉發到副本分片,作為此處理的一部分。子請求完成后,將響應發送到協調節點。

Elasticsearch 具有有限大小的請求隊列的原因是:為了防止集群過載,從而增加了穩定性和可靠性。

如果沒有任何限制,客戶端可以很容易地通過惡意攻擊行為將整個集群搞宕機。

這里就引申出下面的優化點。

寫入優化八:合理設置線程池和隊列大小

關於線程池和隊列,參考:Elasticsearch 線程池和隊列問題,請先看這一篇。

核心建議就是:結合 CPU 核數和 esrally 的測試結果謹慎的調整 write 線程池和隊列大小。

為什么要謹慎設置?

針對批量寫入拒絕(reject)的場景,官方建議:

增加隊列的大小不太可能改善集群的索引性能或吞吐量。相反,這只會使集群在內存中排隊更多數據,這很可能導致批量請求需要更長的時間才能完成。

隊列中的批量請求越多,將消耗更多的寶貴堆空間。如果堆上的壓力太大,則可能導致許多其他性能問題,甚至導致集群不穩定。

https://www.elastic.co/cn/blog/why-am-i-seeing-bulk-rejections-in-my-elasticsearch-cluster

其他寫入優化建議

寫入優化九:設置合理的 Mapping

實戰業務場景中不推薦使用默認 dynamic Mapping,一定要手動設置 Mapping。

  • 舉例1:默認字符串類型是:text 和 keyword 的組合類型,就不見得適用所有業務場景。要結合自己業務場景設置,正文 cont 文本內容一般不需要設置 keyword 類型(因為:不需要排序和聚合操作)。

  • 舉例2:互聯網采集數據存儲場景,正文需要全文檢索,但包含 html 樣式的文本一般留給前端展示用,不需要索引。這時候 Mapping 設置階段要果斷將 index 設置為 false。 

寫入優化十:合理的使用分詞器

分詞器決定分詞的粒度,常見的中文分詞 IK 可細分為:

  •  粗粒度分詞:ik_smart。

  •  細粒度分詞:ik_max_word。 

從存儲角度基於 ik_max_word 分詞的索引會比基於 ik_smart 分詞的索引占據空間大。

而更細粒度自定義分詞 ngram 會占用大量資源,並且可能減慢索引速度並顯着增加索引大小。

所以要結合檢索指標(召回率和精准率)、結合寫入場景斟酌選型。

寫入優化十一:必要時,使用 SSD 磁盤

SSD 很貴,但很香。

尤其針對寫入密集型場景,如果其他優化點都考慮了,這可能是你最后一根“救命稻草“。

寫入優化十二:合理設置集群節點角色

這也是經常被問到的問題,集群規模小的時候,一般節點會混合多種角色,如:主節點 + 數據節點、數據節點 + 協調節點混合部署。

但,集群規模大了之后,硬件資源相對豐富后,強烈建立:獨立主節點、獨立協調節點。

讓各個角色的節點各盡其責,對寫入、檢索性能都會有幫助。

寫入優化十三:推薦使用官方客戶端

推薦使用官方 Elasticsearch API,因為官方在連接池和保持連接狀態方面有優化。

高版本 JAVA API 推薦:官方的High-level-Rest API。

其他寫入優化

待補充......

寫入過程中做好監控

如下是 kibana 監控截圖,其中:index Rate 就是寫入速率。

  • index rate: 每秒寫入的文檔數。

  • search rate:每秒的查詢次數(分片級別,非請求級別),也就意味着一次查詢請求命中的分片數越多,值越大。

小結

Elasticsearch 寫入優化沒有普適的最優優化建議,只有適合自己業務場景的反復試驗、調優,形成屬於自己業務場景的最佳實踐。

鏈接轉自:https://c.m.163.com/news/a/G9L713DK0511FQO9.html?spss=newsapp


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM