我如何吸引Elastic创始人一起对高并发写入进行优化?
導(dǎo)語:在騰訊金融科技數(shù)據(jù)應(yīng)用部的全民 BI 項(xiàng)目里,我們每天面對超過 10?億級的數(shù)據(jù)寫入,提高 ES 寫入性能迫在眉睫,在最近的一次優(yōu)化中,有幸參與到了 Elasticsearch 開源社區(qū)中。
背景
為了更便捷地分析數(shù)據(jù),騰訊金融科技數(shù)據(jù)應(yīng)用部去年推出了全民 BI 的系統(tǒng)。這個(gè)系統(tǒng)通過 Elasticsearch 進(jìn)行基礎(chǔ)的統(tǒng)計(jì),超過 10?億級的數(shù)據(jù)量需要盡可能快速地導(dǎo)入到 ES 系統(tǒng)中。即使經(jīng)過多次的參數(shù)優(yōu)化,我們依然需要幾個(gè)小時(shí)才能完成導(dǎo)入,這是系統(tǒng)此前存在的一大瓶頸。
在這樣的背景下,我們開始決定進(jìn)一步深入 ES,尋找優(yōu)化點(diǎn)。
優(yōu)化前的準(zhǔn)備
我們準(zhǔn)備了 1000?萬的數(shù)據(jù),并在原程序(Spark 程序?qū)懭?#xff09;上進(jìn)行了幾輪單機(jī)壓測,得到了一些基本的性能數(shù)據(jù)。
機(jī)器配置:CPU 24核,內(nèi)存?64GES 基本配置:堆內(nèi)存?31G
其他參數(shù)調(diào)整包括?lock memory,translog.durability?調(diào)整成?async?等(更詳細(xì)的策略可以參見
CPU:80%+
尋找理論值
在往下進(jìn)入深水區(qū)之前,我們需要先回顧一下 ES 本身,ES 本身是在 Lucene 基礎(chǔ)上設(shè)計(jì)的分布式搜索系統(tǒng),在寫入方面主要提供了:事務(wù)日志和成組提交的機(jī)制提高寫入性能并保證可靠性
提供?schema?的字段定義(映射到 Lucene?的字段類型)
要進(jìn)行優(yōu)化,首先得驗(yàn)證一個(gè)問題:Lucene 的極限速率能到達(dá)多少,所以我在我的本機(jī)上構(gòu)建了這樣的一個(gè)測試。
Macbook Pro 15,6核12線程數(shù)據(jù)量?1000?萬,每個(gè)?document 400?個(gè)字段,10?個(gè)線程并發(fā)(考慮?mac cpu Turbo 4.5G?,服務(wù)器?2.4G(24核),所以只采用?10?線程并發(fā))
驗(yàn)證寫入耗時(shí) 549s(約 10?分鐘)。
26 分鐘?—> 10?分鐘,意味著理論上是可行的。那剩下的就看如何接近這個(gè)極限。因?yàn)槟钦f明一定是 ES 本身的一些默認(rèn)特性導(dǎo)致了寫入速率無法提升。
下面的介紹忽略了一些相對簡單的參數(shù)調(diào)優(yōu),比如關(guān)閉 docvalues,這個(gè)對于非 text 字段,ES 默認(rèn)開啟,對于不需要 groupby 的場景,是不必要的,這個(gè)可以減少不少性能。
經(jīng)過初步的參數(shù)優(yōu)化寫入耗時(shí)降低到了?18?分鐘,這是后面繼續(xù)往下優(yōu)化的基礎(chǔ)。
理解 ES?寫入的機(jī)制
ES?的寫入流程(主分片節(jié)點(diǎn))主要有下面的幾步根據(jù)文檔 ID?獲取文檔版本信息,判斷進(jìn)行?add?或?update?操作
寫 Lucene:這里只寫內(nèi)存,會(huì)定期進(jìn)行成組提交到磁盤生成新分段
寫 translog:寫入文件
▲ translog?作用
除了上面的直接流程,還有三個(gè)相關(guān)的異步流程
定期進(jìn)行?flush,對 Lucene?進(jìn)行?commit
定期對?translog?進(jìn)行滾動(dòng)(生成新文件),更新?check point?文件
定期執(zhí)行 merge 操作,合并 Lucene 分段,這是一個(gè)比較消耗資源的操作,但默認(rèn)情況下都是配置了一個(gè)線程。
優(yōu)化第一步 —?參數(shù)調(diào)優(yōu)
寫 Lucene 前面已經(jīng)優(yōu)化過,那么第一步的文檔查找其實(shí)是在所有分段中進(jìn)行查找,因?yàn)橹惶峁┝艘粋€(gè)線程進(jìn)行 merge,如果 merge 不及時(shí),導(dǎo)致分段過的,必然影響文檔版本這一塊的耗時(shí)。
所以我們觀察了寫入過程中分段數(shù)的變化:▲ 寫入過程中分段的變化
觀察發(fā)現(xiàn),分段的增長速度比預(yù)期的快很多。按照默認(rèn)配置,index_buffer=10%,堆內(nèi)存 31G 的情況,按 Lucene 的寫分段機(jī)制,平均到每個(gè)線程,也有 125M,分段產(chǎn)生的速度不應(yīng)該那么快。而這個(gè)問題的根源就是 flush_threshold_size 默認(rèn)值只有 512M ,這個(gè)參數(shù)表示在當(dāng)未提交的 translog 日志達(dá)到該閾值的時(shí)候進(jìn)行一次刷盤操作。
▲ 小分段的產(chǎn)生
▲ 調(diào)整后比較緩和的分段增長
測試結(jié)果一看:18?分鐘!基本沒有效果!
理論上可行的方案,為什么卻沒有效果,帶著這個(gè)疑問繼續(xù)潛入深水區(qū)。
優(yōu)化繼續(xù) —?線程分析
這時(shí)候就需要進(jìn)行堆棧分析了,多次取樣后,發(fā)現(xiàn)了下面的一個(gè)頻繁出現(xiàn)的現(xiàn)象:▲ 被堵塞的線程發(fā)現(xiàn)很多線程都停在了獲取鎖的等待上,而 writeLock 被 rollGeneration 占用了。
寫線程需要獲取?readLock
而在高 flush_threshold_size 的配置下,rollGeneration 發(fā)生了 300+?次,每次平均耗時(shí) 560ms,浪費(fèi)了超過 168s,而這個(gè)時(shí)間里寫入線程都只能等待,小分段的優(yōu)化被這個(gè)抵消了。這里有很多的關(guān)聯(lián)關(guān)系,lush 操作和 rollGeneration 操作是互斥的,因?yàn)?flush 耗時(shí)較長(5~10?秒左右),在默認(rèn) flush_threshold_size 配置下,rollGeneration 并沒有這么頻繁在 100?次左右,提高 flush_threshold 放大了這個(gè)問題。初步優(yōu)化方案提交
因?yàn)槲覀冊趯懭脒^程中使用的 translog 持久化策略是 async,所以我很自然地想到了把寫日志和刷盤異步化。▲ 初版提交社區(qū)的方案
一開始的方案則想引入disruptor,消除寫線程之間的競爭問題,后面因?yàn)閑s的第三方組件檢查禁止使用sun.misc.Unsafe (disruptor無鎖機(jī)制基于Unsafe實(shí)現(xiàn))而放棄。基于這個(gè)方案,測試結(jié)果終于出現(xiàn)了跨越:13分鐘。
初版的方案問題比較多,但是它有兩個(gè)特點(diǎn):足夠激進(jìn):在配置為?async 策略時(shí),將底層都異步化了
凸顯了原方案的問題:讓大家看到了?translog 寫入的影響
Elastic?創(chuàng)始人加入討論
沒想到的是,在社區(qū)提交幾次優(yōu)化后,竟然吸引了大佬 Simon Willnauer 的加入。
Simon WillnauerElastic?公司創(chuàng)始人之一和技術(shù)?Leader
Lucene Core Commiter and PMC Member
Simon 的加入讓我們重新復(fù)盤了整個(gè)問題。
通過對關(guān)鍵的地方增加統(tǒng)計(jì)信息,我最終明確了關(guān)鍵的問題點(diǎn)在于 FileChannel.force 方法,這個(gè)操作是最耗時(shí)的一步。
sync 操作會(huì)調(diào)用 FileChannel.force,但沒有在 writer 的對象鎖范圍中,所以影響較小。但是因?yàn)?rollGeneration 在 writeLock 中執(zhí)行,所以阻塞的影響范圍就變大了
跟社區(qū)討論后,Simon 最后建議了一個(gè)折中的小技巧,就是在關(guān)閉原 translog 文件之前(writeLock 之外),先執(zhí)行一次刷盤操作。▲ 代碼修改
這個(gè)調(diào)整的效果可以讓每次 rollGeneration 操作的耗時(shí)從平均 570ms 降低到 280ms,在我的基準(zhǔn)測試中(配置 flush_threhold_size=30G,該參數(shù)僅用于單索引壓測設(shè)計(jì),不能在生產(chǎn)環(huán)境使用),耗時(shí)會(huì)從 18 分鐘下降到 15 分鐘。
事實(shí)上,這并不是一個(gè)非常令人滿意的解決方案,這里選擇這個(gè)方案主要出于兩點(diǎn)考慮:1.未來新的版本將考慮不使用 translog?進(jìn)行副分片的?recovery,translog?的滾動(dòng)策略會(huì)進(jìn)行調(diào)整(具體方案?elasitc未透露)2.這個(gè)修改非常的風(fēng)險(xiǎn)非常小提交社區(qū)
最后根據(jù)討論的最終結(jié)論,我們重新提交了 PR,提交了這個(gè)改動(dòng),并合并到了主干中。
總結(jié)和待續(xù)
下面是 ES 寫入中的影響關(guān)系和調(diào)用關(guān)系圖,從圖中可以看到各個(gè)因素直接的相互影響。▲ InternalEngine?中的影響關(guān)系
最近提交的優(yōu)化實(shí)時(shí)上只優(yōu)化了 rollGeneration,而實(shí)際上這里還有一些優(yōu)化空間 trimUnreferenceReader,這個(gè)也在跟社區(qū)溝通中,并需要足夠的測試數(shù)據(jù)證明調(diào)整的效果,這個(gè)調(diào)整還在測試中。
而在我們目前實(shí)際應(yīng)用場景中,我們通過調(diào)整下面兩個(gè)參數(shù)提高性能:
index.translog.flush_threshold_size 默認(rèn) 512M,可以適當(dāng)調(diào)大,但不能超過 indexBufferSize*1.5 倍/(可能并發(fā)寫的大索引數(shù)量),否則會(huì)觸發(fā)限流,并導(dǎo)致 JVM 內(nèi)存不釋放!
index.translog.generation_threshold_size(默認(rèn)?64M,系統(tǒng)支持,但官方文檔沒有的參數(shù),超過該閾值會(huì)產(chǎn)生新的?translog?文件),要小于?index.translog.flush_threshold_size,否則會(huì)影響?flush,進(jìn)而觸發(fā)限流機(jī)制
張超《Elasticsearch源碼解析與優(yōu)化實(shí)戰(zhàn)》
總結(jié)
以上是生活随笔為你收集整理的我如何吸引Elastic创始人一起对高并发写入进行优化?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 打不死我的,终将使我强大!DevOps黑
- 下一篇: 微软+开源,那些亲爱的以及热爱的