在上一章第四十一章: 基于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