linux伺服器,發現在網上運行總會莫名其妙的主動丟包,向知乎大神們請教?

當前工作是開發Linux上的伺服器,但是最近發現有兩台伺服器在接收消息時,總會莫名其妙的丟包。

當前伺服器的工作原理是:

主線程A用來接收消息,分發到14個工作線程,進行處理消息。

每個工作線程都有自己的消息隊列。在網上運行時間已經很長時間,最近才發現有兩台伺服器在丟包。收到了4億多個消息,丟了1萬多個包。雖然丟包比率不是很高,但是其他伺服器都運行正常,只有這兩台,很傷腦筋。。。。查看丟包時的CPU是正常的。

補充:該系統並不是不允許丟包,可以丟,但是現在的問題是。消息數很少的情況下還會出現這個情況。並且網上很多刀片,消息速度比這個快,都沒出現,只有這兩台。。。

並且主線程和14個工作線程都沒有綁定在同一個CPU上,不應該是調度導致的。

從當前的現象看,只有主線程搶不到14個工作線程的隊列鎖導致,因為當時的消息隊列長度都很小,隊列不可能滿。並且通過日誌推測搶不到鎖導致。

補充:

關於代碼的正確與否先可以忽略一下,因為公司保密性強,所以不是直接截圖公司的代碼。

知道這種回存在丟包的可能性,但是網上怎麼也得有不下兩百台伺服器跑這個代碼,只有這兩台出現這個情況。而且這兩台收包速度也很低,有很多伺服器的收消息速度比這個要快好幾倍,也沒出現問題。所以幫忙看下有沒有其他可以入手點了

並且不是由於消息數多,導致網卡層面緩存滿,導致丟包。而是這個消息已經被主線程取出來了,但是在分發到工作線程是,沒有搶到鎖而丟的。


按你的代碼絕對不是只有兩台丟包這麼簡單,只要有機會讓所有的隊列都非空,就有概率在主線程中丟包,哪怕隊列沒有滿。話說你這個邏輯本來就很詭異啊,你沒發現嗎,你鎖搶不到,意思是工作線程在收隊列里的數據,跟隊列滿了完全就是兩回事,稍等一下就能放進隊列了,你怎麼就丟包了呢?你這邏輯都不對,就不要問為什麼在別的伺服器上能正常執行了,我猜100%別的伺服器上也沒有正常執行,只是你壓根沒發現。

正常來說應該是一個隊列,然後所有工作線程去同一個隊列中讀取數據來處理,所有的鎖都應該是wait而不是try。去看看別人的隊列怎麼實現的,別自己瞎造輪子。依你的「輪詢+搶鎖」的實現方式,「丟包」是正常的。

你貼的代碼是錯的,trylock 的參數類型不對。假設處理一個消息需要 1ms,那 1ms 內如果有 13 個消息,那必然有一個要丟掉。跟隊列長度、消息多少沒有關係,隊列為空一樣丟,只有 13 個消息也一樣丟。你的代碼實現的邏輯就是這樣的,工作線程都在忙的時候,主線程就丟了,而不是等……

這兩台伺服器要麼是承載的峰值消息量高,要麼是處理消息太慢,所以消息總量雖然小但丟包多。

首先其他答主已經說了,這種模式下的確是會丟包的。不過題主可能是問為什麼只有兩個伺服器丟包,其他沒丟。

如果是我,會這樣排查:

  1. 確認一共多少台機器。如果就3台,兩台會丟包。那不奇怪,把代碼優化下吧。
  2. 確認下伺服器配置。如果出問題的伺服器核數比較少,或者會跑別的程序,那的確丟包概率大些。
  3. 確認下所有伺服器收到的消息數,消息類型。會不會是出問題的機器收到的消息比較多。

再說下代碼

  1. 首先代碼要正確,其次才是性能。這裡題主也意識到了丟包情況。先從單一隊列,大家搶一把鎖開始吧。
  2. 如果確實需要sharding,寫成這樣。可以再額外加一個global的隊列,12把鎖都沒搶到就把消息放到global queue里。worker 的thread local queue空了,再去看下global的。(不過我覺得這個是亡羊補牢,不美,而且會飢餓)。
  3. 每個線程可以兩個queue,q1用來裝主線程發來的消息,q2是空。worker拿到鎖後,把q2換上去代替q1收消息。然後解鎖,從q1里取數據處理。再如此周而復始。好處是臨界區只有一次指針切換,省去了拷貝。

第三點是受 @陳碩 網路書中logging buffer啟發想到的。

我覺得這個問題還挺有意思的,希望陳老師以及其他大大們也能從如何排查問題,以及如何優化代碼這兩點展開說下。

不管發包還是收包線程,資源不滿足時,都要pthread_cond_wait。發包線程用pthread_cond_wait等待時,接收消息就得暫停。如果消息隊列滿,發消息的任務必須等待。

如此的一個"消息傳遞鏈"。

若消息傳遞鏈上前端發送者不等待的發,要保證後端不丟包,顯然不可能的。

如果實在不得不丟包,就將消息做成「丟了也沒關係」的消息。怎麼做呢,訣竅一句話-

別用消息發送事件,要發送事件的狀態。

消息用於同步狀態,不是用於傳遞事件,便不懼怕丟包。

邏輯問題別人已經指出了,這種事情你多畫畫visio,就好辦。

但這代碼風格不好。一行代碼,最多別超過兩個操作。比如if(),裡面只能是一個bool,不要再內嵌運算了,因為越是這樣搞,越容易出問題,思路不清晰,而且也不利於調試。他們都沒說的一種可能性,網線或交換機口壞了,換個試試。


強答。

有12個隊列為什麼還要用鎖呢?我想的話,直接五元組哈希入隊,避免亂序,隊列滿了就丟


感覺。。。這個代碼是屬於三大代碼風格中的,撞大運編程= - =


鑒於其他刀片伺服器沒有問題,只有兩台刀片有,建議抓下包用wireshark 分析一下這兩個刀片的網路狀態。


丟包總讓人想到網路或系統上,這個你都查出來是應用層,就說請求丟了吧,建議你先把伺服器換個硬體。對線上業務也太不尊重了。


可以在主線程丟包的時候列印下日誌,看看其他伺服器會不會也丟包。另外沒空閑worker線程就丟包這個設計本身也有問題呀


很詭異的解決辦法,關鍵是你原理和檢測手段都沒有。

上鎖區域要儘可能簡單,傳遞消息或者交換指針等。 再就是使用多個線程,只用一個隊列和鎖。


隊列未滿就把包丟了,這邏輯只要機器性能一抖動不丟包都難吧。我猜你不只這兩台伺服器丟包,其他的可能不明顯你沒發現而已。為什麼不用一個隊列,壓測過嗎?


設計問題吧,因為a線程是輪訓12個接受線程隊列的,那麼有可能第一次 當a線程給所有接受線程都發送了消息,然後所有線程開始處理,假設這個消息很多,每個線程的隊列都是滿的那麼所有的接受線程應該都會鎖住進行處理,這時候有新消息來,a再次遍歷所有的接受線程,因為沒處理完,接受線程應該會lock住保護隊列,所以會trylock失敗,丟消息。重現的辦法就是處理線程每處理一個消息sleep一下,就容易出現了,還有可能就是接受線程有io等待了。如果每次a只放一個消息的話,應該沒問題。如果a一次有1200以上的消息要分發就會有丟了。一切都是猜的!


是不是當12個鎖都被工作線程佔有,主線程不能及時從接受緩衝區取走數據,導致接受緩衝區溢出,進而丟包


當12個鎖都被工作線程鎖住時,消息就被丟了,但這時不一定是隊列滿啊;還有為什麼不只建一個隊列呢?這樣只需要用過條件變數就OK了啊,不需要輪詢。


為什麼不試試無鎖隊列呢?


你用mutex搶鎖本來效率就慢,忙時主線程處理14個鎖的性能開銷估計大的很,容易雪崩。

搞不懂為啥搶不到鎖就丟消息,繼續等待循環不行嗎?

丟消息打個日誌,14個線程接收和處理消息之後也打個日誌,看看丟消息時的運行狀態。

伺服器上可能跑著其他進程擠佔了cpu,或者你的資料庫等配置文件有問題,造成伺服器上工作線程處理效率低下,記錄下每消息處理時間和極端值,從這方面下手。

順便一說我發現那個陳碩回問題基本都是廢話,可以不用關注他浪費時間了。


推薦閱讀:

TAG:Linux | 伺服器 | 知乎用戶推薦 |