滴滴ElasticSearch千萬級TPS寫入效能翻倍技術剖析
桔妹導讀:滴滴ElasticSearch平臺承接了公司內部所有使用ElasticSearch的業務,包括核心搜尋、RDS從庫、日誌檢索、安全資料分析、指標資料分析等等。平臺規模達到了3000+節點,5PB 的資料儲存,超過萬億條資料。平臺寫入的峰值寫入TPS達到了2000w/s,每天近 10 億次檢索查詢。為了承接這麼大的體量和豐富的使用場景,滴滴ElasticSearch需要解決穩定性、易用性、效能、成本等諸多問題。我們在4年多的時間裡,做了大量優化,積攢了非常豐富的經驗。通過建設滴滴搜尋平臺,打造滴滴ES引擎,全方位提升使用者使用ElasticSearch體驗。這次給大家分享的是滴滴在寫入效能優化的實踐,優化後,我們將ES索引的寫入效能翻倍,結合資料冷熱分離場景,支援大規格儲存的物理機,給公司每年節省千萬左右的伺服器成本。
1.背景
前段時間,為了降低使用者使用ElasticSearch的儲存成本,我們做了資料的冷熱分離。為了保持叢集磁碟利用率不變,我們減少了熱節點數量。ElasticSearch叢集開始出現寫入瓶頸,節點產生大量的寫入rejected,大量從kafka同步的資料出現寫入延遲。我們深入分析寫入瓶頸,找到了突破點,最終將Elasticsearch的寫入效能提升一倍以上,解決了ElasticSearch瓶頸導致的寫入延遲。這篇文章介紹了我們是如何發現寫入瓶頸,並對瓶頸進行深入分析,最終進行了創新性優化,極大的提升了寫入效能。
2.寫入瓶頸分析
2.1發現瓶頸
我們去分析這些延遲問題的時候,發現了一些不太好解釋的現象。之前做效能測試時,ES節點cpu利用率能超過80%,而生產環境延遲索引所在的節點cpu資源只使用了不到50%,叢集平均cpu利用率不到40%,這時候IO和網路頻寬也沒有壓力。通過提升寫入資源,寫入速度基本沒增加。於是我們開始一探究竟,我們選取了一個索引進行驗證,該索引使用10個ES節點。從下圖看到,寫入速度不到20w/s,10個ES節點的cpu,峰值在40-50%之間。
為了確認客戶端資源是足夠的,在客戶端不做任何調整的情況下,將索引從10個節點,擴容到16個節點,從下圖看到,寫入速度來到了30w/s左右。
這證明了瓶頸出在服務端,ES節點擴容後,效能提升,說明10個節點寫入已經達到瓶頸。但是上圖可以看到,CPU最多隻到了50%,而且此時IO也沒達到瓶頸。
2.2 ES寫入模型說明
這裡要先對ES寫入模型進行說明,下面分析原因會跟寫入模型有關。
客戶端一般是準備好一批資料寫入ES,這樣能極大減少寫入請求的網路互動,使用的是ES的BULK介面,請求名為BulkRequest。這樣一批資料寫入ES的ClientNode。ClientNode對這一批資料按資料中的routing值進行分發,組裝成一批BulkShardRequest請求,傳送給每個shard所在的DataNode。傳送BulkShardRequest請求是非同步的,但是BulkRequest請求需要等待全部BulkShardRequest響應後,再返回客戶端。
2.3 尋找原因
我們在ES ClientNode上有記錄BulkRequest寫入slowlog。
-
items
是一個BulkRequest的傳送請求數 -
totalMills
是BulkRequest請求的耗時 -
max
記錄的是耗時最長的BulkShardRequest請求 -
avg
記錄的是所有BulkShardRequest請求的平均耗時。
我這裡擷取了部分示例。
[xxx][INFO ][o.e.m.r.RequestTracker ] [log6-clientnode-sf-5aaae-10] bulkDetail||requestId=null||size=10486923||items=7014||totalMills=2206||max=2203||avg=37
[xxx][INFO ][o.e.m.r.RequestTracker ] [log6-clientnode-sf-5aaae-10] bulkDetail||requestId=null||size=210506||items=137||totalMills=2655||max=2655||avg=218
從示例中可以看到,2條記錄的avg相比max都小了很多。一個BulkRequest請求的耗時,取決於最後一個BulkShardRequest請求的返回。這就很容易聯想到分散式系統的長尾效應。
接下來再看一個現象,我們分析了某個節點的write執行緒的狀態,發現節點有時候write執行緒全是runnable狀態,有時候又有大量在waiting。此時寫入是有瓶頸的,runnable狀態可以理解,但卻經常出現waiting狀態。所以這也能印證了CPU利用率不高。同時也論證長尾效應的存在,因為長尾節點繁忙,ClientNode在等待繁忙節點返回BulkShardRequest請求,其他節點可能出現相對空閒的狀態。下面是一個節點2個時刻的執行緒狀態:
時刻一:
時刻二:
2.4 瓶頸分析
谷歌大神Jeffrey Dean《The Tail At Scale》介紹了長尾效應,以及導致長尾效應的原因。總結下來,就是正常請求都很快,但是偶爾單次請求會特別慢。這樣在分散式操作時會導致長尾效應。我們從ES原理和實現中分析,造成ES單次請求特別慢的原因。發現了下面幾個因素會造成長尾問題:
2.4.1 lucene refresh
我們開啟lucene引擎內部的一些日誌,可以看到:
write執行緒是用來處理BulkShardRequest請求的,但是從截圖的日誌可以看到,write執行緒也會會進行refresh操作。這裡面的實現比較複雜,簡單說,就是ES定期會將寫入buffer的資料refresh成segment,ES為了防止refresh不過來,會在BulkShardRequest請求的時候,判斷當前shard是否有正在refresh的任務,有的話,就會幫忙一起分攤refresh壓力,這個是在write執行緒中進行的。這樣的問題就是造成單次BulkShardRequest請求寫入很慢。還導致長時間佔用了write執行緒。在write queue的原因會具體介紹這種危害。
2.4.2 translog ReadWriteLock
ES的translog類似LSM-Tree的WAL log。ES實時寫入的資料都在lucene記憶體buffer中,所以需要依賴寫入translog保證資料的可靠性。ES translog具體實現中,在寫translog的時候會上ReadLock。在translog過期、翻滾的時候會上WriteLock。這會出現,在WriteLock期間,實時寫入會等待ReadLock,造成了BulkShardRequest請求寫入變慢。我們配置的tranlog寫入模式是async,正常開銷是非常小的,但是從圖中可以看到,寫translog偶爾可能超過100ms。
2.4.3 write queue
ES DataNode的寫入是用標準的執行緒池模型是,提供一批active執行緒,我們一般配置為跟cpu個數相同。然後會有一個write queue,我們配置為1000。DataNode接收BulkShardRequest請求,先將請求放入write queue,然後active執行緒有空隙的,就會從queue中獲取BulkShardRequest請求。這種模型下,當寫入active執行緒繁忙的時候,queue中會堆積大量的請求。這些請求在等待執行,而從ClientNode角度看,就是BulkShardRequest請求的耗時變長了。下面日誌記錄了action的slowlog,其中waitTime就是請求等待執行的時間,可以看到等待時間超過了200ms。
[xxx][INFO ][o.e.m.r.RequestTracker ] [log6-datanode-sf-4f136-100] actionStats||action=indices:data/write/bulk[s][p]||requestId=546174589||taskId=6798617657||waitTime=231||totalTime=538
[xxx][INFO ][o.e.m.r.RequestTracker ] [log6-datanode-sf-4f136-100] actionStats||action=indices:data/write/bulk[s][p]||requestId=546174667||taskId=6949350415||waitTime=231||totalTime=548
2.4.4 JVM GC
ES正常一次寫入請求基本在亞毫秒級別,但是jvm的gc可能在幾十到上百毫秒,這也增加了BulkShardRequest請求的耗時。這些加重長尾現象的case,會導致一個情況就是,有的節點很繁忙,發往這個節點的請求都delay了,而其他節點卻空閒下來,這樣整體cpu就無法充分利用起來。
2.5 論證結論
長尾問題主要來自於BulkRequest的一批請求會分散寫入多個shard,其中有的shard的請求會因為上述的一些原因導致響應變慢,造成了長尾。如果每次BulkRequest只寫入一個shard,那麼就不存在寫入等待的情況,這個shard返回後,ClientNode就能將結果返回給客戶端,那麼就不存在長尾問題了。
我們做了一個驗證,修改客戶端SDK,在每批BulkRequest寫入的時候,都傳入相同的routing值,然後寫入相同的索引,這樣就保證了BulkRequest的一批資料,都寫入一個shard中。
優化後,第一個平穩曲線是,每個bulkRequest為10M的情況,寫入速度在56w/s左右。之後將bulkRequest改為1M(10M差不多有4000條記錄,之前寫150個shard,所以bulkSize比較大)後,效能還有進一步提升,達到了65w/s。
從驗證結果可以看到,每個bulkRequest只寫一個shard的話,效能有很大的提升,同時cpu也能充分利用起來,這符合之前單節點壓測的cpu利用率預期。
3. 效能優化
從上面的寫入瓶頸分析,我們發現了ES無法將資源用滿的原因來自於分散式的長尾問題。於是我們著重思考如何消除分散式的長尾問題。然後也在探尋其他的優化點。整體效能優化,我們分成了三個方向:
-
橫向優化,優化寫入模型,消除分散式長尾效應。
-
縱向優化,提升單節點寫入能力。
-
應用優化,探究業務節省資源的可能。
這次的效能優化,我們在這三個方向上都取得了一些突破。
3.1 優化寫入模型
寫入模型的優化思路是將一個BulkRequest請求,轉發到儘量少的shard,甚至只轉發到一個shard,來減少甚至消除分散式長尾效應。我們完成的寫入模型優化,最終能做到一個BulkRequest請求只轉發到一個shard,這樣就消除了分散式長尾效應。
寫入模型的優化分成兩個場景。一個是資料不帶routing的場景,這種場景使用者不依賴資料分佈,比較容易優化的,可以做到只轉發到一個shard。另一個是資料帶了routing的場景,使用者對資料分佈有依賴,針對這種場景,我們也實現了一種優化方案。
3.1.1 不帶routing場景
由於使用者對routing分佈沒有依賴,ClientNode在處理BulkRequest請求中,給BulkRequest的一批請求帶上了相同的隨機routing值,而我們生成環境的場景中,一批資料是寫入一個索引中,所以這一批資料就會寫入一個物理shard中。
3.1.2 帶routing場景
下面著重介紹下我們在帶routing場景下的實現方案。這個方案,我們需要在ES Server層和ES SDK都進行優化,然後將兩者綜合使用,來達到一個BulkRequest上的一批資料寫入一個物理shard的效果。優化思路ES SDK做一次資料分發,在ES Server層做一次隨機寫入來讓一批資料寫入同一個shard。
先介紹下Server層引入的概念,我們在ES shard之上,引入了邏輯shard的概念,命名為number_of_routing_size
。ES索引的真實shard我們稱之為物理shard,命名是number_of_shards
。
物理shard必須是邏輯shard的整數倍,這樣一個邏輯shard可以對映到多個物理shard。一組邏輯shard,我們命名為slot,slot總數為number_of_shards / number_of_routing_size
。
資料在寫入ClientNode的時候,ClientNode會給BulkRequest的一批請求生成一個相同的隨機值,目的是為了讓寫入的一批資料,都能寫入相同的slot中。資料流轉如圖所示:
最終計算一條資料所在shard的公式如下:
slot = hash(random(value)) % (number_of_shards/number_of_routing_size)
shard_num = hash(_routing) % number_of_routing_size + number_of_routing_size * slot
然後我們在ES SDK層進一步優化,在BulkProcessor寫入的時候增加邏輯shard引數,在add資料的時候,可以按邏輯shard進行hash,生成多個BulkRequest。這樣傳送到Server的一個BulkRequest請求,只有一個邏輯shard的資料。最終,寫入模型變為如下圖所示:
經過SDK和Server的兩層作用,一個BulkRequest中的一批請求,寫入了相同的物理shard。
這個方案對寫入是非常友好的,但是對查詢會有些影響。由於routing值是對應的是邏輯shard,一個邏輯shard要對應多個物理shard,所以使用者帶routing的查詢時,會去一個邏輯shard對應的多個物理shard中查詢。
我們針對優化的是日誌寫入的場景,日誌寫入場景的特徵是寫多讀少,而且讀寫比例差別很大,所以在實際生產環境中,查詢的影響不是很大。
3.2 單節點寫入能力提升
單節點寫入效能提升主要有以下優化:
backport社群優化,包括下面2方面:
-
merge 社群flush優化特性:[#27000] Don't refresh on
_flush
_force_merge
and_upgrade
-
merge 社群translog優化特性,包括下面2個:
-
[#45765] Do sync before closeIntoReader when rolling generation to improve index performance
-
[#47790] sync before trimUnreferencedReaders to improve index preformance
這些特性我們在生產環境驗證下來,效能大概可以帶來18%的效能提升。
我們還做了2個可選效能優化點:
-
優化translog,支援動態開啟索引不寫translog,不寫translog的話,我們可以不再觸發translog的鎖問題,也可以緩解了IO壓力。但是這可能帶來資料丟失,所以目前我們做成動態開關,可以在需要追資料的時候臨時開啟。後續我們也在考慮跟flink團隊結合,通過flink checkpoint保證資料可靠性,就可以不依賴寫入translog。從生產環境我們驗證的情況看,在寫入壓力較大的索引上開啟不寫translog,能有10-30%不等的效能提升。
-
優化lucene寫入流程,支援在索引上配置在write執行緒不同步flush segment,解決前面提到長尾原因中的lucene refresh問題。在生產環境上,我們驗證下來,能有7-10%左右的效能提升。
3.2.1 業務優化
在本次進行寫入效能優化探究過程中,我們還和業務一起發現了一個優化點,業務的日誌資料中存在2個很大的冗餘欄位(args、response),這兩個欄位在日誌原文中存在,還另外用了2個欄位儲存,這兩個欄位並沒有加索引,日誌資料寫入ES時可以不從日誌中解析出這2個欄位,在查詢的時候直接從日誌原文中解析出來。
不清洗大的冗餘欄位,我們驗證下來,能有20%左右的效能提升,該優化同時還帶來了10%左右儲存空間節約。
4. 生產環境效能提升結果
4.1 寫入模型優化
我們重點看下寫入模型優化的效果,下面的優化,都是在客戶端、服務端資源沒做任何調整的情況下的生產資料。
下圖所示索引開啟寫入模型優化後,寫入tps直接從50w/s,提升到120w/s。
生產環境索引寫入效能的提升比例跟索引混部情況、索引所在資源大小(長尾問題影響程度)等因素影響。從實際優化效果看,很多索引都能將寫入速度翻倍,如下圖所示:
4.2 寫入拒絕量(write rejected)下降
然後再來看一個關鍵指標,寫入拒絕量(write rejected)。ES datanode queue滿了之後就會出現rejected。
rejected異常帶來個危害,一個是個別節點出現rejected,說明寫入佇列滿了,大量請求在佇列中等待,而region內的其他節點卻可能很空閒,這就造成了cpu整體利用率上不去。
rejected異常另一個危害是造成失敗重試,這加重了寫入負擔,增加了寫入延遲的可能。
優化後,由於一個bulk請求不再分到每個shard上,而是寫入一個shard。一來減少了寫入請求,二來不再需要等待全部shard返回。
4.3 延遲情況緩解
最後再來看下寫入延遲問題。經過優化後,寫入能力得到大幅提升後,極大的緩解了當前的延遲情況。下面擷取了叢集優化前後的延遲情況對比。
5.總結
這次寫入效能優化,滴滴ES團隊取得了突破性進展。寫入效能提升後,我們用更少的SSD機器支撐了資料寫入,支撐了資料冷熱分離和大規格儲存物理機的落地,在這過程中,我們下線了超過400臺物理機,節省了每年千萬左右的伺服器成本。在整個優化過程中,我們深入分析ES寫入各個環節的耗時情況,去探尋每個耗時環節的優化點,對ES寫入細節有了更加深刻的認識。我們還在持續探尋更多的優化方式。而且我們的優化不僅在寫入效能上。在查詢的效能和穩定性,叢集的後設資料變更效能等等方面也都在不斷探索。我們也在持續探究如何給使用者提交高可靠、高效能、低成本、更易用的ES,未來會有更多幹貨分享給大家。
團隊介紹
滴滴雲平臺事業群滴滴搜尋平臺在開源 Elasticsearch 基礎上提供企業級的海量資料的 binlog 數倉,資料分析、日誌搜尋,全文檢索等場景的服務。 經過多年的技術沉澱,基於滴滴深度定製的Elasticsearch核心,打造了穩定易用,低成本、高效能的搜尋服務。滴滴搜尋平臺除了服務滴滴內部使用Elasticsearch的全部業務,還在進行商業化輸出,已和多家公司展開商業合作。目前團隊內部有三位Elasticsearch Contributor。
作者簡介
滴滴Elasticsearch引擎負責人,負責帶領引擎團隊深入Elasticsearch核心,解決在海量規模下Elasticsearch遇到的穩定性、效能、成本方面的問題。曾在盛大、網易工作,有豐富的引擎建設經驗。
延伸閱讀
相關文章
- Elasticsearch核心技術(二):Elasticsearch入門Elasticsearch
- 騰訊萬億級 Elasticsearch 技術解密Elasticsearch解密
- 【小白寫論文】技術性論文結構剖析
- 《Elasticsearch技術解析與實戰》Chapter 1.1:Elasticsearch入門和倒排索引ElasticsearchAPT索引
- 剖析公司技術棧
- Javascript 技術原理剖析JavaScript
- 剖析Elasticsearch的IndexSorting:一種查詢效能優化利器ElasticsearchIndex優化
- Elasticsearch核心技術(一):Elasticsearch環境搭建Elasticsearch
- 4個Excel核心技術,讓你效率翻倍!Excel
- JVM效能優化(一)JVM技術入門JVM優化
- 滴滴曹樂:如何成為技術大牛?
- 公告 | FISCO BCOS v3.3.0釋出,新增塊內分片技術,單鏈效能突破10萬TPS
- Elasticsearch 資料寫入原理分析Elasticsearch
- [直播預告]如何寫好技術文章?開源技術寫作入門與實踐
- Elasticsearch技術解析與實戰(六)Elasticsearch併發Elasticsearch
- ELK技術棧ElasticSearch,Logstash,KibanaElasticsearch
- 公司技術棧用到了ElasticsearchElasticsearch
- 剖析 Elasticsearch 的索引原理Elasticsearch索引
- Python技術棧效能測試工具Locust入門Python
- 後端技術雜談4:Elasticsearch與solr入門實踐後端ElasticsearchSolr
- 騰訊遊戲學院專家:UE高階效能剖析技術之RHI遊戲
- 千萬級流量衝擊下,如何保證極致效能
- Elasticsearch從0到千萬級資料查詢實踐(非轉載)Elasticsearch
- DartVM GC 深度剖析|得物技術DartGC
- 《Elasticsearch技術解析與實戰》Chapter 1.2 Elasticsearch安裝ElasticsearchAPT
- Elasticsearch 技術分析(九):Elasticsearch的使用和原理總結Elasticsearch
- 千萬級高併發下,看天翼雲如何為“健康碼”突破技術瓶頸
- 基於滴滴雲部署 Elasticsearch + Kibana + FluentdElasticsearch
- Elasticsearch核心技術(四):索引原理分析Elasticsearch索引
- Elasticsearch核心技術(三):Mapping設定ElasticsearchAPP
- 滴滴AR實景導航背後的技術
- PB級資料實時查詢,滴滴Elasticsearch多叢集架構實踐Elasticsearch架構
- 《Elasticsearch技術解析與實戰》Chapter 1.4 Spring Boot整合ElasticsearchElasticsearchAPTSpring Boot
- ES寫入效能優化優化
- 效能課堂-TPS 瓶頸精準定位
- 滴滴 Elasticsearch 多叢集架構實踐Elasticsearch架構
- 《Elasticsearch技術解析與實戰》Chapter 1.3 Elasticsearch增刪改查ElasticsearchAPT
- Elasticsearch Lucene 資料寫入原理 | ES 核心篇Elasticsearch