最近生産環境出現多次primary寫入qps太高,導緻seconary的同步無法跟上的問題(secondary上的最新oplog時間戳比primary上最舊oplog時間戳小),使得secondary變成recovering狀态,這時需要人工介入處理,向secondary發送resync指令,讓secondary重新全量同步一次。
下圖是mongodb資料同步的流程

新節點加入(或者主動向secondary發送resync)時,secondary會先進行一次initial sync,即全量同步,周遊primary上的所有db的所有集合,将資料拷貝到自身節點,然後讀取『全量同步開始到結束時間段内』的oplog并重放。全量同步不是本文讨論的重點,将不作過多的介紹。
全量同步結束後,secondary就開始從結束時間點建立tailable cursor,不斷的從同步源拉取oplog并重放應用到自身,這個過程并不是由一個線程來完成的,mongodb為了提升同步效率,将拉取oplog以及重放oplog分到了不同的線程來執行。
producer thread,這個線程不斷的從同步源上拉取oplog,并加入到一個blockqueue的隊列裡儲存着,blockqueue最大存儲240mb的oplog資料,當超過這個門檻值時,就必須等到oplog被replbatcher消費掉才能繼續拉取。
replbatcher thread,這個線程負責逐個從producer thread的隊列裡取出oplog,并放到自己維護的隊列裡,這個隊列最多允許5000個元素,并且元素總大小不超過512mb,當隊列滿了時,就需要等待oplogapplication消費掉。
oplogapplication會取出replbatch thread目前隊列的所有元素,并将元素根據docid(如果存儲引擎不支援文檔鎖,則根據集合名稱)分散到不同的replwriter線程,replwriter線程将所有的oplog應用到自身;等待所有oplog都應用完畢,oplogapplication線程将所有的oplog順序寫入到local.oplog.rs集合。
producer的buffer和apply線程的統計資訊都可以通過db.serverstatus().metrics.repl來查詢到,在測試過程中,向primary模拟約10000 qps的寫入,觀察secondary上的同步,寫入速率遠小于primary,大緻隻有3000左右的qps,同時觀察到<code>producer的buffer很快就達到飽和,可以判斷出oplog重放的線程跟不上</code>。
預設情況下,secondary采用16個replwriter線程來重放oplog,可通過啟動時設定replwriterthreadcount參數來定制線程數,當提升線程數到32時,同步的情況大大改觀,主備寫入的qps基本持平,主備上資料同步的延時控制在1s以内,進一步驗證了上述結論。
如果因primary上的寫入qps很高,經常出現secondary同步無法追上的問題,可以考慮以下改進思路
配置更高的replwriterthreadcount,secondary上加速oplog重放,代價是更高的記憶體開銷
附修改replwriterthreadcount參數的方法,具體應該調整到多少跟primary上的寫入負載如寫入qps、平均文檔大小等相關,并沒有統一的值。
通過mongod指令行來指定
mongod --setparameter replwriterthreadcount=32
在配置檔案中指定
<a href="https://docs.mongodb.com/manual/core/capped-collections/">capped collection</a>
<a href="https://jira.mongodb.org/browse/server-18908">server-18908</a>
<a href="https://docs.mongodb.com/v3.0/tutorial/change-oplog-size/">修改oplog的大小</a>
<a href="https://www.aliyun.com/product/mongodb/?spm=5176.7960203.237031.33.ydynry">阿裡雲mongodb資料庫</a>