search
尋找貓咪~QQ 地點 桃園市桃園區 Taoyuan , Taoyuan

skynet 網路線程的一點優化

skynet 是一個注重并行業務處理的框架,設計它的初衷是可以充分利用多核 CPU 更好的處理那些比較消耗 CPU 的,天然可以并行的業務,比如網路遊戲。網路 I/O 並不是優化重點。

基於這個設計動機,skynet 的網路層使用單線程實現。因為我認為,即使是代碼量稍大一些的單線程程序,也會比代碼量較小的多線程程序更容易理解,出 bug 的機會也更少。而且經典的網路服務程序,如 redis nginx 並沒有因為單線程處理網路 IO 而變現得不堪,反而有不錯的口碑。

所以,skynet 的 epoll 循環並不像 erlang 那樣,只關注讀寫事件,而讓每個 actor 自己去處理真正的 socket 讀寫。那樣固然可以獲得更高的網路處理能力,但勢必讓網路 API (由存在多個工作線程里的多個 actor 分別調用)依賴鎖來保證正確性。這是我不太希望看到的。目前的設計是,所有網路請求,都通過把指令寫到一個進程內的 pipe ,串列化到網路處理線程,依次處理,然後再把結果投遞到 skynet 的其它服務中。

這個做法未必最好,但也恰恰能用,一般網路遊戲伺服器,根據我們的實際項目數據,在其它業務處理的 CPU 佔用到極限時,單台機器網路帶寬不大會超過 30MB 左右的上下行帶寬。一個核每秒處理 60MB 的數據是綽綽有餘的。

不過我一直有個想法,或許可以優化一下這部分,讓 skynet 可以適應一些重 IO 而不僅僅是重業務處理的場合。前些年和一個使用 skynet 做流媒體廣播的同學交流過,他們的生產環境上,一台機器會配置幾塊千兆網卡,skynet 在處理高帶寬的 udp 廣播時,跑不滿硬體的帶寬。

前幾天,skynet 的issue #646又讓我想到這件事情。就這個 issue 而言,我不認為達到網路線程處理極限,可能尚有未發現的其它因素影響,不過就這個機會,我試著實現了一下以前的想法。

我的想法是,可以把網路寫操作從網路線程中拿出來。當每次要寫數據時,先檢查一下該 fd 中發送隊列是否為空,如果為空的話,就嘗試直接在當前工作線程發送(這往往是大多數情況)。發送成功就皆大歡喜,如果失敗或部分發送,則把沒發送的數據放在 socket 結構中,並開啟 epoll 的可寫事件。

網路線程每次發送待發隊列前,需要先檢查有沒有直接發送剩下的部分,有則加到隊列頭,然後再依次發送。

當然 udp 會更簡單一些,一是 udp 包沒有部分發送的可能,二是 udp 不需要保證次序。所以 udp 立即發送失敗后,可以直接按原流程扔到發送隊列尾即可。

加上這個優化后,就必須在每個 socket 結構上增加一個 spinlock 。直接發送的邏輯可以用 try lock 嘗試加鎖,而不一定要獲得鎖;只在網路線程發送隊列期間這一個地方才需要加鎖。所以這個鎖幾乎不會競爭。

畢竟是多線程代碼容易出 bug ,而且也不太容易做測試。所以我暫時把它放在一個獨立分支上,希望感興趣的同學可以幫助 review 一下,以後再考慮是否合併到主幹。



熱門推薦

本文由 yidianzixun 提供 原文連結

寵物協尋 相信 終究能找到回家的路
寫了7763篇文章,獲得2次喜歡
留言回覆
回覆
精彩推薦