接到現場報告,客戶MongoDB間資料延遲越來越大,有的已經超過2-3個小時,造成有些打到延遲mongodb上面的資料庫請求無法反應資料庫的最新更改,這個問題反復出現在高峰期尤其明顯,持續近一月,
架構
客戶為異地雙機房架構,兩地機房相隔上千公里,帶寬250M,光纖,具體鏈路情況不明
F5 | F5
N中心 | S中心
業務服務 ... 業務服務 | 業務服務 .... 業務服務
mongodb4 ... mongodb6 <----|----- mongodb1(主) ... mongdb2
所有資料庫在一個復制集共6臺(其中一臺不參與選舉),使用的是mongodb replicaSet 進行同步,
現象
S中心資料庫反復產生延遲,其中一次rs.printSlaveReplicationInfo()查詢集群情況顯示如下,資料庫4,5,6三臺延遲(此處不是每次都是三臺同時延遲,有時只是這三臺中的兩臺或者一臺出現延遲,但是1,2從不延遲),延遲時間最多達到2-3個小時,非高峰期可以恢復,

?
- 客戶在兩個機房間通過ping命令未發現南北網路有延遲現象,
- 在發生延遲時,有時可以通過切換同步源的方式,暫時解決問題,
插入圖
分析
首先查看有問題的機器,發現4,5,6三臺均在N中心,1,2均在S中心,而用rs.status查看,可以發現當延遲時,這三臺都以1為同步源,也就是發生跨網路同步,
由于三臺出現同步的機器都在N中心,可猜測與鏈路有關,客戶ping命令未顯示延遲,說明ping的資料量可能不夠或者問題是由某種條件觸發的,
是否有可能同步帶寬超過了鏈路限制,我們和網路方面的人進行了溝通,得到的訊息是我們的應用使用的帶寬沒有達到上限,最高也就極限帶寬的2/3左右,為確認帶寬情況,我們自己也對帶寬進行了分析,
帶寬使用分析
南北中心業務高峰期正常情況下帶寬占用70Mb/s,也沒有達到理論高度,而且正常情況下某些瞬時,帶寬可以超過150Mb/s,也證明服務遠遠沒有達到帶寬上線,我們進一步對產生延遲的情況下的帶寬進行分析,發現了一個奇怪的情況
:在產生延遲的情況下,資料庫用來同步的帶寬遠遠不足60Mb/s ,只有不到15Mb/s,也就是說,資料庫有大量堆積,產生延遲的時候,資料庫同步反而變慢了,
同時我們監控了S中心內未延遲節點與通中心主節點同步時的帶寬約為17Mb/s ,由于mongodb每臺slave都是復制同樣的資料,可以推知其他機器都應該按照這個速率進行同步,而延遲節點只有4.5Mb/s左右,
我們又查看了延遲節點的監控,查看了延時節點從正常狀態轉入延遲狀態的帶寬情況,發現其使用帶寬會在某種情況下發生驟降
到底是什么導致這種狀況呢?是mongodb本身的問題嗎?
這種情況我們還是決定圍繞核心現象來進行分析:這幾臺服務器和正常的服務器有什么不同,我們一一排查了機器配置,軟體配置的問題,確保出問題的服務器和正常服務器沒有原則不同,同時也查了這幾臺服務器在延遲時的表現,發現延遲時,這幾臺mongo的指標遠遠沒有到達瓶頸,所以唯一可疑的不同還是在網路上,即使ping沒有發現問題,我們還是決定要進一步抓包,
抓包
因為抓包對性能有顯著影響,為了抓出特征包,我們選在節點開始延遲,并且延遲時間還在增大時進行抓包,抓包時間在1分鐘以內,
抓包陳述句 tcpdump -i eth0 port xxx -w /home/tcpdump.cap
下面是我們看到的情況(使用Wireshark)

?
網路中出現了大量的Dup ACK,這個說明了TCP出現了資料丟失導致了重傳,
Dup ACK
TCP的流程是面向連接,會盡力保證資料的完整性,所以有失敗重傳機制,Dup ACK 就是這個機制的一部分,
每個包都有一個Seq號和一個包長度,正常情況下,某一端所有發送的包的Seq號是連續的,比如
第一個包的Seq是1,長度是2,
則第二個包Seq=1+2=3,長度4,
第三個包的Seq=3+4,長度若干,
以此類推,當接收端收到這個包時會將這個包放到緩沖區,緩沖區里可能有很多從另一端發來的包,他們必須能連續起來,接收端會向發送端發包時告知會帶上我接收到的最后一個連續的包的Seq是多少,這個資料會放在ACK里傳送,
上面注意:
- 發送端和接收端是相對的,兩邊都遵循這個規則,
- 不是每一個包都ACK,這樣開銷太大,
- 也不一定有專門用于ACK的包,事實上ACK包往往也可能是對上一個或者幾個包的回復,順便就進行了ACK,
插入圖
假如丟失了一個包會怎么樣?接收端發現快取里的包丟失了,而后面的包已經到了,就會觸發上面的Dup ACK,告知我接收到的連續的資料的序號到哪里為止,也就是DupACK包的ACK,這個請求會反復發送,直到服務端發送缺失包(retransmit),在收到缺失包前,后續的包即使收到也不會ack,這個是retransmit的流程,
上面的流程在dump中典型的例子用wireshark打開會是下面的樣子:注意這里包括【TCP Previous segment not captured】【Dup ACK】 等內容都是wireshark的分析結果,TCP包中并不包含這些標簽,
首先從端發現丟包【TCP Previous segment not captured】然后引發從端大量發送dup ack,直到主端重傳通過TCP Fast Retransmition 或Retransmition 重傳missing package,見下圖,重傳期間所有有缺失的包會暫存入buffer,

?
在wireshark中可以對重傳的包進行過濾 tcp.analysis.retransmission
過濾結果為32條,占所抓包的0.3%,這個統計可以看做是就是丟包的統計,然而由于這樣的丟包,觸發tcp進行反復溝通引起的混亂遠遠大于此,如果統計所有由此引起的dup Ack, Out of Order,Fast Retransmit,Transmit,window_update 等,可以看到這些處理丟包的請求占到了5.5%之多,
同時剛才提到發送端可能發送很多包,但是只是其中一個丟了,這些沒有丟掉的包會放在一個緩沖區中,這個緩沖區叫做window,由于后續包不斷到來,而失去的包沒有補上,所以tcp不能向上層轉發,于是就會更改window的大小,這個也是非常消耗資源的行為,
總結來說:
從庫的dump中,共抓包16.81秒10176個包,錯誤重傳造成的dup ack和重傳包達到562 占 5.5%;
主庫(同步源)的dump中,共抓包12秒 3501個包,錯誤重傳以及dup ack相關的包占到325,占比9.3%;
而同機房內部節點完全未見丟包與重傳,且在跨機房節點處于無延遲正常狀態也未見丟包重傳,
重傳與流量下降間的關系
為了確保這個現象只在跨機房呼叫中出現,我們也抓取了同機房同步,以及跨機房無延遲時的情況,這兩種情況下均無上述問題,重傳錯誤與流量的關系可見下圖,折線為吞吐,柱形為error包數量,可見流量的重大轉折均伴隨有相關錯誤,下圖也展現了window scaling的情況,展示出由于錯誤發生,windows size 的修改情況,可見6分鐘左右發生了一次大的scaling,正對應了dump中的錯誤,

?
結論與解決方法
在丟包率0.3%的情況下,mongodb的replicaSet發生了比較嚴重的問題,表現為同步速率大幅下降,然后產生延遲,
客戶網路情況比較復雜,鏈路是運營商鏈路,還有許多第三方廠商的設備,難以在短時間內排查原因,所以再發生同步時,我們采用rs.syncFrom腳本切換同步源臨時解決這個問題:即當腳本監測延遲>10分鐘時,立刻切換到另外一個資料源,
此方法基于在產生延遲后,立即切換另外一個資料源可以重新打開同步鏈路快速同步,瞬間同步時速可以到達160Mb/s,延時資料能很快消失,
但是在執行程序中,出現過切換同時,mongodb由于大量寫入而影響業務的情況,所以后來改為在非高峰先講需要修改的節點改為hide節點再進行處理,
未來最終解決方案
解決網路延遲:這個是最直接的解決辦法,畢竟同機房同步未見問題,
減少replicaSet的量:檢查業務中的寫入更新洗掉邏輯,減少生成量,具體分析和處理方法將另文描述,
使用mongoshake等第三方同步工具:由于尚未明晰重傳為何導致復制集處理速度下降,所以不使用復制集也可能可以解決這個問題,且復制集對源的選擇有自己的演算法,雖然可以短時間的切換,但是更多時候是根據mongodb自身的演算法進行選擇,比如在本案中,三臺N節點Mongo都到S節點同步,造成帶寬乘以3倍,雖然可以手動制定其中兩臺從同節點一臺復制,但mongodb還是有可能自動根據自己的演算法切換源,而使用第三方同步工具可以解決上面的兩個問題,但是要檢查第三方工具在0.3%網路丟包下的作業情況,
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/4604.html
標籤:NoSQL
