Spring Boot系列21 Spring Websocket實現websocket叢集方案討論

hryou0922發表於2018-07-26

概述

本文對websocket叢集的方案進行討論:

  1. 在websocket叢集中,後端準確將指定的訊息推送到指定的使用者,前端實時接收服務推送的訊息
  2. 對websocket叢集的方案進行討論,並確定最佳方案

webscoket叢集方案

叢集方案分析

這裡寫圖片描述

在上個博文Spring Boot系列20 Spring Websocket實現向指定的使用者傳送訊息中實現向指定使用者傳送訊息的功能,但是我們將提供websocket服務的服務進行叢集(如上圖)則存在如下問題:

上圖中,使用者A通過websocket註冊到服務A,服務A通過STOMP協議訂閱RabbitMQ上的訊息,同理使用者B。如果使用者A連線到服務A上,那麼在位於服務B上的MQ模組即使使用SimpMessagingTemplate例項向使用者A傳送訊息,此訊息也無法到達使用者A,原因是因為服務B上沒有服務A的註冊資訊,無法準確的推送訊息.只有在服務A上的MQ模組使用SimpMessagingTemplate例項向這個使用者傳送訊息,訊息才會到達使用者A

針對這個問題下文我們通過3個方案解決這個問題,並詳細分析每個方案的有缺點。

webSocket叢集方案一

概述 不管訊息的接收者連線在哪個服務上,每個服務A/B都接收訊息,對相同的訊息都使用SimpMessagingTemplate例項進行推送,保證總有一個訊息會被使用者收到。

這裡寫圖片描述

詳細流程如下

  1. 使用者A/B分別通過ws連線服務A/B, 然後服務A/B通過stomp協議接入RabbitMQ
  2. 訊息傳送者將訊息傳送到RabbitMQ的交換機上,使用扇形交換機。這樣保證同一個訊息可以同時被服務A/B接收
  3. 兩個服務上的MQ模組接收對應訊息後,不管對應的使用者是否是通過自己連線到RabbitMQ,直接使用SimpMessagingTemplate例項向訊息中指定的使用者推送訊息
  4. 使用者A/B接收到對應的訊息

優點

  1. 實現比較簡單

不足

  1. 訊息生產者傳送訊息的RabbitMQ交換機必須是廣播功能,如扇形交換機
  2. 為了保證訊息順利到達使用者,相同的訊息必須在兩個服務A/B上執行相同的操作。這樣如果服務越多,則重複的傳送訊息越多
  3. 如果使用者不線上,無論傳送多少訊息使用者都不能收到

webSocket叢集方案二

概述 使用redis快取使用者的websocket連線資訊,記錄使用者登入到哪個服務上,當有訊息過來時,將訊息推送到使用者登入的服務,然後服務都使用SimpMessagingTemplate例項進行推送

這裡寫圖片描述

在方案一的基礎上增加如下功能:

  1. 服務A/B上增加MQ模組,服務A/B上MQ模組會連線到RabbitMQ,分別訂閱佇列A/B
  2. 服務A/B增加WS模組,當websocket連線過來時,將此使用者的連線資訊儲存到redis上,系統記住每個使用者登入的到哪個服務
  3. 訊息生產者將訊息推送到交換機,不直接推送到服務A/B
  4. 增加新的模組dispatch,此模組接收到訊息,然後從redis中讀取要訊息要推送到使用者連線到那個伺服器上,然後將訊息傳送到使用者連線服務對應的佇列中。如果訊息要傳送給使用者B,則dispatch模組會將訊息傳送到佇列B
  5. 服務A/B的MQ模組接收到訊息後,使用SimpMessagingTemplate例項向指定使用者推送訊息

優點

  1. 此方案克服上一個方案不足的地方

缺點

  1. 實現複雜
  2. 傳送MQ訊息的次數增加1倍

webSocket叢集方案三

概述 不使用SimpMessagingTemplate,使用RabbitMQ的客戶端API直接向使用者在RabbitMQ上訂閱的佇列傳送訊息

發現使用者通過瀏覽器登入websocket並註冊RabbitMQ時,此時這個連線會在RabbitMQ建立一個佇列,佇列的名稱類似stomp-subscription-***,此佇列繫結到預設交換機amq.topic,路由鍵為"web訂閱佇列名稱+'-user'+websocket sessionId"(這裡是demo-userpjplggbl,demo是stomp weboscket連線的佇列名稱,pjplggbl登入websocket登入時的websocket sessionId值),圖片如下:

這裡寫圖片描述

根據這個,設計如下架構:

這裡寫圖片描述

在方案一的基礎進行如下修改,新的架構圖流程如下:

  1. 服務A增加WS模組,當websocket連線過來時,將此使用者的連線資訊(主要是websocket sesionId值)儲存redis中
  2. 訊息生產者傳送訊息到的交換機,這些服務不直接推送服務A/B
  3. 增加新的模組dispatch,此模組接收推送過來的資訊,並從redis中讀取訊息接收使用者對應的websocket sesionId值,然後根據上面的規則計算出使用者對應的路由鍵,然後將訊息傳送到使用者訂閱的佇列上
  4. 前端接收訊息

優點:

  1. 即克服第一個方案不足的地方,又比第二個方案簡單

結論

方案三是最好的方案,下一篇文章,我們會介紹如何在程式碼中實現方案三

相關文章