第四十二章: 基於SpringBoot & RabbitMQ完成DirectExchange分散式訊息多消費者消費

恆宇少年發表於2018-01-04

在上一章第四十一章: 基於SpringBoot & RabbitMQ完成DirectExchange分散式訊息消費我們講解到了RabbitMQ訊息佇列的DirectExchange路由鍵訊息單個消費者消費,原始碼請訪問SpringBoot對應章節原始碼下載檢視,訊息佇列目的是完成訊息的分散式消費,那麼我們是否可以為一個Provider建立並繫結多個Consumer呢?

本章目標

基於SpringBoot平臺整合RabbitMQ訊息佇列,完成一個Provider繫結多個Consumer進行訊息消費。

SpringBoot 企業級核心技術學習專題

專題 專題名稱 專題描述
001 Spring Boot 核心技術 講解SpringBoot一些企業級層面的核心元件
002 Spring Boot 核心技術章節原始碼 Spring Boot 核心技術簡書每一篇文章碼雲對應原始碼
003 Spring Cloud 核心技術 對Spring Cloud核心技術全面講解
004 Spring Cloud 核心技術章節原始碼 Spring Cloud 核心技術簡書每一篇文章對應原始碼
005 QueryDSL 核心技術 全面講解QueryDSL核心技術以及基於SpringBoot整合SpringDataJPA
006 SpringDataJPA 核心技術 全面講解SpringDataJPA核心技術

構建專案

我們基於上一章的專案進行升級,我們先來將Chapter41專案Copy一份命名為Chapter42

構建 rabbitmq-consumer-node2

基於我們複製的Chapter42專案,建立一個Module子專案命名為rabbitmq-consumer-node2,用於消費者的第二個節點,接下來我們為rabbitmq-consumer-node2專案建立一個入口啟動類RabbitmqConsumerNode2Application,程式碼如下所示:

/**
 * 訊息佇列訊息消費者節點2入口
 * ========================
 *
 * @author 恆宇少年
 * Created with IntelliJ IDEA.
 * Date:2017/11/26
 * Time:15:15
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@SpringBootApplication
public class RabbitmqConsumerNode2Application
{
    static Logger logger = LoggerFactory.getLogger(RabbitmqConsumerNode2Application.class);

    /**
     * rabbitmq消費者啟動入口
     * @param args
     */
    public static void main(String[] args)
    {
        SpringApplication.run(RabbitmqConsumerNode2Application.class,args);

        logger.info("【【【【【訊息佇列-訊息消費者節點2啟動成功.】】】】】");
    }
}
複製程式碼

為了區分具體的消費者節點,我們在專案啟動成功後列印了相關的日誌資訊,下面我們來編寫application.properties配置檔案資訊,可以直接從rabbitmq-consumer子專案內複製內容,複製後需要修改server.port以及spring.application.name,如下所示:

#埠號
server.port=1112
#專案名稱
spring.application.name=rabbitmq-consumer-node2


#rabbitmq相關配置
#使用者名稱
spring.rabbitmq.username=guest
#密碼
spring.rabbitmq.password=guest
#伺服器ip
spring.rabbitmq.host=localhost
#虛擬空間地址
spring.rabbitmq.virtual-host=/
#埠號
spring.rabbitmq.port=5672
#配置釋出訊息確認回撥
spring.rabbitmq.publisher-confirms=true
複製程式碼

因為我們是本地測試專案,所以需要修改對應的埠號,防止埠被佔用。

建立使用者註冊消費者

複製rabbitmq-consumer子專案內的UserConsumer類到rabbitmq-consumer-node2子專案對應的package內,如下所示:

/**
 * 使用者註冊訊息消費者
 * 分散式節點2
 * ========================
 *
 * @author 恆宇少年
 * Created with IntelliJ IDEA.
 * Date:2017/11/26
 * Time:15:20
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@Component
@RabbitListener(queues = "user.register.queue")
public class UserConsumer {

    /**
     * logback
     */
    private Logger logger = LoggerFactory.getLogger(UserConsumer.class);

    @RabbitHandler
    public void execute(Long userId)
    {
        logger.info("使用者註冊消費者【節點2】獲取訊息,使用者編號:{}",userId);

        //...//自行業務邏輯處理
    }
}
複製程式碼

為了區分具體的消費者輸出內容,我們在上面UserConsumer消費者消費方法內列印了相關日誌輸出,下面我們同樣把rabbitmq-consumer子專案內UserConsumer的消費方法寫入相關日誌,如下所示:

@RabbitHandler
    public void execute(Long userId)
    {
        logger.info("使用者註冊消費者【節點1】獲取訊息,使用者編號:{}",userId);

        //...//自行業務邏輯處理
    }
複製程式碼

到目前為止我們的多節點RabbitMQ消費者已經編寫完成,下面我們來模擬多個使用者註冊的場景,來檢視使用者註冊訊息是否被轉發並唯一性的分配給不同的消費者節點。

執行測試

我們開啟上一章編寫的UserTester測試類,為了模擬多使用者註冊請求,我們對應的建立一個內部執行緒類BatchRabbitTester,線上程類內編寫註冊請求程式碼,如下所示:

 /**
     * 批量新增使用者執行緒測試類
     * run方法傳送使用者註冊請求
     */
    class BatchRabbitTester implements Runnable
    {
        private int index;
        public BatchRabbitTester() { }

        public BatchRabbitTester(int index) {
            this.index = index;
        }


        @Override
        public void run() {
            try {
                mockMvc.perform(MockMvcRequestBuilders.post("/user/save")
                        .param("userName","yuqiyu" + index)
                        .param("name","恆宇少年" + index)
                        .param("age","23")
                )
                        .andDo(MockMvcResultHandlers.log())
                        .andReturn();
            }catch (Exception e){
                e.printStackTrace();
            }

        }
    }
複製程式碼

為了區分每一個註冊資訊是否都已經寫入到資料庫,我們為BatchRabbitTester新增了一個有參的構造方法,將for迴圈的i值對應的傳遞為index的值。下面我們來編寫對應的批量註冊的測試方法,如下所示:

 /**
     * 測試使用者批量新增
     * @throws Exception
     */
    @Test
    public void testBatchUserAdd() throws Exception
    {
        for (int i = 0 ; i < 10 ; i++) {
            //建立使用者註冊執行緒
            Thread thread = new Thread(new BatchRabbitTester(i));
            //啟動執行緒
            thread.start();
        }
        //等待執行緒執行完成
        Thread.sleep(2000);
    }
複製程式碼

我們迴圈10次來測試使用者註冊請求,每一次都會建立一個執行緒去完成傳送註冊請求邏輯,在方法底部新增了sleep方法,目的是為了阻塞測試用例的結束,因為我們測試使用者完成方法後會自動停止,不會去等待其他執行緒執行完成,所以這裡我們阻塞測試主執行緒來完成傳送註冊執行緒請求邏輯。

執行批量註冊測試方法

我們在執行測試批量註冊使用者訊息之前,先把rabbitmq-consumerrabbitmq-consumer-node2兩個消費者子專案啟動,專案啟動完成後可以看到控制檯輸出啟動成功日誌,如下所示:

rabbitmq-consumer:
2017-12-10 17:10:36.961  INFO 15644 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 1111 (http)
2017-12-10 17:10:36.964  INFO 15644 --- [           main] c.h.r.c.RabbitmqConsumerApplication      : Started RabbitmqConsumerApplication in 2.405 seconds (JVM running for 3.39)
2017-12-10 17:10:36.964  INFO 15644 --- [           main] c.h.r.c.RabbitmqConsumerApplication      : 【【【【【訊息佇列-訊息消費者啟動成功.】】】】】

rabbitmq-consumer-node2:
2017-12-10 17:11:31.679  INFO 13812 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 1112 (http)
2017-12-10 17:11:31.682  INFO 13812 --- [           main] c.h.c.RabbitmqConsumerNode2Application   : Started RabbitmqConsumerNode2Application in 2.419 seconds (JVM running for 3.129)
2017-12-10 17:11:31.682  INFO 13812 --- [           main] c.h.c.RabbitmqConsumerNode2Application   : 【【【【【訊息佇列-訊息消費者節點2啟動成功.】】】】】

複製程式碼

接下來我們來執行testBatchUserAdd方法,檢視測試控制檯輸出內容如下所示:

2017-12-10 17:15:02.619  INFO 14456 --- [       Thread-3] o.s.a.r.c.CachingConnectionFactory       : Created new connection: rabbitConnectionFactory#528df369:0/SimpleConnection@39b6ba57 [delegate=amqp://guest@127.0.0.1:5672/, localPort= 60936]
 回撥id:194b5e67-6913-474a-b2ac-6e938e1e85e8
訊息傳送成功
 回撥id:e88ce59c-3eb9-433c-9e25-9429e7076fbe
訊息傳送成功
 回撥id:3e5b8382-6f63-450f-a641-e3d8eee255b2
訊息傳送成功
 回撥id:39103357-6c80-4561-acb7-79b32d6171c9
訊息傳送成功
 回撥id:9795d227-b54e-4cde-9993-a5b880fcfe39
訊息傳送成功
 回撥id:e9b8b828-f069-455f-a366-380bf10a5909
訊息傳送成功
 回撥id:6b5b4a9c-5e7f-4c53-9eef-98e06f8be867
訊息傳送成功
 回撥id:619a42f3-cb94-4434-9c75-1e28a04ce350
訊息傳送成功
 回撥id:6b720465-b64a-4ed9-9d8c-3e4dafa4faed
訊息傳送成功
 回撥id:b4296f7f-98cc-423b-a4ef-0fc31d22cb08
訊息傳送成功
複製程式碼

可以看到確實已經成功的傳送了10條使用者註冊訊息到RabbitMQ服務端,那麼是否已經正確的成功的將訊息轉發到消費者監聽方法了呢?我們來開啟rabbitmq-consumer子專案的啟動控制檯檢視日誌輸出內容如下所示:

2017-12-10 17:10:36.964  INFO 15644 --- [           main] c.h.r.c.RabbitmqConsumerApplication      : 【【【【【訊息佇列-訊息消費者啟動成功.】】】】】
2017-12-10 17:15:02.695  INFO 15644 --- [cTaskExecutor-1] c.h.rabbitmq.consumer.user.UserConsumer  : 使用者註冊消費者【節點1】獲取訊息,使用者編號:20
2017-12-10 17:15:02.718  INFO 15644 --- [cTaskExecutor-1] c.h.rabbitmq.consumer.user.UserConsumer  : 使用者註冊消費者【節點1】獲取訊息,使用者編號:22
2017-12-10 17:15:02.726  INFO 15644 --- [cTaskExecutor-1] c.h.rabbitmq.consumer.user.UserConsumer  : 使用者註冊消費者【節點1】獲取訊息,使用者編號:26
2017-12-10 17:15:02.729  INFO 15644 --- [cTaskExecutor-1] c.h.rabbitmq.consumer.user.UserConsumer  : 使用者註冊消費者【節點1】獲取訊息,使用者編號:21
2017-12-10 17:15:02.789  INFO 15644 --- [cTaskExecutor-1] c.h.rabbitmq.consumer.user.UserConsumer  : 使用者註冊消費者【節點1】獲取訊息,使用者編號:28
複製程式碼

可以看到成功的接受了5條對應使用者註冊訊息內容,不過這裡具體接受的條數並不是固定的,這也是RabbitMQ訊息轉發權重內部問題。 下面我們開啟rabbitmq-consumer-node2子專案控制檯檢視日誌輸出內容如下所示:

2017-12-10 17:11:31.682  INFO 13812 --- [           main] c.h.c.RabbitmqConsumerNode2Application   : 【【【【【訊息佇列-訊息消費者節點2啟動成功.】】】】】
2017-12-10 17:15:02.708  INFO 13812 --- [cTaskExecutor-1] com.hengyu.consumer.user.UserConsumer    : 使用者註冊消費者【節點2】獲取訊息,使用者編號:25
2017-12-10 17:15:02.717  INFO 13812 --- [cTaskExecutor-1] com.hengyu.consumer.user.UserConsumer    : 使用者註冊消費者【節點2】獲取訊息,使用者編號:23
2017-12-10 17:15:02.719  INFO 13812 --- [cTaskExecutor-1] com.hengyu.consumer.user.UserConsumer    : 使用者註冊消費者【節點2】獲取訊息,使用者編號:24
2017-12-10 17:15:02.727  INFO 13812 --- [cTaskExecutor-1] com.hengyu.consumer.user.UserConsumer    : 使用者註冊消費者【節點2】獲取訊息,使用者編號:27
2017-12-10 17:15:02.790  INFO 13812 --- [cTaskExecutor-1] com.hengyu.consumer.user.UserConsumer    : 使用者註冊消費者【節點2】獲取訊息,使用者編號:29
複製程式碼

同樣獲得了5條使用者註冊訊息,不過並沒有任何規律可言,編號也不是順序的。

所以多節點時訊息具體分發到哪個節點並不是固定的,完全是RabbitMQ分發機制來控制。

總結

本章完成了基於SpringBoot平臺整合RabbitMQ單個Provider對應繫結多個Consumer來進行多節點分散式消費者訊息消費,實際生產專案部署時完全可以將消費節點分開到不同的伺服器,只要消費節點可以訪問到RabbitMQ服務端,可以正常通訊,就可以完成訊息消費。

本章原始碼已經上傳到碼雲: SpringBoot配套原始碼地址:gitee.com/hengboy/spr… SpringCloud配套原始碼地址:gitee.com/hengboy/spr… SpringBoot相關係列文章請訪問:目錄:SpringBoot學習目錄 QueryDSL相關係列文章請訪問:QueryDSL通用查詢框架學習目錄 SpringDataJPA相關係列文章請訪問:目錄:SpringDataJPA學習目錄,感謝閱讀! 歡迎加入QQ技術交流群,共同進步。

QQ技術交流群

相關文章