Spring Boot系列十七 Spring Boot 整合 websocket,使用RabbitMQ做為訊息代理

hryou0922發表於2018-04-07

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外掛

  1. 在RabbitMQ上啟動rabbitmq_web_stomp外掛,在rabbitMQ上執行如下命令:sudo rabbitmq-plugins enable rabbitmq_web_stomp
  2. 登入RabbitMQ管理平臺,看到如下資訊,發現已經開啟stomp代理服務

Spring Boot系列十七 Spring Boot 整合 websocket,使用RabbitMQ做為訊息代理

2.2. 訊息流程圖

此圖和使用簡單訊息最大的不同是"broker relay"用於通過TCP將訊息傳遞給外部STOMP代理(如這裡是RabbitMQ),並將訊息從代理傳遞給訂閱客戶

Spring Boot系列十七 Spring Boot 整合 websocket,使用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。

在本文的程式碼基礎進行如下修改

  1. 在RabbitMQ上建立名為rabbitmq交換機

  2. 在BroadcastRabbitMQCtl中修改傳送者程式碼

    SendTo("/exchange/rabbitmq/get-response")
    public ResponseMessage broadcast(RequestMessage requestMessage){
    …
    }
    複製程式碼
  3. 在ws-broadcast-rabbitmq.jsp中修改接收者的程式碼

    stompClient.subscribe('/exchange/rabbitmq/get-response', function(respnose){
                    showResponse(JSON.parse(respnose.body).responseMessage);
                })
    複製程式碼

測試: 開啟兩個頁面,其中一個頁面傳送3次,這3個訊息被兩個都收到

Spring Boot系列十七 Spring Boot 整合 websocket,使用RabbitMQ做為訊息代理

4.2. /queue/queuename

使用預設交換機訂閱/釋出訊息,預設由stomp自動建立一個持久化佇列,引數說明 a. /queue:固定值 b. queuename:自動建立一個持久化佇列

對於接收者端,訂閱佇列queuename的訊息 對於接收者端,向queuename傳送訊息 [對於 SEND frame,destination 只會在第一次傳送訊息的時候會定義的共享 queue]

在本文的程式碼基礎進行如下修改

  1. 在BroadcastRabbitMQCtl中修改程式碼

    @SendTo("/queue/rabbitmq")
    public ResponseMessage broadcast(RequestMessage requestMessage){
    …
    }
    複製程式碼
  2. 在ws-broadcast-rabbitmq.jsp中修改接收者的程式碼

     
    stompClient.subscribe(
                    '/queue/rabbitmq',
                    function(respnose){
                    showResponse(JSON.parse(respnose.body).responseMessage);
                });
    複製程式碼

測試: 開啟兩個頁面,其中一個頁面傳送7次,這7個訊息被兩個頁面輪流接收

Spring Boot系列十七 Spring Boot 整合 websocket,使用RabbitMQ做為訊息代理

4.3. /amq/queue/queuename

和上文的"/queue/queuename"相似,兩者的區別是 a. 與/queue/queuename的區別在於佇列不由stomp自動進行建立,佇列不存在失敗

這種情況下無論是傳送者還是接收者都不會產生佇列。 但如果該佇列不存在,接收者會報錯。

在本文的程式碼基礎進行如下修改

  1. 在RabbitMQ上手動建立名為rabbitmq2的佇列

  2. 在BroadcastRabbitMQCtl中修改程式碼

    @SendTo("/amq/queue/rabbitmq2")
        public ResponseMessage broadcast(RequestMessage requestMessage){
    ..
    }
    複製程式碼
  3. 在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個訊息被兩個頁面輪流接收

Spring Boot系列十七 Spring Boot 整合 websocket,使用RabbitMQ做為訊息代理

4.4. /topic/routing_key

通過amq.topic交換機訂閱/釋出訊息,訂閱時預設建立一個臨時佇列,通過routing_key與topic進行繫結 a. /topic:固定字首 b. routing_key:路由鍵

對於傳送者端,會建立出自動刪除的、非持久的佇列並根據 routing_key路由鍵繫結到 amq.topic 交換機 上,同時實現對該佇列的訂閱。 對於傳送者端,訊息會被髮送到 amq.topic 交換機中。

在本文的程式碼基礎進行如下修改

  1. 在BroadcastRabbitMQCtl中修改程式碼

     @SendTo("/topic/get-response")
        public ResponseMessage broadcast(RequestMessage requestMessage){
    …
    }
    複製程式碼
  2. 在ws-broadcast-rabbitmq.jsp中修改接收者的程式碼

    stompClient.subscribe(
                    '/topic/get-response',
                    function(respnose){
                    showResponse(JSON.parse(respnose.body).responseMessage);
                });
    複製程式碼

測試: 開啟兩個頁面,其中一個頁面傳送4次,這4個訊息同時被兩個都收到

Spring Boot系列十七 Spring Boot 整合 websocket,使用RabbitMQ做為訊息代理

5. 程式碼

所有的詳細程式碼見github程式碼,請儘量使用tag v0.20,不要使用master,因為master一直在變,不能保證文章中程式碼和github上的程式碼一直相同

相關文章