第四十二章: 基于SpringBoot & RabbitMQ完成DirectExchange分布式消息多消費(fèi)者消費(fèi)

在上一章第四十一章: 基于SpringBoot & RabbitMQ完成DirectExchange分布式消息消費(fèi)我們講解到了RabbitMQ消息隊(duì)列的DirectExchange路由鍵消息單個(gè)消費(fèi)者消費(fèi),源碼請(qǐng)?jiān)L問(wèn)SpringBoot對(duì)應(yīng)章節(jié)源碼下載查看,消息隊(duì)列目的是完成消息的分布式消費(fèi),那么我們是否可以為一個(gè)Provider創(chuàng)建并綁定多個(gè)Consumer呢?

免費(fèi)教程專題

恒宇少年在博客整理三套免費(fèi)學(xué)習(xí)教程專題,由于文章偏多特意添加了閱讀指南,新文章以及之前的文章都會(huì)在專題內(nèi)陸續(xù)填充,希望可以幫助大家解惑更多知識(shí)點(diǎn)。

本章目標(biāo)

基于SpringBoot平臺(tái)整合RabbitMQ消息隊(duì)列,完成一個(gè)Provider綁定多個(gè)Consumer進(jìn)行消息消費(fèi)。

SpringBoot 企業(yè)級(jí)核心技術(shù)學(xué)習(xí)專題


專題 專題名稱 專題描述
001 Spring Boot 核心技術(shù) 講解SpringBoot一些企業(yè)級(jí)層面的核心組件
002 Spring Boot 核心技術(shù)章節(jié)源碼 Spring Boot 核心技術(shù)簡(jiǎn)書每一篇文章碼云對(duì)應(yīng)源碼
003 Spring Cloud 核心技術(shù) 對(duì)Spring Cloud核心技術(shù)全面講解
004 Spring Cloud 核心技術(shù)章節(jié)源碼 Spring Cloud 核心技術(shù)簡(jiǎn)書每一篇文章對(duì)應(yīng)源碼
005 QueryDSL 核心技術(shù) 全面講解QueryDSL核心技術(shù)以及基于SpringBoot整合SpringDataJPA
006 SpringDataJPA 核心技術(shù) 全面講解SpringDataJPA核心技術(shù)
007 SpringBoot核心技術(shù)學(xué)習(xí)目錄 SpringBoot系統(tǒng)的學(xué)習(xí)目錄,敬請(qǐng)關(guān)注點(diǎn)贊!!!

構(gòu)建項(xiàng)目

我們基于上一章的項(xiàng)目進(jìn)行升級(jí),我們先來(lái)將Chapter41項(xiàng)目Copy一份命名為Chapter42。

構(gòu)建 rabbitmq-consumer-node2

基于我們復(fù)制的Chapter42項(xiàng)目,創(chuàng)建一個(gè)Module子項(xiàng)目命名為rabbitmq-consumer-node2,用于消費(fèi)者的第二個(gè)節(jié)點(diǎn),接下來(lái)我們?yōu)?code>rabbitmq-consumer-node2項(xiàng)目創(chuàng)建一個(gè)入口啟動(dòng)類RabbitmqConsumerNode2Application,代碼如下所示:

/**
 * 消息隊(duì)列消息消費(fèi)者節(jié)點(diǎn)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消費(fèi)者啟動(dòng)入口
     * @param args
     */
    public static void main(String[] args)
    {
        SpringApplication.run(RabbitmqConsumerNode2Application.class,args);

        logger.info("【【【【【消息隊(duì)列-消息消費(fèi)者節(jié)點(diǎn)2啟動(dòng)成功.】】】】】");
    }
}

為了區(qū)分具體的消費(fèi)者節(jié)點(diǎn),我們?cè)陧?xiàng)目啟動(dòng)成功后打印了相關(guān)的日志信息,下面我們來(lái)編寫application.properties配置文件信息,可以直接從rabbitmq-consumer子項(xiàng)目?jī)?nèi)復(fù)制內(nèi)容,復(fù)制后需要修改server.port以及spring.application.name,如下所示:

#端口號(hào)
server.port=1112
#項(xiàng)目名稱
spring.application.name=rabbitmq-consumer-node2


#rabbitmq相關(guān)配置
#用戶名
spring.rabbitmq.username=guest
#密碼
spring.rabbitmq.password=guest
#服務(wù)器ip
spring.rabbitmq.host=localhost
#虛擬空間地址
spring.rabbitmq.virtual-host=/
#端口號(hào)
spring.rabbitmq.port=5672
#配置發(fā)布消息確認(rèn)回調(diào)
spring.rabbitmq.publisher-confirms=true

因?yàn)槲覀兪潜镜販y(cè)試項(xiàng)目,所以需要修改對(duì)應(yīng)的端口號(hào),防止端口被占用。

創(chuàng)建用戶注冊(cè)消費(fèi)者

復(fù)制rabbitmq-consumer子項(xiàng)目?jī)?nèi)的UserConsumer類到rabbitmq-consumer-node2子項(xiàng)目對(duì)應(yīng)的package內(nèi),如下所示:

/**
 * 用戶注冊(cè)消息消費(fèi)者
 * 分布式節(jié)點(diǎn)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("用戶注冊(cè)消費(fèi)者【節(jié)點(diǎn)2】獲取消息,用戶編號(hào):{}",userId);

        //...//自行業(yè)務(wù)邏輯處理
    }
}

為了區(qū)分具體的消費(fèi)者輸出內(nèi)容,我們?cè)谏厦?code>UserConsumer消費(fèi)者消費(fèi)方法內(nèi)打印了相關(guān)日志輸出,下面我們同樣把rabbitmq-consumer子項(xiàng)目?jī)?nèi)UserConsumer的消費(fèi)方法寫入相關(guān)日志,如下所示:

@RabbitHandler
    public void execute(Long userId)
    {
        logger.info("用戶注冊(cè)消費(fèi)者【節(jié)點(diǎn)1】獲取消息,用戶編號(hào):{}",userId);

        //...//自行業(yè)務(wù)邏輯處理
    }

到目前為止我們的多節(jié)點(diǎn)RabbitMQ消費(fèi)者已經(jīng)編寫完成,下面我們來(lái)模擬多個(gè)用戶注冊(cè)的場(chǎng)景,來(lái)查看用戶注冊(cè)消息是否被轉(zhuǎn)發(fā)并唯一性的分配給不同的消費(fèi)者節(jié)點(diǎn)。

運(yùn)行測(cè)試

我們打開(kāi)上一章編寫的UserTester測(cè)試類,為了模擬多用戶注冊(cè)請(qǐng)求,我們對(duì)應(yīng)的創(chuàng)建一個(gè)內(nèi)部線程類BatchRabbitTester,在線程類內(nèi)編寫注冊(cè)請(qǐng)求代碼,如下所示:

 /**
     * 批量添加用戶線程測(cè)試類
     * run方法發(fā)送用戶注冊(cè)請(qǐng)求
     */
    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();
            }

        }
    }

為了區(qū)分每一個(gè)注冊(cè)信息是否都已經(jīng)寫入到數(shù)據(jù)庫(kù),我們?yōu)?code>BatchRabbitTester添加了一個(gè)有參的構(gòu)造方法,將for循環(huán)的i值對(duì)應(yīng)的傳遞為index的值。下面我們來(lái)編寫對(duì)應(yīng)的批量注冊(cè)的測(cè)試方法,如下所示:

 /**
     * 測(cè)試用戶批量添加
     * @throws Exception
     */
    @Test
    public void testBatchUserAdd() throws Exception
    {
        for (int i = 0 ; i < 10 ; i++) {
            //創(chuàng)建用戶注冊(cè)線程
            Thread thread = new Thread(new BatchRabbitTester(i));
            //啟動(dòng)線程
            thread.start();
        }
        //等待線程執(zhí)行完成
        Thread.sleep(2000);
    }

我們循環(huán)10次來(lái)測(cè)試用戶注冊(cè)請(qǐng)求,每一次都會(huì)創(chuàng)建一個(gè)線程去完成發(fā)送注冊(cè)請(qǐng)求邏輯,在方法底部添加了sleep方法,目的是為了阻塞測(cè)試用例的結(jié)束,因?yàn)槲覀儨y(cè)試用戶完成方法后會(huì)自動(dòng)停止,不會(huì)去等待其他線程執(zhí)行完成,所以這里我們阻塞測(cè)試主線程來(lái)完成發(fā)送注冊(cè)線程請(qǐng)求邏輯。

執(zhí)行批量注冊(cè)測(cè)試方法

我們?cè)趫?zhí)行測(cè)試批量注冊(cè)用戶消息之前,先把rabbitmq-consumer、rabbitmq-consumer-node2兩個(gè)消費(fèi)者子項(xiàng)目啟動(dòng),項(xiàng)目啟動(dòng)完成后可以看到控制臺(tái)輸出啟動(dòng)成功日志,如下所示:

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      : 【【【【【消息隊(duì)列-消息消費(fèi)者啟動(dòng)成功.】】】】】

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   : 【【【【【消息隊(duì)列-消息消費(fèi)者節(jié)點(diǎn)2啟動(dòng)成功.】】】】】

接下來(lái)我們來(lái)運(yùn)行testBatchUserAdd方法,查看測(cè)試控制臺(tái)輸出內(nèi)容如下所示:

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]
 回調(diào)id:194b5e67-6913-474a-b2ac-6e938e1e85e8
消息發(fā)送成功
 回調(diào)id:e88ce59c-3eb9-433c-9e25-9429e7076fbe
消息發(fā)送成功
 回調(diào)id:3e5b8382-6f63-450f-a641-e3d8eee255b2
消息發(fā)送成功
 回調(diào)id:39103357-6c80-4561-acb7-79b32d6171c9
消息發(fā)送成功
 回調(diào)id:9795d227-b54e-4cde-9993-a5b880fcfe39
消息發(fā)送成功
 回調(diào)id:e9b8b828-f069-455f-a366-380bf10a5909
消息發(fā)送成功
 回調(diào)id:6b5b4a9c-5e7f-4c53-9eef-98e06f8be867
消息發(fā)送成功
 回調(diào)id:619a42f3-cb94-4434-9c75-1e28a04ce350
消息發(fā)送成功
 回調(diào)id:6b720465-b64a-4ed9-9d8c-3e4dafa4faed
消息發(fā)送成功
 回調(diào)id:b4296f7f-98cc-423b-a4ef-0fc31d22cb08
消息發(fā)送成功

可以看到確實(shí)已經(jīng)成功的發(fā)送了10條用戶注冊(cè)消息到RabbitMQ服務(wù)端,那么是否已經(jīng)正確的成功的將消息轉(zhuǎn)發(fā)到消費(fèi)者監(jiān)聽(tīng)方法了呢?我們來(lái)打開(kāi)rabbitmq-consumer子項(xiàng)目的啟動(dòng)控制臺(tái)查看日志輸出內(nèi)容如下所示:

2017-12-10 17:10:36.964  INFO 15644 --- [           main] c.h.r.c.RabbitmqConsumerApplication      : 【【【【【消息隊(duì)列-消息消費(fèi)者啟動(dòng)成功.】】】】】
2017-12-10 17:15:02.695  INFO 15644 --- [cTaskExecutor-1] c.h.rabbitmq.consumer.user.UserConsumer  : 用戶注冊(cè)消費(fèi)者【節(jié)點(diǎn)1】獲取消息,用戶編號(hào):20
2017-12-10 17:15:02.718  INFO 15644 --- [cTaskExecutor-1] c.h.rabbitmq.consumer.user.UserConsumer  : 用戶注冊(cè)消費(fèi)者【節(jié)點(diǎn)1】獲取消息,用戶編號(hào):22
2017-12-10 17:15:02.726  INFO 15644 --- [cTaskExecutor-1] c.h.rabbitmq.consumer.user.UserConsumer  : 用戶注冊(cè)消費(fèi)者【節(jié)點(diǎn)1】獲取消息,用戶編號(hào):26
2017-12-10 17:15:02.729  INFO 15644 --- [cTaskExecutor-1] c.h.rabbitmq.consumer.user.UserConsumer  : 用戶注冊(cè)消費(fèi)者【節(jié)點(diǎn)1】獲取消息,用戶編號(hào):21
2017-12-10 17:15:02.789  INFO 15644 --- [cTaskExecutor-1] c.h.rabbitmq.consumer.user.UserConsumer  : 用戶注冊(cè)消費(fèi)者【節(jié)點(diǎn)1】獲取消息,用戶編號(hào):28

可以看到成功的接受了5條對(duì)應(yīng)用戶注冊(cè)消息內(nèi)容,不過(guò)這里具體接受的條數(shù)并不是固定的,這也是RabbitMQ消息轉(zhuǎn)發(fā)權(quán)重內(nèi)部問(wèn)題。
下面我們打開(kāi)rabbitmq-consumer-node2子項(xiàng)目控制臺(tái)查看日志輸出內(nèi)容如下所示:

2017-12-10 17:11:31.682  INFO 13812 --- [           main] c.h.c.RabbitmqConsumerNode2Application   : 【【【【【消息隊(duì)列-消息消費(fèi)者節(jié)點(diǎn)2啟動(dòng)成功.】】】】】
2017-12-10 17:15:02.708  INFO 13812 --- [cTaskExecutor-1] com.hengyu.consumer.user.UserConsumer    : 用戶注冊(cè)消費(fèi)者【節(jié)點(diǎn)2】獲取消息,用戶編號(hào):25
2017-12-10 17:15:02.717  INFO 13812 --- [cTaskExecutor-1] com.hengyu.consumer.user.UserConsumer    : 用戶注冊(cè)消費(fèi)者【節(jié)點(diǎn)2】獲取消息,用戶編號(hào):23
2017-12-10 17:15:02.719  INFO 13812 --- [cTaskExecutor-1] com.hengyu.consumer.user.UserConsumer    : 用戶注冊(cè)消費(fèi)者【節(jié)點(diǎn)2】獲取消息,用戶編號(hào):24
2017-12-10 17:15:02.727  INFO 13812 --- [cTaskExecutor-1] com.hengyu.consumer.user.UserConsumer    : 用戶注冊(cè)消費(fèi)者【節(jié)點(diǎn)2】獲取消息,用戶編號(hào):27
2017-12-10 17:15:02.790  INFO 13812 --- [cTaskExecutor-1] com.hengyu.consumer.user.UserConsumer    : 用戶注冊(cè)消費(fèi)者【節(jié)點(diǎn)2】獲取消息,用戶編號(hào):29

同樣獲得了5條用戶注冊(cè)消息,不過(guò)并沒(méi)有任何規(guī)律可言,編號(hào)也不是順序的。

所以多節(jié)點(diǎn)時(shí)消息具體分發(fā)到哪個(gè)節(jié)點(diǎn)并不是固定的,完全是RabbitMQ分發(fā)機(jī)制來(lái)控制。

總結(jié)

本章完成了基于SpringBoot平臺(tái)整合RabbitMQ單個(gè)Provider對(duì)應(yīng)綁定多個(gè)Consumer來(lái)進(jìn)行多節(jié)點(diǎn)分布式消費(fèi)者消息消費(fèi),實(shí)際生產(chǎn)項(xiàng)目部署時(shí)完全可以將消費(fèi)節(jié)點(diǎn)分開(kāi)到不同的服務(wù)器,只要消費(fèi)節(jié)點(diǎn)可以訪問(wèn)到RabbitMQ服務(wù)端,可以正常通訊,就可以完成消息消費(fèi)。

本章源碼已經(jīng)上傳到碼云:
SpringBoot配套源碼地址:https://gitee.com/hengboy/spring-boot-chapter
SpringCloud配套源碼地址:https://gitee.com/hengboy/spring-cloud-chapter

作者個(gè)人 博客
使用開(kāi)源框架 ApiBoot 助你成為Api接口服務(wù)架構(gòu)師

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容