1. 概述
在上篇文章Spring Boot系列十六 WebSocket簡介和spring boot整合簡單訊息代理中我們使用的訊息代理是spring內建的簡單訊息代理,簡單訊息代理非常適合入門,但是隻支援STOMP命令的子集(如不支援acks, receipts),依賴於訊息傳送迴圈,並且不支援叢集。我們可以使用外部的訊息代理(如RabbitMQ, ActiveMQ),來實現全功能訊息代理。本文以整合RabbitMQ為例。本文的主要內容如下:
- 使用RabbitMQ做websocket訊息代理的準備工作和訊息流程圖
- Spring Boot使用RabbitMQ做websocket的主要程式碼
- 演示在RabbitMQ不同目的的(destination)用法
2. 使用RabbitMQ做websocket訊息代理的準備工作和訊息流程圖
關於RabbitMQ的用法,可以參考本作者的RabbitMQ系列文章
2.1. 使用RabbitMQ做websocket訊息代理的準備工作
我們選擇類似RabbitMQ全功能的訊息代理。安裝訊息代理後,以支援STOMP的情況情況執行服務。 我們在RabbitMQ上啟動rabbitmq_web_stomp外掛
- 在RabbitMQ上啟動rabbitmq_web_stomp外掛,在rabbitMQ上執行如下命令:sudo rabbitmq-plugins enable rabbitmq_web_stomp
- 登入RabbitMQ管理平臺,看到如下資訊,發現已經開啟stomp代理服務
2.2. 訊息流程圖
此圖和使用簡單訊息最大的不同是"broker relay"用於通過TCP將訊息傳遞給外部STOMP代理(如這裡是RabbitMQ),並將訊息從代理傳遞給訂閱客戶
3. Spring Boot使用RabbitMQ做websocket的主要程式碼
3.1. pom.xml
首先在上一篇文章的基礎上增加如下jar
<!-- https://mvnrepository.com/artifact/io.projectreactor/reactor-net -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-net</artifactId>
<version>2.0.8.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.22.Final</version>
</dependency>
複製程式碼
3.2. BroadcastRabbitMQCtl
和上文BroadcastCtl類似,這裡略
3.3. WebSocketRabbitMQMessageBrokerConfigurer 配置
配置外部Rabibitmq替代Simple Broker做訊息代理:在configureMessageBroker()方法中配置外部RabbitMQ的地址、帳號密碼連線到RabbitMQ
@Configuration
// 此註解開使用STOMP協議來傳輸基於訊息代理的訊息,此時可以在@Controller類中使用@MessageMapping
@EnableWebSocketMessageBroker
public class WebSocketRabbitMQMessageBrokerConfigurer extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
/**
* 註冊 Stomp的端點
*
* addEndpoint:新增STOMP協議的端點。這個HTTP URL是供WebSocket或SockJS客戶端訪問的地址
* withSockJS:指定端點使用SockJS協議
*/
registry.addEndpoint("/websocket-rabbitmq").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
/**
* 配置訊息代理
* 使用RabbitMQ做為訊息代理,替換預設的Simple Broker
*/
registry
// "STOMP broker relay"處理所有訊息將訊息傳送到外部的訊息代理
.enableStompBrokerRelay("/exchange","/topic","/queue","/amq/queue")
.setRelayHost("192.168.0.113")
.setClientLogin("hry")
.setClientPasscode("hry")
.setSystemLogin("hry")
.setSystemPasscode("hry")
.setSystemHeartbeatSendInterval(5000)
.setSystemHeartbeatReceiveInterval(4000);
;
}
}
複製程式碼
3.4. ws-broadcast-rabbitmq.jsp
這裡的jsp和上面的jsp類似,這裡略
3.5. 測試方法
執行啟動類: WebSocketRabbitMQApplication 如果連線RabbitMQ,會列印如下資訊:
2018-03-26 23:22:04.354 [reactor-tcp-io-1] INFO o.s.m.s.s.StompBrokerRelayMessageHandler - "System" session connected.
2018-03-26 23:22:04.358 [reactor-tcp-io-1] INFO o.s.m.s.s.StompBrokerRelayMessageHandler - BrokerAvailabilityEvent[available=true, StompBrokerRelay[192.168.0.113:61613]]
複製程式碼
測試請求: http://127.0.0.1:8080//broadcast-rabbitmq/index 具體測試的配置見下方
4. 演示在RabbitMQ不同目的的(destination)用法
WebSocketRabbitMQMessageBrokerConfigurer中我們需要配置訊息代理的字首。在RabbitMQ中合法的目的字首:/temp-queue, /exchange, /topic, /queue, /amq/queue, /reply-queue/. 我們這裡演示以上後4個的用法
4.1. /exchange/exchangename/[routing_key]
通過交換機訂閱/釋出訊息,交換機需要手動建立,引數說明 a. /exchange:固定值 b. exchangename:交換機名稱 c. [routing_key]:路由鍵,可選
對於接收者端,該 destination 會建立一個唯一的、自動刪除的隨機queue, 並根據 routing_key將該 queue 繫結到所給的 exchangename,實現對該佇列的訊息訂閱。 對於傳送者端,訊息就會被髮送到定義的 exchangename中,並且指定了 routing_key。
在本文的程式碼基礎進行如下修改
-
在RabbitMQ上建立名為rabbitmq交換機
-
在BroadcastRabbitMQCtl中修改傳送者程式碼
SendTo("/exchange/rabbitmq/get-response") public ResponseMessage broadcast(RequestMessage requestMessage){ … } 複製程式碼
-
在ws-broadcast-rabbitmq.jsp中修改接收者的程式碼
stompClient.subscribe('/exchange/rabbitmq/get-response', function(respnose){ showResponse(JSON.parse(respnose.body).responseMessage); }) 複製程式碼
測試: 開啟兩個頁面,其中一個頁面傳送3次,這3個訊息被兩個都收到
4.2. /queue/queuename
使用預設交換機訂閱/釋出訊息,預設由stomp自動建立一個持久化佇列,引數說明 a. /queue:固定值 b. queuename:自動建立一個持久化佇列
對於接收者端,訂閱佇列queuename的訊息 對於接收者端,向queuename傳送訊息 [對於 SEND frame,destination 只會在第一次傳送訊息的時候會定義的共享 queue]
在本文的程式碼基礎進行如下修改
-
在BroadcastRabbitMQCtl中修改程式碼
@SendTo("/queue/rabbitmq") public ResponseMessage broadcast(RequestMessage requestMessage){ … } 複製程式碼
-
在ws-broadcast-rabbitmq.jsp中修改接收者的程式碼
stompClient.subscribe( '/queue/rabbitmq', function(respnose){ showResponse(JSON.parse(respnose.body).responseMessage); }); 複製程式碼
測試: 開啟兩個頁面,其中一個頁面傳送7次,這7個訊息被兩個頁面輪流接收
4.3. /amq/queue/queuename
和上文的"/queue/queuename"相似,兩者的區別是 a. 與/queue/queuename的區別在於佇列不由stomp自動進行建立,佇列不存在失敗
這種情況下無論是傳送者還是接收者都不會產生佇列。 但如果該佇列不存在,接收者會報錯。
在本文的程式碼基礎進行如下修改
-
在RabbitMQ上手動建立名為rabbitmq2的佇列
-
在BroadcastRabbitMQCtl中修改程式碼
@SendTo("/amq/queue/rabbitmq2") public ResponseMessage broadcast(RequestMessage requestMessage){ .. } 複製程式碼
-
在ws-broadcast-rabbitmq.jsp中修改接收者的程式碼
stompClient.subscribe( '/amq/queue/rabbitmq2', function(respnose){ showResponse(JSON.parse(respnose.body).responseMessage); }); 複製程式碼
測試: 對於 SUBCRIBE frame,destination 會實現對佇列的訊息訂閱。 對於 SEND frame,訊息會通過預設的 exhcange 直接被髮送到佇列中。
開啟兩個頁面,其中一個頁面傳送7次,這7個訊息被兩個頁面輪流接收
4.4. /topic/routing_key
通過amq.topic交換機訂閱/釋出訊息,訂閱時預設建立一個臨時佇列,通過routing_key與topic進行繫結 a. /topic:固定字首 b. routing_key:路由鍵
對於傳送者端,會建立出自動刪除的、非持久的佇列並根據 routing_key路由鍵繫結到 amq.topic 交換機 上,同時實現對該佇列的訂閱。 對於傳送者端,訊息會被髮送到 amq.topic 交換機中。
在本文的程式碼基礎進行如下修改
-
在BroadcastRabbitMQCtl中修改程式碼
@SendTo("/topic/get-response") public ResponseMessage broadcast(RequestMessage requestMessage){ … } 複製程式碼
-
在ws-broadcast-rabbitmq.jsp中修改接收者的程式碼
stompClient.subscribe( '/topic/get-response', function(respnose){ showResponse(JSON.parse(respnose.body).responseMessage); }); 複製程式碼
測試: 開啟兩個頁面,其中一個頁面傳送4次,這4個訊息同時被兩個都收到
5. 程式碼
所有的詳細程式碼見github程式碼,請儘量使用tag v0.20,不要使用master,因為master一直在變,不能保證文章中程式碼和github上的程式碼一直相同