TCP窗口被填滿問題的排查實(shí)踐
問題背景:
某日17:12左右,收到實(shí)施人員投訴,有部分設(shè)備不能正常升級、收不到控制臺下發(fā)的指令等問題,同事查看control工程(后面簡稱control)那邊的日志,發(fā)現(xiàn)control沒有收到設(shè)備上報(bào)的影子信息,所以沒有下發(fā)指令。control工程直接對接設(shè)備,根據(jù)設(shè)備上報(bào)的信息對設(shè)備下發(fā)一些指令及配置信息,包括升級、上報(bào)日志等,IoT平臺上線之前control依賴心跳上報(bào)來獲取設(shè)備的當(dāng)前信息,IoT平臺上線之后依賴設(shè)備影子信息來獲取設(shè)備的當(dāng)前信息,control會(huì)訂閱設(shè)備的影子信息,但影子信息是由影子服務(wù)(簡稱IoT)轉(zhuǎn)發(fā)過去的,它不直接對接設(shè)備影子上報(bào),具體流轉(zhuǎn)細(xì)節(jié),
請看這下面兩個(gè)圖:
應(yīng)用程序(control)獲取設(shè)備狀態(tài)
應(yīng)用程序(control)下發(fā)設(shè)備指令
得知control收不到影子消息以后,我立馬去rabbitmq的控制臺查看是否有消息,
確定兩個(gè)事:
1.設(shè)備是否上報(bào)了消息
2.rabbitmq是否正常
下面圖1、圖2是當(dāng)時(shí)截取的rabbitmq控制臺的兩個(gè)圖,從圖1可以很清楚的確定設(shè)備是有消息上報(bào)的,但是有很多消息是unacked(說明已經(jīng)投遞給了消費(fèi)者,只是消費(fèi)者沒有ack而已,理論上等待一段時(shí)間就能正常)的,具體是哪個(gè)隊(duì)列堆積unacked的消息請看圖2,“spacebridgeiot-shadow”正是我們用來接收設(shè)備上報(bào)的影子信息的隊(duì)列,消息都被堆積到隊(duì)列了所以沒有轉(zhuǎn)發(fā)到control也是合理的,觀察了一段時(shí)間發(fā)現(xiàn)unacked的數(shù)量變成了0,但是total的總數(shù)確沒有太大變化,給人的感覺像是unacked的消息重新回到了消息隊(duì)列里等待投遞,果然過了幾分鐘以后又發(fā)現(xiàn)有大量unacked的消息,過了幾分鐘以后這部分unacked的消息重新回到隊(duì)列里,control那邊依然沒有收到消息,這時(shí)查看IoT那邊的日志發(fā)現(xiàn)竟然沒有影子消息進(jìn)來,在rabbitmq的控制臺查看“spacebridgeiot-shadow”這個(gè)隊(duì)列下居然沒有消費(fèi)者了,如圖3所示。
這時(shí)查看rabbitmq的日志確實(shí)有錯(cuò)誤信息,如圖4所示,rabbitmq主動(dòng)關(guān)閉了連接。
圖1:rabbitmq概覽圖
圖2:rabbitmq隊(duì)列統(tǒng)計(jì)圖
圖3:spacebridgeiot-shadow 概覽
圖4:rabbitmq報(bào)錯(cuò)信息
臨時(shí)解決方案:
由于當(dāng)時(shí)已經(jīng)有大量投訴過來了,所以采用了比較暴力的解決辦法“將堆積的消息刪除”,刪除以后果然正常了(備注:線上問題必須盡快解決,沒有時(shí)間允許我們?nèi)シ治鋈罩救缓笥袟l不紊的解決,必須快)。
通過線下環(huán)境復(fù)現(xiàn)問題:
- 1.往10.200.41.166環(huán)境的rabbitmq的隊(duì)列“mirrorTestQueue”堆積大量消息(起碼萬級)
- 2.停掉mirrorTestQueue的消費(fèi)者,待堆積完成以后重新啟動(dòng)
- 3.堆積完成,重新啟動(dòng)消費(fèi)者
和我們設(shè)想的一樣,幾秒內(nèi)有幾千條消息推給了消費(fèi)者,持續(xù)幾分鐘以后rabbitmq主動(dòng)關(guān)閉了和消費(fèi)者之間的連接,這時(shí)從控制臺看不到隊(duì)列的消費(fèi)者。
由于我們的消費(fèi)者設(shè)置了自動(dòng)恢復(fù),所以過一陣又會(huì)自動(dòng)連上,但很快又會(huì)被斷連,和我們線上遇到的問題基本一樣,究竟是什么導(dǎo)致了這個(gè)問題呢?說實(shí)話當(dāng)時(shí)沒有什么思路,網(wǎng)上找了一圈也沒找到什么特別滿意的答案(當(dāng)時(shí)沒有抓到問題的本質(zhì),搜的關(guān)鍵詞太泛了),后來我們猜測可能是TCP層面出了什么問題,所以決定抓包試試能不能找到什么端倪。
果然,幸運(yùn)的事情發(fā)生了,話不多說,直接上圖。
13:06:25.643428之前rabbitmq還一直在給消費(fèi)者推消息,直到13:06:25.643428這個(gè)時(shí)間點(diǎn),開始出現(xiàn)消費(fèi)者tcp窗口被打滿的情況,大概持續(xù)了30秒左右,rabbitmq主動(dòng)斷開了連接(發(fā)了一個(gè)rst包),之后消費(fèi)者重連,然后窗口又繼續(xù)被打滿,又持續(xù)30秒左右繼續(xù)被斷連。
感覺還挺有規(guī)律,每次持續(xù)30s,感覺是可配置的一個(gè)參數(shù),大概總結(jié)一下就是“tcp full window導(dǎo)致了服務(wù)端主動(dòng)rst連接,而且還有規(guī)律”
這次換了一下搜索的關(guān)鍵詞找到了答案,rabbitmq有一個(gè)參數(shù)叫tcp_listen_options.send_timeout 是來控制寫超時(shí)的一個(gè)參數(shù),當(dāng)寫超時(shí)了以后就會(huì)觸發(fā)tcp的RST(https://github.com/rabbitmq/rabbitmq-java-client/issues/341),修改一下試試效果如何:
1. 將寫超時(shí)時(shí)間改成10s
tcp_listen_options.send_timeout = 10000
2.抓包看看是否起作用
可以看到從窗口滿到關(guān)閉連接持續(xù)10s左右,說明這個(gè)參數(shù)是起作用的。
現(xiàn)象復(fù)盤:
由于rabbitmq的消費(fèi)端沒有設(shè)置prefetch所以rabbitmq一次性給消費(fèi)端投遞了過多的消息,從而導(dǎo)致消費(fèi)端的 tcp 窗口被占滿,進(jìn)而觸發(fā)了rabbitmq 的tcp_listen_options.send_timeout,這個(gè)寫超時(shí)達(dá)到一個(gè)閾值后會(huì)觸發(fā)rabbitmq斷開消費(fèi)者的tcp 連接。
終極解決方案:
之前刪除消息只是迫不得已的方案,雖然解決了問題但太暴力,我們需要找到一個(gè)優(yōu)雅的方案來應(yīng)對,既然是推給消費(fèi)者的消息太多造成了tcp窗口被打滿,那我們就應(yīng)該在接收速率上下點(diǎn)功夫,在連接rabbitmq的時(shí)候告訴它別給我發(fā)太多就行。
后面這段話摘自 http://www.dbjr.com.cn/article/236407.htm
rabbitmq
有一個(gè)屬性叫prefetch
prefetch是指單一消費(fèi)者最多能消費(fèi)的unacked messages數(shù)目。
如何理解呢?mq為每一個(gè) consumer設(shè)置一個(gè)緩沖區(qū),大小就是prefetch。每次收到一條消息,MQ會(huì)把消息推送到緩存區(qū)中,然后再推送給客戶端。當(dāng)收到一個(gè)ack消息時(shí)(consumer 發(fā)出baseack指令),mq會(huì)從緩沖區(qū)中空出一個(gè)位置,然后加入新的消息。但是這時(shí)候如果緩沖區(qū)是滿的,MQ將進(jìn)入堵塞狀態(tài)。更具體點(diǎn)描述,假設(shè)prefetch值設(shè)為10,共有兩個(gè)consumer。也就是說每個(gè)consumer每次會(huì)從queue中預(yù)抓取 10 條消息到本地緩存著等待消費(fèi)。同時(shí)該channel的unacked數(shù)變?yōu)?0。而Rabbit投遞的順序是,先為consumer1投遞滿10個(gè)message,再往consumer2投遞10個(gè)message。如果這時(shí)有新message需要投遞,先判斷channel的unacked數(shù)是否等于20,如果是則不會(huì)將消息投遞到consumer中,message繼續(xù)呆在queue中。之后其中consumer對一條消息進(jìn)行ack,unacked此時(shí)等于19,Rabbit就判斷哪個(gè)consumer的unacked少于10,就投遞到哪個(gè)consumer中。
具體到代碼里就是
如何評估這個(gè)值呢,rabbitmq官方有個(gè)文章說的很好,就不細(xì)說了,我們的系統(tǒng)中目前設(shè)置的是20。
https://www.rabbitmq.com/blog/2012/05/11/some-queuing-theory-throughput-latency-and-bandwidth/
結(jié)束語:
對于關(guān)鍵組件的使用一定要熟悉其api,理解各個(gè)參數(shù)的含義和語法,當(dāng)出現(xiàn)問題時(shí)不要局限于組件層面排查,必要的時(shí)候需要深入到底層,比如網(wǎng)絡(luò),操作系統(tǒng)等。
以上就是TCP窗口被填滿問題的排查實(shí)踐的詳細(xì)內(nèi)容,更多關(guān)于TCP窗口填滿問題排查的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
ASP,PHP與.NET偽造HTTP-REFERER方法及防止偽造REFERER方法探討
ASP,PHP與.NET偽造HTTP-REFERER方法及防止偽造REFERER方法探討...2007-03-03即時(shí)通訊軟件在網(wǎng)頁上啟動(dòng)臨時(shí)對話的鏈接代碼
旺旺臨時(shí)對話的鏈接,MSN臨時(shí)對話的鏈接,Skype臨時(shí)對話的鏈接2008-11-11Git基礎(chǔ)學(xué)習(xí)之文件刪除操作命令詳解
這篇文章主要為大家詳細(xì)介紹了Git基礎(chǔ)學(xué)習(xí)中的文件刪除操作命令,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以了解一下2022-10-10Istio?訪問外部服務(wù)流量控制最常用的5個(gè)技巧示例
這篇文章主要介紹了Istio訪問外部服務(wù)流量控制最常用5個(gè)技巧示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06