站長資訊網
        最全最豐富的資訊網站

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          Oppo文檔數據庫研發負責人:楊亞洲

          1.背景

          線上某集群峰值TPS超過100萬/秒左右(主要為寫流量,讀流量較少,讀寫流量做了主從讀寫分離,讀流量走從節點,qps數百上千),峰值tps幾乎已經到達集群上限,同時平均時延也超過100ms,隨著讀寫流量的進一步增加,時延抖動嚴重影響業務可用性。該集群采用mongodb天然的分片模式架構,數據均衡的分布于各個分片中,添加片鍵啟用分片功能后實現完美的負載均衡。集群每個節點流量監控如下圖所示:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐
        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          從上圖可以看出集群流量比較大,峰值已經突破120萬/秒,其中delete過期刪除的流量不算在總流量里面(delete由主觸發刪除,但是主上面不會顯示,只會在從節點拉取oplog的時候顯示)。如果算上主節點的delete流量,峰值總tps超過150萬/秒。

          2.軟件優化

          在不增加服務器資源的情況下,首先做了如下軟件層面的優化,并取得了理想的數倍性能提升:

          業務層面優化

          Mongodb配置優化

          存儲引擎優化

          2.1 業務層面優化

          該集群總文檔近百億條,每條文檔記錄默認保存三天,業務隨機散列數據到三天后任意時間點隨機過期淘汰。由于文檔數目很多,白天平峰監控可以發現從節點經常有大量delete操作,甚至部分時間點delete刪除操作數已經超過了業務方讀寫流量,因此考慮把delete過期操作放入夜間進行,過期索引添加方法如下:

          Db.collection.createIndex( { “expireAt”: 1 }, { expireAfterSeconds: 0 } )

          上面的過期索引中expireAfterSeconds=0,代表collection集合中的文檔的過期時間點在expireAt時間點過期,例如: db.collection.insert( {

          //表示該文檔在夜間凌晨1點這個時間點將會被過期刪除

          ”expireAt”: new Date(‘July 22, 2019 01:00:00’),

          ”logEvent”: 2,

          ”logMessage”: “Success!”

          } )

          通過隨機散列expireAt在三天后的凌晨任意時間點,即可規避白天高峰期觸發過期索引引入的集群大量delete,從而降低了高峰期集群負載,最終減少業務平均時延及抖動。

          Delete過期Tips1: expireAfterSeconds含義

          1. 在expireAt指定的絕對時間點過期,也就是12.22日凌晨2:01過期

          Db.collection.createIndex( { “expireAt”: 1 }, { expireAfterSeconds: 0 } )

          db.log_events.insert( { “expireAt”: new Date(Dec 22, 2019 02:01:00′),”logEvent”: 2,”logMessage”: “Success!”})

          expireAt指定的時間往后推遲expireAfterSeconds秒過期,也就是當前時間往后推遲60秒過期

          db.log_events.insert( {“createdAt”: new Date(),”logEvent”: 2,”logMessage”: “Success!”} )

          Db.collection.createIndex( { “expireAt”: 1 }, { expireAfterSeconds: 60 } )

          Delete過期Tips2: 為何mongostat只能監控到從節點有delete操作,主節點沒有?

          原因是過期索引只在master主節點觸發,觸發后主節點會直接刪除調用對應wiredtiger存儲引擎接口做刪除操作,不會走正常的客戶端鏈接處理流程,因此主節點上看不到delete統計。

          主節點過期delete后會生存對于的delete oplog信息,從節點通過拉取主節點oplog然后模擬對于client回放,這樣就保證了主數據刪除的同時從數據也得以刪除,保證數據最終一致性。從節點模擬client回放過程將會走正常的client鏈接過程,因此會記錄delete count統計,詳見如下代碼:

          官方參考如下: https://docs.mongodb.com/manual/tutorial/expire-data/

          2.2 Mongodb配置優化(網絡IO復用,網絡IO和磁盤IO做分離)

          由于集群tps高,同時整點有大量推送,因此整點并發會更高,mongodb默認的一個請求一個線程這種模式將會嚴重影響系統負載,該默認配置不適合高并發的讀寫應用場景。官方介紹如下:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          2.2.1 Mongodb內部網絡線程模型實現原理

          mongodb默認網絡模型架構是一個客戶端鏈接,mongodb會創建一個線程處理該鏈接fd的所有讀寫請求及磁盤IO操作。

          Mongodb默認網絡線程模型不適合高并發讀寫原因如下:

          1. 在高并發的情況下,瞬間就會創建大量的線程,例如線上的這個集群,連接數會瞬間增加到1萬左右,也就是操作系統需要瞬間創建1萬個線程,這樣系統load負載就會很高。

          2. 此外,當鏈接請求處理完,進入流量低峰期的時候,客戶端連接池回收鏈接,這時候mongodb服務端就需要銷毀線程,這樣進一步加劇了系統負載,同時進一步增加了數據庫的抖動,特別是在PHP這種短鏈接業務中更加明顯,頻繁的創建線程銷毀線程造成系統高負債。

          3. 一個鏈接一個線程,該線程除了負責網絡收發外,還負責寫數據到存儲引擎,整個網絡I/O處理和磁盤I/O處理都由同一個線程負責,本身架構設計就是一個缺陷。

          2.2.2 網絡線程模型優化方法

          為了適應高并發的讀寫場景,mongodb-3.6開始引入serviceExecutor: adaptive配置,該配置根據請求數動態調整網絡線程數,并盡量做到網絡IO復用來降低線程創建消耗引起的系統高負載問題。此外,加上serviceExecutor: adaptive配置后,借助boost:asio網絡模塊實現網絡IO復用,同時實現網絡IO和磁盤IO分離。這樣高并發情況下,通過網絡鏈接IO復用和mongodb的鎖操作來控制磁盤IO訪問線程數,最終降低了大量線程創建和消耗帶來的高系統負載,最終通過該方式提升高并發讀寫性能。

          2.2.3 網絡線程模型優化前后性能對比

          在該大流量集群中增加serviceExecutor: adaptive配置實現網絡IO復用及網絡IO與磁盤IO做分離后,該大流量集群時延大幅度降低,同時系統負載和慢日志也減少很多,具體如下:

          2.2.3.1 優化前后系統負載對比

          驗證方式:

          該集群有多個分片,其中一個分片配置優化后的主節點和同一時刻未優化配置的主節點load負載比較: 未優化配置的load

          

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          優化配置的load

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          2.2.3.2 優化前后慢日志對比

          驗證方式:

          該集群有多個分片,其中一個分片配置優化后的主節點和同一時刻未優化配置的主節點慢日志數比較:

          同一時間的慢日志數統計:

          未優化配置的慢日志數(19621)

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

           優化配置后的慢日志數(5222):

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          2.2.3.3 優化前后平均時延對比

          驗證方式:

          該集群所有節點加上網絡IO復用配置后與默認配置的平均時延對比如下:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          從上圖可以看出,網絡IO復用后時延降低了1-2倍。

          2.3 wiredtiger存儲引擎優化

          從上一節可以看出平均時延從200ms降低到了平均80ms左右,很顯然平均時延還是很高,如何進一步提升性能降低時延?繼續分析集群,我們發現磁盤IO一會兒為0,一會兒持續性100%,并且有跌0現象,現象如下:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          從圖中可以看出,I/O寫入一次性到2G,后面幾秒鐘內I/O會持續性阻塞,讀寫I/O完全跌0,avgqu-sz、awit巨大,util次序性100%,在這個I/O跌0的過程中,業務方反應的TPS同時跌0。

          此外,在大量寫入IO后很長一段時間util又持續為0%,現象如下:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          總體IO負載曲線如下:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          從圖中可以看出IO很長一段時間持續為0%,然后又飆漲到100%持續很長時間,當IO util達到100%后,分析日志發現又大量滿日志,同時mongostat監控流量發現如下現象:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐
        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          從上可以看出我們定時通過mongostat獲取某個節點的狀態的時候,經常超時,超時的時候剛好是io util=100%的時候,這時候IO跟不上客戶端寫入速度造成阻塞。

          有了以上現象,我們可以確定問題是由于IO跟不上客戶端寫入速度引起,第2章我們已經做了mongodb服務層的優化,現在我們開始著手wiredtiger存儲引擎層面的優化,主要通過以下幾個方面:

          1、cachesize調整

          2、臟數據淘汰比例調整

          3、checkpoint優化

          2.3.1 cachesize調整優化(為何cacheSize越大性能越差)

          前面的IO分析可以看出,超時時間點和I/O阻塞跌0的時間點一致,因此如何解決I/O跌0成為了解決改問題的關鍵所在。

          找個集群平峰期(總tps50萬/s)查看當時該節點的TPS,發現TPS不是很高,單個分片也就3-4萬左右,為何會有大量的刷盤,瞬間能夠達到10G/S,造成IO util持續性跌0(因為IO跟不上寫入速度)。繼續分析wiredtiger存儲引擎刷盤實現原理,wiredtiger存儲引擎是一種B+樹存儲引擎,mongodb文檔首先轉換為KV寫入wiredtiger,在寫入過程中,內存會越來越大,當內存中臟數據和內存總占用率達到一定比例,就開始刷盤。同時當達到checkpoint限制也會觸發刷盤操作,查看任意一個mongod節點進程狀態,發現消耗的內存過多,達到110G,如下圖所示:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          于是查看mongod.conf配置文件,發現配置文件中配置的cacheSizeGB: 110G,可以看出,存儲引擎中KV總量幾乎已經達到110G,按照5%臟頁開始刷盤的比例,峰值情況下cachesSize設置得越大,里面得臟數據就會越多,而磁盤IO能力跟不上臟數據得產生速度,這種情況很可能就是造成磁盤I/O瓶頸寫滿,并引起I/O跌0的原因。

          此外,查看該機器的內存,可以看到內存總大小為190G,其中已經使用110G左右,幾乎是mongod的存儲引起占用,這樣會造成內核態的page cache減少,大量寫入的時候內核cache不足就會引起磁盤缺頁中斷。

          

          #FormatImgID_14#

          解決辦法:通過上面的分析問題可能是大量寫入的場景,臟數據太多容易造成一次性大量I/O寫入,于是我們可以考慮把存儲引擎cacheSize調小到50G,來減少同一時刻I/O寫入的量,從而規避峰值情況下一次性大量寫入的磁盤I/O打滿阻塞問題。

          2.3.2 存儲引擎dirty臟數據淘汰優化

          調整cachesize大小解決了5s請求超時問題,對應告警也消失了,但是問題還是存在,5S超時消失了,1s超時問題還是偶爾會出現。

          因此如何在調整cacheSize的情況下進一步規避I/O大量寫的問題成為了問題解決的關鍵,進一步分析存儲引擎原理,如何解決內存和I/O的平衡關系成為了問題解決的關鍵,mongodb默認存儲因為wiredtiger的cache淘汰策略相關的幾個配置如下:

          wiredtiger淘汰相關配置默認值工作原理

          eviction_target80當用掉的內存超過總內存的百分比達到 eviction_target,后臺evict線程開始淘汰

          eviction_trigger95當用掉的內存超過總內存的 eviction_trigger,用戶線程也開始淘汰

          eviction_dirty_target5當cache中臟數據比例超過 eviction_dirty_target,后臺evict線程開始淘汰

          eviction_dirty_trigger20當cache中臟數據比例超過 eviction_dirty_trigger, 用戶線程也開始淘汰

          evict.threads_min4后臺evict線程最小數

          evict.threads_max4后臺evict線程最大數

          調整cacheSize從120G到50G后,如果臟數據比例達到5%,則極端情況下如果淘汰速度跟不上客戶端寫入速度,這樣還是容易引起I/O瓶頸,最終造成阻塞。

          解決辦法: 如何進一步減少持續性I/O寫入,也就是如何平衡cache內存和磁盤I/O的關系成為問題關鍵所在。從上表中可以看出,如果臟數據及總內占用存達到一定比例,后臺線程開始選擇page進行淘汰寫盤,如果臟數據及內存占用比例進一步增加,那么用戶線程就會開始做page淘汰,這是個非常危險的阻塞過程,造成用戶請求驗證阻塞。平衡cache和I/O的方法: 調整淘汰策略,讓后臺線程盡早淘汰數據,避免大量刷盤,同時降低用戶線程閥值,避免用戶線程進行page淘汰引起阻塞。優化調整存儲引起配置如下:

           eviction_target: 75%

          eviction_trigger:97%

          eviction_dirty_target: %3

          eviction_dirty_trigger:25%

          evict.threads_min:8

          evict.threads_max:12

          總體思想是讓后臺evict盡量早點淘汰臟頁page到磁盤,同時調整evict淘汰線程數來加快臟數據淘汰,調整后mongostat及客戶端超時現象進一步緩解。

          2.3.3 存儲引擎checkpoint優化調整

          存儲引擎得checkpoint檢測點,實際上就是做快照,把當前存儲引擎的臟數據全部記錄到磁盤。觸發checkpoint的條件默認又兩個,觸發條件如下:

          1、固定周期做一次checkpoint快照,默認60

          2、增量的redo log(也就是journal日志)達到2G

          當journal日志達到2G或者redo log沒有達到2G并且距離上一次時間間隔達到60s,wiredtiger將會觸發checkpoint,如果在兩次checkpoint的時間間隔類evict淘汰線程淘汰的dirty page越少,那么積壓的臟數據就會越多,也就是checkpoint的時候臟數據就會越多,造成checkpoint的時候大量的IO寫盤操作。如果我們把checkpoint的周期縮短,那么兩個checkpoint期間的臟數據相應的也就會減少,磁盤IO 100%持續的時間也就會縮短。

          checkpoint調整后的值如下:

          checkpoint=(wait=25,log_size=1GB)

          2.3.4 存儲引擎優化前后IO對比

          通過上面三個方面的存儲引擎優化后,磁盤IO開始散列到各個不同的時間點,iostat監控優化后的IO負載如下:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          從上面的io負載圖可以看出,之前的IO一會兒為0%,一會兒100%現象有所緩解,總結如下圖所示(注: 優化后的IO曲線只是反應大概趨勢,和真實趨勢有些出入):

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          2.3.5 存儲引擎優化前后時延對比

          優化前后時延對比如下(注: 該集群有幾個業務同時使用,優化前后時延對比如下):

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐
        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐
        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐
        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐
        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          從上圖可以看出,存儲引擎優化后時間延遲進一步降低并趨于平穩,從平均80ms到平均20ms左右,但是還是不完美,有抖動。

          3 服務器系統磁盤IO問題解決

          3.1 服務器IO硬件問題背景

          如第前面章節所述,當wiredtiger大量淘汰數據時,發現只要每秒磁盤寫入量超過500M/s,接下來的幾秒鐘內util就會持續100%,w/s幾乎跌0,于是開始懷疑磁盤硬件存在缺陷。如下圖所示:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐
        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          從上圖可以看出磁盤為nvMe的ssd盤,查看相關數據可以看出該盤IO性能很好,支持每秒2G寫入,iops能達到15萬,而我們線上的盤只能每秒寫入最多500M。

          3.2 服務器IO硬件問題解決后性能對比

          通過大量的線下測試以及服務器廠商的配合,nvme的ssd io瓶頸問題得以解決,經過和廠商確認分析,最終定位到IO問題是linux內核版本不匹配引起,如果大家nvme ssd盤有同樣問題,記得升級linux版本到3.10.0-957.27.2.el7.x86_64版本,升級后nvme ssd的IO能力達到近2G/s寫入。

          說明: 從本節開始,我們的服務器分為兩種:1. 低IO服務器,也就是沒有做操作系統升級的服務器,IO寫入能力500M/s,2. 高IO服務器,也就是操作系統升級后的服務器,IO寫入能力近2G/s.。

          于是考慮把該分片集群的主節點全部遷移到操作系統升級后的高IO服務器(為了謹慎,我們只替換了分片主節點,從節點還是老的未升級的低IO服務器),該服務器io寫入能達到近2G/s(注意:只遷移了主節點,從節點還是在之前的IO-500M/s的低IO服務器),具體的操作過程在后面章節會描述,詳見3.3章節。遷移完成后,發現性能得到了進一步提升,時延遲降低到平均2-4ms/s,幾個不同業務接口看到的時延監控如下圖所示:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐
        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐
        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          從上圖時延可以看出,遷移主節點到操作系統升級后的高IO機器后,時延進一步降低到平均2-4ms。

          雖然時延降低到了平均2-4ms,但是還是有很多幾十ms的尖刺,因此我們需要進一步的優化分析。

          3.3 硬件問題回顧及遺留問題

          在前面的分析中,我們在定位nvme ssd硬件IO問題的過程中,和廠商一起分析發現IO問題是由于操作系統版本不對引起,于是開始對線上的主從mongod實例的服務器硬件進行升級,升級后開始替換線上該集群的實例。具體操作過程如下:

          1、為了驗證IO升級后的機器,我們替換一個分片的從節點為升級操作系統后的高IO服務器(IO問題得以解決,IO能力從之前的500M/s寫入達到了近2G/s,我們稱IO升級后的服務器為高IO服務器,未升級的服務器為低IO服務器),替換后通過iostat可以看到該從節點的IO 100%問題得到了很大程度的緩解,不會出現持續性IO跌0問題。

          2、在第1步的從節點在高IO服務器跑了一周后,我們確定升級后的高IO服務器運行穩定,為了謹慎起見,我們雖然確定該高IO服務器在從節點運行沒有問題,但是我們需要進一步在主節點驗證是否穩定,于是我們做了一次主從切換,該高IO服務器變為主節點運行,也就是集群中某個分片的主節點服務器變為了高IO服務器,但是從節點還是低IO服務器。

          3、當第2步的主節點在高IO服務器跑了數周后,我們確定主節點在高IO服務器運行正常,于是我們得下結論: 升級后的服務器運行穩定。

          4、確定高IO服務器沒問題后,我們開始批量替換mongod實例到該服務器。為了保險起見,畢竟只驗證了一臺高IO服務器在主從運行都沒問題,于是我們考慮只把整個集群的主節點替換為高IO服務器(當時我認為客戶端都是用的默認配置,數據寫到主節點就會返回OK,雖然從節點IO慢,但是還是可以追上oplog速度的,這樣客戶端時延不會因為以前主節點IO有問題而抖動)。

          為了謹慎保險起見,通過上面的硬件替換升級過程,我們只替換了所有分片的主節點,替換升級前后架構發生了變化,原有集群硬件架構如下圖所示:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          所有分片主節點硬件升級后的架構圖如下圖所示:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          從上圖可知,新的集群架構,主從節點服務器IO能力有比較大的差距。最開始我認為業務方默認沒有設置WriteConncern,也就是默認寫入到Primary就向客戶端發送確認,因此不會影響業務寫入。

          所有分片主節點升級為高IO服務器后,多個業務接口的時間訪問延遲降到了平均2-4ms左右,但是在超大流量沖擊的時候,還是有幾十ms的尖刺,我選取一個抖動比較典型的接口時延為例,如下圖所示:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          從上圖可以看出,特別是在大流量沖擊的時間點,尖刺越明顯。

          主節點硬件升級后續優化

          4.1 readConcern配置優化

          在上一節,我們替換了分片的所有主節點為高IO服務器,從節點還是以前未升級的低IO服務器。由于業務方默認沒有設置WriteConncern,因此認為客戶端寫到主成功就會返回客戶端OK,即使從服務器性能差也不會影響客戶端寫主。

          在升級主服務器后,我繼續優化存儲引擎把eviction_dirty_trigger:25%調整到了30%。

          由于受到超大流量的高并發沖擊,會從平峰期的幾十萬TPS瞬間飆升到百萬級別,而且該毛刺幾乎每天都會出現好幾次,比較容易復現。于是提前部署好mongostat監控所有實例,同時在每個服務器上用Iostat監控實時的IO狀況,同時編寫腳本實時采集db.serverstatus()、db.printSlaveReplicationInfo()、db.printReplicationInfo()等集群重要信息。

          當某個時間點監控出現毛刺后,于是開始分析mongostat,我們發現一個問題,即使在平峰期,臟數據比例也會持續增長到閥值(30%),我們知道當臟數據比例超過eviction_dirty_trigger:30%閥值,用戶線程就會進行evict淘汰,這樣用戶線程就會阻塞直到騰出內存空間,因此淘汰刷盤過程業務訪問很慢。分析平峰期毛刺時間點對應的mongostat監控,發現如下情況:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          從上圖可以看出,集群TPS才40-50萬左右的時候某個分片的主節點出現了臟數據達到eviction_dirty_trigger:30%閥值,于是整個集群訪問時延就會瞬間增加,原因是一個分片的用戶線程需要刷盤,導致這個分片的訪問時延上升(實際上其他分片的訪問時延還是正常的),最終把整體平均時延拉上去了。

            為什么普通平峰期也會有抖動?這很明顯不科學。

          于是獲取出問題的主節點的一些監控信息,得出以下結論:

          1、IO正常,IO不是瓶頸。

          2、分析抖動的時候的系統top負載,負載正常。

          3、該分片的TPS才4萬左右,顯然沒到到分片峰值。

          4、db.printSlaveReplicationInfo()看到主從延遲較高。

          當客戶端時延監控發現時間延遲尖刺后,我們發現主節點所有現象一切正常,系統負載、IO、TPS等都沒有到達瓶頸,但是有一個唯一的異常,就是主從同步延遲持續性增加,如下圖所示:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          同時對應低IO服務器的從節點上面的io狀況如下圖:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          從節點的IO性能一塌烏涂,這也正是主從延遲增加的根源。

          從上圖可以看出在時延尖刺的同樣時間點,主從延遲超大。于是懷疑時延尖刺可能和從節點拉取Oplog速度有關系,于是把整個mongostat、iostat、top、db.printSlaveReplicationInfo()、db.serverstatus()等監控持續跑了兩天,記錄下了兩天內的一些核心系統和mongo監控指標。

          兩天后,對著客戶端時延尖刺時間點分析對應監控數據,發現一個共同的現象,尖刺出現時間點和臟數據eviction_dirty_trigger超過閥值時間點一致,同時主從延遲在這個時間點都有很大的延遲。

          到這里,我越來越懷疑問題和從節點拉取oplog速度有關。之前認為業務方默認沒有設置WriteConncern,也就是默認寫入到Primary就向客戶端發送確認,可能應答客戶端前還有其他流程會影響服務的返回OK給客戶端。于是查看官方mongodb-3.6的Production Notes,從中發現了如下信息:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          從Production Notes可以看出,mongodb-3.6默認啟用了 read concern “majority”功能,于是懷疑抖動可能和該功能有關。為了避免臟讀,mongodb增加了該功能,啟用該功能后,mongodb為了確保帶有帶有參數readConcern(“Majority”)的客戶端讀取到的數據確實是同步到大多數實例的數據,因此mongodb必須在內存中借助snapshot 及主從通信來維護更多的版本信息,這就增加了wiredtiger存儲引擎對內存的需求。由于從節點是低IO服務器,很容易造成阻塞,這樣拉取oplog的速度就會跟不上進度,造成主節點消耗大量的內存來維護快照信息,這樣就會導致大量的內存消耗,最終導致臟數據瞬間劇增,很快達到eviction_dirty_trigger閥值,業務也因此抖動。

          說一個小插曲,因為mongodb-3.6默認開啟enableMajorityReadConcern功能,我們在這個過程中出現過幾次嚴重的集群故障,業務流量有段時間突然暴漲,造成時延持續性達到幾千ms,寫入全部阻塞,現象如下:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          該問題的根源也是因為enableMajorityReadConcern功能引起,由于從節點嚴重落后主節點,導致主節點為了維護各種snapshot快照,消耗大量內存,同時從節點和主節點的oplog延后,導致主節點維護了更多的內存版本,臟數據比例持續性增長,直到從節點追上oplog。由于我們的業務不需要readConcert功能,因此我們考慮禁用該功能(配置文件增加配置replication.enableMajorityReadConcern=false)。

          鑒于篇幅,enableMajorityReadConcern及主從硬件IO能力不足引起的嚴重業務故障,本篇不做詳細的分析,后期會寫一篇專門的《百萬級高并發mongodb集群性能優化采坑記》中做分享,除了ReadConcern采坑,還有其他好幾個核心采坑點,敬請關注。

          此外,后續會專門寫一篇ReadConcern的原理及代碼實現分析文章,敬請關注。

          4.2 替換從節點服務器為升級后的高IO服務器

          除了通過replication.enableMajorityReadConcern=false在配置文件中禁用ReadConcern Majority功能,我們繼續把所有分片的從節點由之前的低IO服務器替換為升級后的高IO服務器,升級后所有主從硬件資源性能完全一樣,升級后集群分片架構如下圖所示:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          通過禁用enableMajorityReadConcern功能,并統一主從服務器硬件資源后,查看有抖動的一個接口的時間延遲,如下圖所示:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          從上圖可以看出,通過MajorityReadConcern功能并且替把所有從節點的低IO服務器做系統升級后,業務時間延遲抖動的峰值進一步降低了,從平均2-4ms降低到了1ms左右,同時,峰值毛刺從前面第3節中的80ms降低到了現在的峰值40ms左右。

          此外,4.1節提到的臟數據持續性突破eviction_dirty_trigger閥值引起客戶端時延飆漲到幾千ms的問題得以徹底解決。

          總結: MajorityReadConcern功能禁用并升級從節點到高IO服務器后,總體收益如下:

          1、平均時延從2-4ms降低到1ms左右

          2、峰值時延毛刺從80ms降低到40ms

          3、之前出現的臟數據比例突破30%飆漲到50%的問題徹底解決。

          4、尖刺持續時間變短

          4.3 繼續優化調整存儲引起參數

          通過前面的條優化,我們發現有一個業務接口還是偶爾有40ms時延尖刺,分析發現主要是eviction_dirty_trigger達到了我們配置的閥值,業務線程開始淘汰page cache,這樣就造成業務線程很慢,最終導致平均時延尖刺。

          為了進一步減緩時延尖刺,我們繼續在之前基礎上對存儲引擎調優,調整后配置如下:

          eviction_target: 75%

          eviction_trigger:97%

          eviction_dirty_target: %3

          eviction_dirty_trigger:30%

          evict.threads_min:14

          evict.threads_max:18

          checkpoint=(wait=20,log_size=1GB)

          經過此輪的存儲引擎調優后,該業務的核心接口時延進一步好轉,時延尖刺相比之前有了進一步的改善,時延最大尖刺時間從前面的45ms降低到了35ms,同時尖刺出現的頻率明顯降低了,如下圖所示:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          從此圖可以看出,在個別時間點還是有一次時延尖刺,對照該尖刺的時間點分析提前部署好的mongostat和iostat監控,得到如下信息:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          從上圖可以看出,在峰值TPS百萬級別的時候,部分節點evict淘汰速率已經跟不上寫入速度,因此出現了用戶線程刷盤的情況。

          在這個時間點分析對應機器的系統負載、磁盤io狀況、內存狀況等,發現系統負載、比較正常,但是對應服務器磁盤IO偏高,如下圖所示:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          同時分析對應服務器對應時間點的慢日志,我們發現尖刺出現時間的的慢日志統計如下:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          分析非時延尖刺時間點,對應慢日志統計如下:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          分析兩個時間點慢日志可以看出,慢日志出現的條數和時間延遲尖刺出現的時間點一致,也就是磁盤IO負載很高的時候。

          結論:通過上面的分析可以看出,當磁盤IO比較高(util超過50%)的時候,慢日志和時延都會增加,他們之間成正比關系,IO依然時性能瓶頸點。

          疑問:量高的時候通過調整存儲引擎evict和checkpoint配置,IO寫入分散到了不同時間點,相比以起集中再一個時間點寫入有了很大改善。但是,還是會出現部分時間點IO寫入接近為0,其他時間點IO 100%的現象。

          5.總結

          通過軟件層面(mongodb服務配置、業務優化、存儲引擎優化)及硬件系統優化(升級操作系統)后,該大流量集群的核心接口時延從最初的平均成百上千ms降低到了現在的平均1-2ms,性能提升比較可觀,整體時延性能提升數十倍。

          優化前主要業務接口時延:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐
        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          在不增加物理服務器資源的基礎上,經過一系列的優化措施,最終業務方主要接口時延控制到了幾ms,如下圖所示:

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐
        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          6.遺留問題

          如4.3分析所述,當峰值tps持續性達到100萬左右的時候,有明顯的磁盤IO問題,但是IO寫入在不同時間點很不均衡,有時候在流量持續性高峰期存在如下現象(注: 下圖只是大概反應高峰期持續性寫入的IO趨勢狀況):

        Oppo百萬級高并發mongodb集群性能數十倍提升優化實踐

          如上圖所示,有時候高峰期不同時間點磁盤IO不均衡,如果我們能把IO平均散列到各個不同時間點,這樣或許可以解決IO瓶頸問題。我試著通過繼續調大evict線程數來達到目的,但是當線程數超過一定值后效果不明顯。后續會持續分析wiredtiger存儲引擎代碼實現來了解整個機制,分析有時候磁盤IO嚴重分布不均衡代碼實現原理。

          7. 后續相關分享

            近期繼續分享如下主題,敬請關注:

          《百萬級高并發mongodb集群性能優化采坑記》

          《線上典型集群抖動、不可用等問題匯總分析》

          《Mongodb文檔數據庫業務使用最佳案例分享》

          《Mongodb-3.6網絡IO線程模型設計及代碼實現》

          注意

            文章中的一些優化方法并不是一定適用于所有mongodb場景,請根據實際業務場景和硬件物理資源進行優化,而不要按部就班。

          作者簡介

          楊亞洲,前滴滴出行技術專家,現任Oppo文檔數據庫研發負責人,一直專注于分布式緩存、高性能服務器、分布式存儲、數據庫、中間件等相關研發,后續會持續分析Mongodb內核、wiredtiger存儲引擎、rocksdb存儲引擎源碼及,相關分享會發布到我的git賬號。Github賬號地址: https://github.com/y123456yz 郵箱:yangyazhou#oppo.com

          Oppo公司互聯網服務簡介

          可能外界對Oppo的認識僅僅停留在手機業務,這是一種誤解。Oppo的互聯網業務流量絕不輸于部分一線互聯網公司,歡迎加入Oppo互聯網運維云存儲團隊參與公司百萬級高并發文檔數據庫研發。

        特別提醒:本網內容轉載自其他媒體,目的在于傳遞更多信息,并不代表本網贊同其觀點。其原創性以及文中陳述文字和內容未經本站證實,對本文以及其中全部或者部分內容、文字的真實性、完整性、及時性本站不作任何保證或承諾,并請自行核實相關內容。本站不承擔此類作品侵權行為的直接責任及連帶責任。如若本網有任何內容侵犯您的權益,請及時聯系我們,本站將會在24小時內處理完畢。

        贊(0)
        分享到: 更多 (0)
        網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
        主站蜘蛛池模板: 久久无码人妻精品一区二区三区| 日韩精品人成在线播放| 欧美日韩国产精品 | 巨大黑人极品VIDEOS精品 | 亚洲动漫精品无码av天堂| 国产视频精品免费视频| 欧美精品一本久久男人的天堂| 无码人妻精品一区二区三区66 | 国产日韩精品欧美一区| 亚洲精品tv久久久久| 国产亚洲精品免费视频播放 | 欧美精品播放| 国产欧美精品一区二区色综合| 国产一区二区精品久久| 精品国产一区AV天美传媒| 亚洲国产精品无码一线岛国| 日韩精品视频在线观看免费| 精品精品国产欧美在线小说区| 四虎精品8848ys一区二区| 国产精品视频一区二区三区经| 精品国产AV一区二区三区 | 国内精品久久久久国产盗摄| 999久久久免费国产精品播放| 精品日产一区二区三区手机| 成人精品视频一区二区三区 | 99在线精品视频在线观看| 精品无码日韩一区二区三区不卡 | 国产在线精品一区二区高清不卡 | 国产精品欧美亚洲韩国日本不卡| 国产成人精品2021| 91精品国产自产在线观看永久 | 久久精品99久久香蕉国产色戒| 亚洲av午夜成人片精品网站| 亚洲欧洲成人精品香蕉网| 最新精品露脸国产在线| 欧美日韩在线精品一区二区三区激情综合| 国产精品欧美亚洲韩国日本久久| 国产精品婷婷午夜在线观看| 精品国产免费人成网站| 精品亚洲欧美中文字幕在线看| 精品免费久久久久国产一区 |