線上實戰問題
問題 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
寫入優化二:優先使用系統自動生成 id
"settings": {
"number_of_replicas": 0
文檔的_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
寫入優化四:合理調整堆內存中索引緩沖區(index_buffer)大小
"settings": {
"refresh_interval": "30s"
堆內存中 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