桔妹導讀:滴滴ElasticSearch平臺承接了公司內部全部使用ElasticSearch的業務,包括核心搜索、RDS從庫、日誌檢索、安全數據分析、指標數據分析等等。平臺規模達到了3000+節點,5PB 的數據存儲,超過萬億條數據。平臺寫入的峯值寫入TPS達到了2000w/s,天天近 10 億次檢索查詢。爲了承接這麼大的體量和豐富的使用場景,滴滴ElasticSearch須要解決穩定性、易用性、性能、成本等諸多問題。咱們在4年多的時間裏,作了大量優化,積攢了很是豐富的經驗。經過建設滴滴搜索平臺,打造滴滴ES引擎,全方位提高用戶使用ElasticSearch體驗。此次給你們分享的是滴滴在寫入性能優化的實踐,優化後,咱們將ES索引的寫入性能翻倍,結合數據冷熱分離場景,支持大規格存儲的物理機,給公司每一年節省千萬左右的服務器成本。node
前段時間,爲了下降用戶使用ElasticSearch的存儲成本,咱們作了數據的冷熱分離。爲了保持集羣磁盤利用率不變,咱們減小了熱節點數量。ElasticSearch集羣開始出現寫入瓶頸,節點產生大量的寫入rejected,大量從kafka同步的數據出現寫入延遲。咱們深刻分析寫入瓶頸,找到了突破點,最終將Elasticsearch的寫入性能提高一倍以上,解決了ElasticSearch瓶頸致使的寫入延遲。這篇文章介紹了咱們是如何發現寫入瓶頸,並對瓶頸進行深刻分析,最終進行了創新性優化,極大的提高了寫入性能。安全
咱們去分析這些延遲問題的時候,發現了一些不太好解釋的現象。以前作性能測試時,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也沒達到瓶頸。服務器
這裏要先對ES寫入模型進行說明,下面分析緣由會跟寫入模型有關。
網絡
客戶端通常是準備好一批數據寫入ES,這樣能極大減小寫入請求的網絡交互,使用的是ES的BULK接口,請求名爲BulkRequest。這樣一批數據寫入ES的ClientNode。ClientNode對這一批數據按數據中的routing值進行分發,組裝成一批BulkShardRequest請求,發送給每一個shard所在的DataNode。發送BulkShardRequest請求是異步的,可是BulkRequest請求須要等待所有BulkShardRequest響應後,再返回客戶端。dom
咱們在ES ClientNode上有記錄BulkRequest寫入slowlog。異步
items
是一個BulkRequest的發送請求數totalMills
是BulkRequest請求的耗時max
記錄的是耗時最長的BulkShardRequest請求avg
記錄的是全部BulkShardRequest請求的平均耗時。我這裏截取了部分示例。jvm
[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
async
從示例中能夠看到,2條記錄的avg相比max都小了不少。一個BulkRequest請求的耗時,取決於最後一個BulkShardRequest請求的返回。這就很容易聯想到分佈式系統的長尾效應。分佈式
接下來再看一個現象,咱們分析了某個節點的write線程的狀態,發現節點有時候write線程全是runnable狀態,有時候又有大量在waiting。此時寫入是有瓶頸的,runnable狀態能夠理解,但卻常常出現waiting狀態。因此這也能印證了CPU利用率不高。同時也論證長尾效應的存在,由於長尾節點繁忙,ClientNode在等待繁忙節點返回BulkShardRequest請求,其餘節點可能出現相對空閒的狀態。下面是一個節點2個時刻的線程狀態:
時刻一:
時刻二:
谷歌大神Jeffrey Dean《The Tail At Scale》介紹了長尾效應,以及致使長尾效應的緣由。總結下來,就是正常請求都很快,可是偶爾單次請求會特別慢。這樣在分佈式操做時會致使長尾效應。咱們從ES原理和實現中分析,形成ES單次請求特別慢的緣由。發現了下面幾個因素會形成長尾問題:
咱們打開lucene引擎內部的一些日誌,能夠看到:
write線程是用來處理BulkShardRequest請求的,可是從截圖的日誌能夠看到,write線程也會會進行refresh操做。這裏面的實現比較複雜,簡單說,就是ES按期會將寫入buffer的數據refresh成segment,ES爲了防止refresh不過來,會在BulkShardRequest請求的時候,判斷當前shard是否有正在refresh的任務,有的話,就會幫忙一塊兒分攤refresh壓力,這個是在write線程中進行的。這樣的問題就是形成單次BulkShardRequest請求寫入很慢。還致使長時間佔用了write線程。在write queue的緣由會具體介紹這種危害。
ES的translog相似LSM-Tree的WAL log。ES實時寫入的數據都在lucene內存buffer中,因此須要依賴寫入translog保證數據的可靠性。ES translog具體實現中,在寫translog的時候會上ReadLock。在translog過時、翻滾的時候會上WriteLock。這會出現,在WriteLock期間,實時寫入會等待ReadLock,形成了BulkShardRequest請求寫入變慢。咱們配置的tranlog寫入模式是async,正常開銷是很是小的,可是從圖中能夠看到,寫translog偶爾可能超過100ms。
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就沒法充分利用起來。
長尾問題主要來自於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利用率預期。
從上面的寫入瓶頸分析,咱們發現了ES沒法將資源用滿的緣由來自於分佈式的長尾問題。因而咱們着重思考如何消除分佈式的長尾問題。而後也在探尋其餘的優化點。總體性能優化,咱們分紅了三個方向:
此次的性能優化,咱們在這三個方向上都取得了一些突破。
寫入模型的優化思路是將一個BulkRequest請求,轉發到儘可能少的shard,甚至只轉發到一個shard,來減小甚至消除分佈式長尾效應。咱們完成的寫入模型優化,最終能作到一個BulkRequest請求只轉發到一個shard,這樣就消除了分佈式長尾效應。
寫入模型的優化分紅兩個場景。一個是數據不帶routing的場景,這種場景用戶不依賴數據分佈,比較容易優化的,能夠作到只轉發到一個shard。另外一個是數據帶了routing的場景,用戶對數據分佈有依賴,針對這種場景,咱們也實現了一種優化方案。
因爲用戶對routing分佈沒有依賴,ClientNode在處理BulkRequest請求中,給BulkRequest的一批請求帶上了相同的隨機routing值,而咱們生成環境的場景中,一批數據是寫入一個索引中,因此這一批數據就會寫入一個物理shard中。
下面着重介紹下咱們在帶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中查詢。
咱們針對優化的是日誌寫入的場景,日誌寫入場景的特徵是寫多讀少,並且讀寫比例差異很大,因此在實際生產環境中,查詢的影響不是很大。
單節點寫入性能提高主要有如下優化:
backport社區優化,包括下面2方面:
_flush
_force_merge
and _upgrade
這些特性咱們在生產環境驗證下來,性能大概能夠帶來18%的性能提高。
咱們還作了2個可選性能優化點:
在本次進行寫入性能優化探究過程當中,咱們還和業務一塊兒發現了一個優化點,業務的日誌數據中存在2個很大的冗餘字段(args、response),這兩個字段在日誌原文中存在,還另外用了2個字段存儲,這兩個字段並無加索引,日誌數據寫入ES時能夠不從日誌中解析出這2個字段,在查詢的時候直接從日誌原文中解析出來。
不清洗大的冗餘字段,咱們驗證下來,能有20%左右的性能提高,該優化同時還帶來了10%左右存儲空間節約。
咱們重點看下寫入模型優化的效果,下面的優化,都是在客戶端、服務端資源沒作任何調整的狀況下的生產數據。
下圖所示索引開啓寫入模型優化後,寫入tps直接從50w/s,提高到120w/s。
生產環境索引寫入性能的提高比例跟索引混部狀況、索引所在資源大小(長尾問題影響程度)等因素影響。從實際優化效果看,不少索引都能將寫入速度翻倍,以下圖所示:
而後再來看一個關鍵指標,寫入拒絕量(write rejected)。ES datanode queue滿了以後就會出現rejected。
rejected異常帶來個危害,一個是個別節點出現rejected,說明寫入隊列滿了,大量請求在隊列中等待,而region內的其餘節點卻可能很空閒,這就形成了cpu總體利用率上不去。
rejected異常另外一個危害是形成失敗重試,這加劇了寫入負擔,增長了寫入延遲的可能。
優化後,因爲一個bulk請求再也不分到每一個shard上,而是寫入一個shard。一來減小了寫入請求,二來再也不須要等待所有shard返回。
最後再來看下寫入延遲問題。通過優化後,寫入能力獲得大幅提高後,極大的緩解了當前的延遲狀況。下面截取了集羣優化先後的延遲狀況對比。
此次寫入性能優化,滴滴ES團隊取得了突破性進展。寫入性能提高後,咱們用更少的SSD機器支撐了數據寫入,支撐了數據冷熱分離和大規格存儲物理機的落地,在這過程當中,咱們下線了超過400臺物理機,節省了每一年千萬左右的服務器成本。在整個優化過程當中,咱們深刻分析ES寫入各個環節的耗時狀況,去探尋每一個耗時環節的優化點,對ES寫入細節有了更加深入的認識。咱們還在持續探尋更多的優化方式。並且咱們的優化不只在寫入性能上。在查詢的性能和穩定性,集羣的元數據變動性能等等方面也都在不斷探索。咱們也在持續探究如何給用戶提交高可靠、高性能、低成本、更易用的ES,將來會有更多幹貨分享給你們。
滴滴雲平臺事業羣滴滴搜索平臺在開源 Elasticsearch 基礎上提供企業級的海量數據的 binlog 數倉,數據分析、日誌搜索,全文檢索等場景的服務。 通過多年的技術沉澱,基於滴滴深度定製的Elasticsearch內核,打造了穩定易用,低成本、高性能的搜索服務。滴滴搜索平臺除了服務滴滴內部使用Elasticsearch的所有業務,還在進行商業化輸出,已和多家公司展開商業合做。目前團隊內部有三位Elasticsearch Contributor。
滴滴Elasticsearch引擎負責人,負責帶領引擎團隊深刻Elasticsearch內核,解決在海量規模下Elasticsearch遇到的穩定性、性能、成本方面的問題。曾在盛大、網易工做,有豐富的引擎建設經驗。
內容編輯 | Charlotte
聯繫咱們 | DiDiTech@didiglobal.com
滴滴技術 出品