一個完整的聊天軟體,會至少包含四個部分。為了避免單點故障這種重大意外,會交由不同伺服器或容器(Pod)處理,後續舉例會提到:
(1) 使用者相關服務
登入、個人資訊、好友資訊、群聊資訊、在聊天室的發言權限....等。
(2) 在線狀態服務
這邊有兩件小任務:判定使用者上線,以及相關好友上線推播。跟扇出(Fanout)設計有關。
(3) 訊息處理服務
Websocket 解決了通訊即時性的問題,提供使用者間訊息傳送媒介。
(4) 推播服務
有人發送訊息後,跳出訊息通知、被標註提及、未讀訊息數等,都需要另外處理。
把服務(Servive)拆分,讓不同部分的 Code 負責不同的工作,提高了後續變動的彈性和維護性。當 A 服務做的事情不因為 B 服務影響,業務邏輯互相獨立,擴充性大大提高。
也就是所謂的解耦(Decoupling)。
當服務之間像流水線分工後,這時還需要溝通,比如 A 服務做完兩件半成品,需要告訴 B 服務有新的物件要幫忙處理。
直觀上有兩種方式:
一個是讓 A 服務直接去告知 B 服務,這時他必須親自確認 B 服務收到訊息,比如透過 TCP/IP,傳送定義好的檔案或格式,如:純文字、XML 或 JSON。
另一種是,透過郵局或物流中心,先轉發至可配送訊息的中介,後續的通知和處理就靠物流中心處理。使用訊息柱列(Message Queue, MQ),就是這一種異步溝通的方式。
MQ是個柱列(Queue),FIFO,如同郵局整理信件,先寄的信優先送出。這時候可以達到排隊的效果。此時的 A服務便是生產者(Producer), B服務便是消費者(Consumer),傳送的message跟寫信一樣,必須定義收件地址和內容。
對於聊天訊息軟體而言,假設今天有使用者要發送訊息,(1) 使用者相關服務伺服器/ Pod ,可能需要先判斷使用者有沒有被黑名單,或是在群聊中有沒有發言權限,完成之後再交由 (3) 訊息處理服務的 Websocket 伺服器/ Pod 處理。
設想一個情況,同時有多台(1) 使用者相關服務送出發訊息的 request ,如何確保訊息的順序? 這時候MQ可以讓大家乖乖排隊。
另外有幾個好處:
(1) 消費者 Consumer 有空時才會處理 message。除了解耦的乾淨俐落外,還提高處理效能。
(2) 生產者 Publisher 不需知道消費者 Consumer 實際位置(IP)。
(3) 生產者 Publisher、柱列Queue、消費者 Consumer 數量可隨需求增減。系統擴張性好。
當然MQ不是萬能,還必須考慮 message 丟失,問題是丟失後怎麼重試。如果重試之後,怎麼辨識有沒有處理過這 message。
總結: 在幾種場景我們可以考慮訊息柱列(Message Queue, MQ)
(1) 排隊,例如: 千人搶票、股票掛單交易
(2) 異步解耦,例如: 可接受些微延遲的寄mail、消耗CPU的事情