skynet 消息分發及服務調度的新設計

jopen 10年前發布 | 7K 次閱讀 消息系統 軟件架構

這個月 skynet 的 1.0 就會 release 最終版了,除了維護這個穩定版本。我考慮可以對一些不太滿意的地方嘗試做大刀闊斧的改變(當然不放在目前的穩定版本中)。

我對 skynet 解決的核心問題:多服務任務調度以及內部消息傳播這塊不是很滿意,覺得如果換個方式實現可能會好一些。下面先把想法記下來。

目前,每個服務都有一個唯一的消息隊列,且在內存足夠的前提下,會無限增長。也就是說,向一個服務發消息是沒有失敗的可能的。多數情況下,單個服務的消息隊列不會太長,在生產消費模型中,也不允許太長。太長意味著消費速度遠遠低于生產速度,情況多半會惡化。在歷史上發生過多起事故,都是和服務過載 有關。

雖然 skynet 提供了 mqlen 這種方法供使用者查詢當前服務的消息隊列長度,以做出應變,但治標不治本。我想做一個大的設計改動來重新考慮這一塊。

我們可以把每個服務的消息隊列實現成固定長度,且固定長度并不需要太長,大約 256 個 slot 這種級別就足夠了。一個固定長度的循環隊列實現起來要簡單的多,且很容易做成進出隊列互不影響。因為出隊列只發生在一個服務內,不可能并發,根本不需要考慮競爭;而僅僅只需要考慮進隊列的競爭問題。

當隊列滿,或有人在入隊列操作時,都認為隊列忙,不需要做鎖,直接失敗即可。遇到忙的時候發送方可以自行緩存代發數據。而多方同時寫入隊列時,對 skynet 來說,其實不需要保證先后次序,有時序要求的僅僅是同一發送者對一個特定接受者。在隊列不忙時,誰先寫都是沒問題的;且同一服務下,當有多個待發出隊列時,先處理哪一個也不太所謂。

這樣就可以把消息進出這塊的所有競爭都去掉,實現起來也非常簡單。而且可以大大緩解服務過載后的雪崩問題。因為退出一些有未發出消息的服務,那些沒有發出的消息也自然被扔掉了。而目前的設計則會將所有消息都堆積在一條消息隊列中,這些消息在玩家頻繁上下線時會有大量無效信息。

這里提到的新設計比現在復雜的一點是,該什么時候喚醒一個服務。在現有的情況下,只有一個服務獲得新消息,且不在熱服務隊列中,它才會把自己壓入熱服務隊列,待工作線程去處理。而做出以上改變后,一個服務又未發出的消息時,也需要在接收方解除擁塞后喚醒。

我的想法是索性把服務的任務調度也一并改進。采用一個更簡單更粗暴的方式來做。

目前其實把服務分成兩類的,一類是熱服務,就是有消息待處理的;另一類是冷服務,服務活著,但消息隊列暫時為空。工作線程只要從熱服務隊列中依次取出服務調用回調函數即可。

這么設計是考慮到熱服務的數量通常遠小于總的服務數量,如此能減少工作線程輪詢一個服務是否有消息可處理的開銷。

是不是可以考慮另一種算法呢?

每個工作線程可以重復一個工作循環。

第一步就是遍歷所有的服務,挑選出當前有事情要做(包括有消息要處理,有消息發出被擁塞)的服務,去掉正在被別的工作線程處理的那些(服務忙),把這些服務放在當前工作線程下的一個集合中。

第二步,依次處理自己集合中的服務消息。在處理工作中,如果碰到別的工作線程已經在處理,或消息隊列空,或待發隊列依然無法處理(接收方擁塞)則立刻將該服務從自己的集合中去掉。

由于移出集合的條件很寬松,而不會加入新的元素,所以只要不斷循環第二步,自己所屬的集合會越來越小。但為了防止某幾個服務霸占工作線程,還可以加一個循環次數上限,相當于讓過熱的服務有個冷卻的機會。

當第二步的集合為空,或是達到了循環上限。那么結束這個工作循環,回到第一步繼續。

以上是一些初步的想法,晾到年后再動手實現。

來自: http://blog.codingnow.com/2016/01/skynet2.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!