一、安裝一個RabbitMq服務節點
1.1 安裝環境
系統環境:CentOS Linux release 7.7.1908 (Core)
Docker安裝版本:19.03.5
1.2 使用docker下載鏡像
# docker pull docker.io/rabbitmq:management
1.3 運行鏡像
# docker run -d --restart=always -p 15672:15672 --name shop-rabbitmq -e RABBITMQ_DEFAULT_USER=guest -e RABBITMQ_DEFAULT_PASS=guest -v /var/lib/rabbitmq:/var/lib/rabbitmq rabbitmq:management
參數說明:
-d ##後臺運行容器
--name ##指定容器名稱
-p ##暴露服務運行的端口(15672:控制檯Web端口號)
-e 指定環境變量(RABBITMQ_DEFAULT_USER:默認的用戶名;RABBITMQ_DEFAULT_PASS:默認用戶名的密碼)
rabbitmq配置文件在/etc/rabbitmq/rabbitmq.conf(這是容器內位置)
RabbitMQ默認端口:
4369 -- erlang發現端口
5672 --client端通信端口,應用訪問端口
15672 -- 管理界面ui端口,控制檯Web端口號
25672 -- server間內部通信端口
二、安裝RabbitMq集羣
2.1 安裝環境
系統環境:CentOS Linux release 7.7.1908 (Core)
Docker安裝版本:19.03.5
2.2 使用docker下載鏡像源
# docker pull rabbitmq:3.6.15-management
2.3 安裝RabbitMQ容器
# docker run -d --hostname rabbit1 --name myrabbit1 -p 15672:15672 -p 5672:5672 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:3.6.15-management
# docker run -d --hostname rabbit2 --name myrabbit2 -p 5673:5672 --link myrabbit1:rabbit1 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:3.6.15-management
# docker run -d --hostname rabbit3 --name myrabbit3 -p 5674:5672 --link myrabbit1:rabbit1 --link myrabbit2:rabbit2 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:3.6.15-management
具體的參數含義,參見上文“啓動RabbitMQ”部分。
注意點:
- 多個容器之間使用“--link”連接,此屬性不能少;
- Erlang Cookie值必須相同,也就是RABBITMQ_ERLANG_COOKIE參數的值必須相同,原因見下文“配置相同Erlang Cookie”部分;
2.4 加入RabbitMQ節點到集羣
2.4.1 設置節點1
[root@docker ~]# docker exec -it myrabbit1 bash
root@rabbit1:/# rabbitmqctl stop_app
Stopping rabbit application on node rabbit@rabbit1
root@rabbit1:/# rabbitmqctl reset
Resetting node rabbit@rabbit1
root@rabbit1:/# rabbitmqctl start_app
Starting node rabbit@rabbit1
root@rabbit1:/# exit
exit
2.4.2 設置節點2,並加入集羣
[root@docker ~]# docker exec -it myrabbit2 bash
root@rabbit2:/# rabbitmqctl stop_app
Stopping rabbit application on node rabbit@rabbit2
root@rabbit2:/# rabbitmqctl reset
Resetting node rabbit@rabbit2
root@rabbit2:/# rabbitmqctl join_cluster --ram rabbit@rabbit1
Clustering node rabbit@rabbit2 with rabbit@rabbit1
root@rabbit2:/# rabbitmqctl start_app
Starting node rabbit@rabbit2
root@rabbit2:/# exit
exit
參數“--ram”表示設置爲內存節點,忽略次參數默認爲磁盤節點。
2.4.3 設置節點3,加入到集羣:
[root@docker ~]# docker exec -it myrabbit3 bash
root@rabbit3:/# rabbitmqctl stop_app
Stopping rabbit application on node rabbit@rabbit3
root@rabbit3:/# rabbitmqctl reset
Resetting node rabbit@rabbit3
root@rabbit3:/# rabbitmqctl join_cluster --ram rabbit@rabbit1
Clustering node rabbit@rabbit3 with rabbit@rabbit1
root@rabbit3:/# rabbitmqctl start_app
Starting node rabbit@rabbit3
root@rabbit3:/# exit
exit
設置好之後,使用http://本機ip:15672 進行訪問了,默認賬號密碼是guest/guest,效果如下圖:
啓動了3個節點,1個磁盤節點和2個內存節點。
2.5 配置相同Erlang Cookie
有些特殊的情況,比如已經運行了一段時間的幾個單個物理機,我們在之前沒有設置過相同的Erlang Cookie值,現在我們要把單個的物理機部署成集羣,實現我們需要同步Erlang的Cookie值。
因爲RabbitMQ是用Erlang實現的,Erlang Cookie相當於不同節點之間相互通訊的祕鑰,Erlang節點通過交換Erlang Cookie獲得認證。
要想知道Erlang Cookie位置,首先要取得RabbitMQ啓動日誌裏面的home dir路徑,作爲根路徑。使用:“docker logs 容器名稱”查看,如下圖:
現將節點1的cookie值複製到本地:
[root@docker ~]# docker cp e03570e49ab75435da40a9b0f3d7a7ab54f3b09e291f79d5859403bf660dab26:/var/lib/rabbitmq/.erlang.cookie /root/.erlang.cookie
然後將本地的cookie值複製到節點2和3的服務器:
[root@docker ~]# docker cp /root/.erlang.cookie 36213791a6d6961d7ff1013428db7c40270a73feaa6115f4a3229d632e5245db:/var/lib/rabbitmq/.erlang.cookie
[root@docker ~]# docker cp /root/.erlang.cookie 659b56ce16e72e17b000b1d73670e3e8da0f0372854fbc735bc19b01f4d2907b:/var/lib/rabbitmq/.erlang.cookie
三、使用java連接RabbitMQ(直連型交換機)
3.1 使用docker運行RabbitMQ
[root@docker ~]# docker run -d --restart=always -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USER=guest -e RABBITMQ_DEFAULT_PASS=guest rabbitmq:management
注意點:
-d:表示後臺運行容器
--restart=always:表示隨着docker重啓服務
-p 15672:15672 -p 5672:5672:表示映射端口,以便其他電腦訪問
-e RABBITMQ_DEFAULT_USER=guest -e RABBITMQ_DEFAULT_PASS=guest:表示設置RabbitMQ的用戶名以及密碼
rabbitmq:management:表示運行的鏡像以及版本
3.2 使用springboot創建RabbitMq的生產者
3.2.1 創建rabbitmqprovider(生產者項目),項目目錄如下:
3.2.2 配置pom.xml文件以及application.yml文件
pom.xml文件配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
application.yml文件配置
server:
port: 8021
spring:
#給項目來個名字
application:
name: rabbitmq-provider
#配置rabbitMq 服務器
rabbitmq:
host: 192.168.183.146
port: 5672
username: guest
password: guest
#虛擬host 可以不設置,使用server默認host
virtual-host: /
注意點:virtual-host:配置的主機名一定要與控制檯的Admin配置相同,否則會出現An unexpected connection driver error occured的報錯:
3.2.3 創建DirectRabbitConfig類,創建direct exchange(直連型交換機)
package com.qhr.rabbitmqprovider.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author : qhr520
* @Date : 2020/1/9 9:13
* @Version : 01
* @Description :
* @ProjectName : rabbitmqprovider
*/
@Configuration
public class DirectRabbitConfig {
//隊列 起名:TestDirectQueue
@Bean
public Queue TestDirectQueue() {
return new Queue("TestDirectQueue",true); //true 是否持久
}
//Direct交換機 起名:TestDirectExchange
@Bean
DirectExchange TestDirectExchange() {
return new DirectExchange("TestDirectExchange");
}
//綁定 將隊列和交換機綁定, 並設置用於匹配鍵:TestDirectRouting
@Bean
Binding bindingDirect() {
return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");
}
}
3.2.4 創建SendMessageController類,用於消息推送
package com.qhr.rabbitmqprovider.controller;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @Author : qhr520
* @Date : 2020/1/9 9:16
* @Version : 01
* @Description :
* @ProjectName : rabbitmqprovider
*/
@RestController
public class SendMessageController {
@Autowired
RabbitTemplate rabbitTemplate; //使用RabbitTemplate,這提供了接收/發送等等方法
@GetMapping("/sendDirectMessage")
public String sendDirectMessage() {
String messageId = String.valueOf(UUID.randomUUID());
String messageData = "test message, hello!";
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String,Object> map=new HashMap<>();
map.put("messageId",messageId);
map.put("messageData",messageData);
map.put("createTime",createTime);
//將消息攜帶綁定鍵值:TestDirectRouting 發送到交換機TestDirectExchange
rabbitTemplate.convertAndSend("TestDirectExchange", "TestDirectRouting", map);
return "ok";
}
}
3.2.5 運行rabbitmqprovider項目
把rabbitmqprovider項目運行,調用下接口:
因爲我們目前還沒弄消費者 rabbitmqconsumer,消息沒有被消費的,我們去rabbitMq管理頁面看看,是否推送成功:
再看看隊列:
如圖所示,消息已經推送成功了.
3.3 創建rabbitmqconsumer(消費者)項目
3.3.1 創建rabbitmqconsumer(消費者)項目,項目目錄如下:
3.3.2 配置pom.xml文件以及application.yml文件
配置pom.xml文件
<!--rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
配置application.yml文件
server:
port: 8022
spring:
#給項目來個名字
application:
name: rabbitmq-consumer
#配置rabbitMq 服務器
rabbitmq:
host: 192.168.183.146
port: 5672
username: guest
password: guest
#虛擬host 可以不設置,使用server默認host
virtual-host: /
注意點:virtual-host:配置的主機名一定要與控制檯的Admin配置相同,否則會出現An unexpected connection driver error occured的報錯:
3.3.3 創建DirectRabbitConfig類
package com.qhr.rabbitmqconsumer.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author : qhr520
* @Date : 2020/1/9 9:13
* @Version : 01
* @Description :
* @ProjectName : rabbitmqconsumer
*/
@Configuration
public class DirectRabbitConfig {
//隊列 起名:TestDirectQueue
@Bean
public Queue TestDirectQueue() {
return new Queue("TestDirectQueue",true);
}
//Direct交換機 起名:TestDirectExchange
@Bean
DirectExchange TestDirectExchange() {
return new DirectExchange("TestDirectExchange");
}
//綁定 將隊列和交換機綁定, 並設置用於匹配鍵:TestDirectRouting
@Bean
Binding bindingDirect() {
return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");
}
}
3.3.4 創建DirectReceiver類,用於接收生產發送的消息
package com.qhr.rabbitmqconsumer.receiver;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @Author : qhr520
* @Date : 2020/1/9 10:52
* @Version : 01
* @Description :
* @ProjectName : rabbitmqconsumer
*/
@Component
@RabbitListener(queues = "TestDirectQueue")//監聽的隊列名稱 TestDirectQueue
public class DirectReceiver {
@RabbitHandler
public void process(Map testMessage) {
System.out.println("DirectReceiver消費者收到消息 : " + testMessage.toString());
}
}
3.3.5 運行rabbitmqconsumer項目
並且比較RabbitMq的控制檯頁面
之前rabbitmqprovider發送的信息,已經被接收,隊列裏已經沒有消息在等待處理了.然後可以再繼續調用rabbitmq-provider項目的推送消息接口,可以看到消費者即時消費消息:
四、配置主題交換機
4.1 配置生產者項目:
4.1.1 在rabbitmqprovider項目裏面創建TopicRabbitConfig.java:
package com.qhr.rabbitmqprovider.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
/**
* @Author : qhr520
* @Date : 2020/1/9 11:28
* @Version : 01
* @Description :
* @ProjectName : rabbitmqprovider
*/
public class TopicRabbitConfig {
//綁定鍵
public final static String man = "topic.man";
public final static String woman = "topic.woman";
@Bean
public Queue firstQueue() {
return new Queue(TopicRabbitConfig.man);
}
@Bean
public Queue secondQueue() {
return new Queue(TopicRabbitConfig.woman);
}
@Bean
TopicExchange exchange() {
return new TopicExchange("topicExchange");
}
//將firstQueue和topicExchange綁定,而且綁定的鍵值爲topic.man
//這樣只要是消息攜帶的路由鍵是topic.man,纔會分發到該隊列
@Bean
Binding bindingExchangeMessage() {
return BindingBuilder.bind(firstQueue()).to(exchange()).with(man);
}
//將secondQueue和topicExchange綁定,而且綁定的鍵值爲用上通配路由鍵規則topic.#
// 這樣只要是消息攜帶的路由鍵是以topic.開頭,都會分發到該隊列
@Bean
Binding bindingExchangeMessage2() {
return BindingBuilder.bind(secondQueue()).to(exchange()).with("topic.#");
}
}
4.1.2 然後添加多2個接口,用於推送消息到主題交換機:
@GetMapping("/sendTopicMessage1")
public String sendTopicMessage1() {
String messageId = String.valueOf(UUID.randomUUID());
String messageData = "message: M A N ";
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String, Object> manMap = new HashMap<>();
manMap.put("messageId", messageId);
manMap.put("messageData", messageData);
manMap.put("createTime", createTime);
rabbitTemplate.convertAndSend("topicExchange", "topic.man", manMap);
return "ok";
}
@GetMapping("/sendTopicMessage2")
public String sendTopicMessage2() {
String messageId = String.valueOf(UUID.randomUUID());
String messageData = "message: woman is all ";
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String, Object> womanMap = new HashMap<>();
womanMap.put("messageId", messageId);
womanMap.put("messageData", messageData);
womanMap.put("createTime", createTime);
rabbitTemplate.convertAndSend("topicExchange", "topic.woman", womanMap);
return "ok";
}
4.1.3 生產者配置完成的項目目錄:
配置完成,暫時不要運行項目。
4.2 配置消費者項目:
4.2.1 在rabbitmqconsumer項目上,創建TopicManReceiver.java:
package com.qhr.rabbitmqconsumer.receiver;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @Author : qhr520
* @Date : 2020/1/9 11:30
* @Version : 01
* @Description :
* @ProjectName : rabbitmqconsumer
*/
@Component
@RabbitListener(queues = "topic.man")
public class TopicManReceiver {
@RabbitHandler
public void process(Map testMessage) {
System.out.println("TopicManReceiver消費者收到消息 : " + testMessage.toString());
}
}
4.2.2 再創建一個TopicTotalReceiver.java:
package com.qhr.rabbitmqconsumer.receiver;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @Author : qhr520
* @Date : 2020/1/9 11:32
* @Version : 01
* @Description :
* @ProjectName : rabbitmqconsumer
*/
@Component
@RabbitListener(queues = "topic.woman")
public class TopicTotalReceiver {
@RabbitHandler
public void process(Map testMessage) {
System.out.println("TopicTotalReceiver消費者收到消息 : " + testMessage.toString());
}
}
4.2.3 同樣,加主題交換機的相關配置,TopicRabbitConfig.java:
package com.qhr.rabbitmqconsumer.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author : qhr520
* @Date : 2020/1/9 11:34
* @Version : 01
* @Description :
* @ProjectName : rabbitmqconsumer
*/
@Configuration
public class TopicRabbitConfig {
//綁定鍵
public final static String man = "topic.man";
public final static String woman = "topic.woman";
@Bean
public Queue firstQueue() {
return new Queue(TopicRabbitConfig.man);
}
@Bean
public Queue secondQueue() {
return new Queue(TopicRabbitConfig.woman);
}
@Bean
TopicExchange exchange() {
return new TopicExchange("topicExchange");
}
//將firstQueue和topicExchange綁定,而且綁定的鍵值爲topic.man
//這樣只要是消息攜帶的路由鍵是topic.man,纔會分發到該隊列
@Bean
Binding bindingExchangeMessage() {
return BindingBuilder.bind(firstQueue()).to(exchange()).with(man);
}
//將secondQueue和topicExchange綁定,而且綁定的鍵值爲用上通配路由鍵規則topic.#
// 這樣只要是消息攜帶的路由鍵是以topic.開頭,都會分發到該隊列
@Bean
Binding bindingExchangeMessage2() {
return BindingBuilder.bind(secondQueue()).to(exchange()).with("topic.#");
}
}
4.2.4 消費者配置完成的項目目錄
4.3 然後把rabbitmqprovider,rabbitmqconsumer兩個項目都跑起來
4.3.1 調用sendTopicMessage1接口:
然後看消費者rabbitmq-consumer的控制檯輸出情況:
TopicManReceiver監聽隊列1,綁定鍵爲:topic.man
TopicTotalReceiver監聽隊列2,綁定鍵爲:topic.#
而當前推送的消息,攜帶的路由鍵爲:topic.man
4.3.2 調用sendTopicMessage2接口
然後看消費者rabbitmq-consumer的控制檯輸出情況:
TopicManReceiver監聽隊列1,綁定鍵爲:topic.man
TopicTotalReceiver監聽隊列2,綁定鍵爲:topic.#
而當前推送的消息,攜帶的路由鍵爲:topic.woman
運行結果:
4.4 總結
主題交換機,這個交換機其實跟直連交換機流程差不多,但是它的特點就是在它的路由鍵和綁定鍵之間是有規則的。
簡單地介紹下規則:
* (星號) 用來表示一個單詞 (必須出現的)
# (井號) 用來表示任意數量(零個或多個)單詞
通配的綁定鍵是跟隊列進行綁定的,舉個小例子
隊列Q1 綁定鍵爲 *.TT.* 隊列Q2綁定鍵爲 TT.#
如果一條消息攜帶的路由鍵爲 A.TT.B,那麼隊列Q1將會收到;
如果一條消息攜帶的路由鍵爲TT.AA.BB,那麼隊列Q2將會收到;
五、Fanout Exchang 扇型交換機
5.1 配置生產者項目
5.1.1 先在rabbitmqprovIder項目上創建FanoutRabbitConfig.java:
package com.qhr.rabbitmqprovider.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author : qhr520
* @Date : 2020/1/9 14:32
* @Version : 01
* @Description :
* @ProjectName : rabbitmqprovider
*/
@Configuration
public class FanoutRabbitConfig {
/**
* 創建三個隊列 :fanout.A fanout.B fanout.C
* 將三個隊列都綁定在交換機 fanoutExchange 上
* 因爲是扇型交換機, 路由鍵無需配置,配置也不起作用
*/
@Bean
public Queue queueA() {
return new Queue("fanout.A");
}
@Bean
public Queue queueB() {
return new Queue("fanout.B");
}
@Bean
public Queue queueC() {
return new Queue("fanout.C");
}
@Bean
FanoutExchange fanoutExchange() {
return new FanoutExchange("fanoutExchange");
}
@Bean
Binding bindingExchangeA() {
return BindingBuilder.bind(queueA()).to(fanoutExchange());
}
@Bean
Binding bindingExchangeB() {
return BindingBuilder.bind(queueB()).to(fanoutExchange());
}
@Bean
Binding bindingExchangeC() {
return BindingBuilder.bind(queueC()).to(fanoutExchange());
}
}
5.1.2 在SendMessageController類中,添加一個接口用於推送消息
@GetMapping("/sendFanoutMessage")
public String sendFanoutMessage() {
String messageId = String.valueOf(UUID.randomUUID());
String messageData = "message: testFanoutMessage ";
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String, Object> map = new HashMap<>();
map.put("messageId", messageId);
map.put("messageData", messageData);
map.put("createTime", createTime);
rabbitTemplate.convertAndSend("fanoutExchange", null, map);
return "ok";
}
5.1.3 生產者配置完成的項目目錄:
5.2 配置消費者項目
5.2.1 在rabbitmqconsumer項目上創建FanoutReceiverA、FanoutReceiverB、FanoutReceiverC三個類
FanoutReceiverA類:
package com.qhr.rabbitmqconsumer.receiver;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @Author : qhr520
* @Date : 2020/1/9 14:45
* @Version : 01
* @Description :
* @ProjectName : rabbitmqconsumer
*/
@Component
@RabbitListener(queues = "fanout.A")
public class FanoutReceiverA {
@RabbitHandler
public void process(Map testMessage) {
System.out.println("FanoutReceiverA消費者收到消息 : " +testMessage.toString());
}
}
FanoutReceiverB類:
package com.qhr.rabbitmqconsumer.receiver;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @Author : qhr520
* @Date : 2020/1/9 14:47
* @Version : 01
* @Description :
* @ProjectName : rabbitmqconsumer
*/
@Component
@RabbitListener(queues = "fanout.B")
public class FanoutReceiverB {
@RabbitHandler
public void process(Map testMessage) {
System.out.println("FanoutReceiverB消費者收到消息 : " +testMessage.toString());
}
}
FanoutReceiverC類:
package com.qhr.rabbitmqconsumer.receiver;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @Author : qhr520
* @Date : 2020/1/9 14:54
* @Version : 01
* @Description :
* @ProjectName : rabbitmqconsumer
*/
@Component
@RabbitListener(queues = "fanout.C")
public class FanoutReceiverC {
@RabbitHandler
public void process(Map testMessage) {
System.out.println("FanoutReceiverC消費者收到消息 : " +testMessage.toString());
}
}
5.2.2 創建FanoutRabbitConfig類
package com.qhr.rabbitmqconsumer.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
/**
* @Author : qhr520
* @Date : 2020/1/9 14:59
* @Version : 01
* @Description :
* @ProjectName : rabbitmqconsumer
*/
public class FanoutRabbitConfig {
/**
* 創建三個隊列 :fanout.A fanout.B fanout.C
* 將三個隊列都綁定在交換機 fanoutExchange 上
* 因爲是扇型交換機, 路由鍵無需配置,配置也不起作用
*/
@Bean
public Queue queueA() {
return new Queue("fanout.A");
}
@Bean
public Queue queueB() {
return new Queue("fanout.B");
}
@Bean
public Queue queueC() {
return new Queue("fanout.C");
}
@Bean
FanoutExchange fanoutExchange() {
return new FanoutExchange("fanoutExchange");
}
@Bean
Binding bindingExchangeA() {
return BindingBuilder.bind(queueA()).to(fanoutExchange());
}
@Bean
Binding bindingExchangeB() {
return BindingBuilder.bind(queueB()).to(fanoutExchange());
}
@Bean
Binding bindingExchangeC() {
return BindingBuilder.bind(queueC()).to(fanoutExchange());
}
}
5.2.3 由於扇形交換機不需要傳遞鍵值,所以沒有自動加入隊列,我們必須手動在RabbitMQ網頁控制檯手動添加,如下圖所示
注:若無添加隊列會報Failed to declare queue(s):[XXX]錯誤
5.2.4 消費者配置完成項目目錄
5.3 然後把rabbitmqprovider,rabbitmqconsumer兩個項目都跑起來
調用sendFanoutMessage :
查看rabbitmqconsumer項目控制檯:
六、消息確認回調函數
6.1 在rabbitmq-provider項目的application.yml文件上,加上消息確認的配置項後:
server:
port: 8021
spring:
#給項目來個名字
application:
name: rabbitmq-provider
#配置rabbitMq 服務器
rabbitmq:
host: 192.168.183.146
port: 5672
username: test
password: qhr7788995201314.
#虛擬host 可以不設置,使用server默認host
virtual-host: demo
#消息確認配置項
#確認消息已發送到交換機(Exchange)
publisher-confirms: true
#確認消息已發送到隊列(Queue)
publisher-returns: true
6.2 配置相關的消息確認回調函數,RabbitConfig.java:
package com.qhr.rabbitmqprovider.config;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author : 邱杭銳
* @Date : 2020/1/9 15:52
* @Version : 01
* @Description :
* @ProjectName : rabbitmqprovider
*/
@Configuration
public class RabbitConfig {
@Bean
public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate = new RabbitTemplate();
rabbitTemplate.setConnectionFactory(connectionFactory);
//設置開啓Mandatory,才能觸發回調函數,無論消息推送結果怎麼樣都強制調用回調函數
rabbitTemplate.setMandatory(true);
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("ConfirmCallback: "+"相關數據:"+correlationData);
System.out.println("ConfirmCallback: "+"確認情況:"+ack);
System.out.println("ConfirmCallback: "+"原因:"+cause);
}
});
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("ReturnCallback: "+"消息:"+message);
System.out.println("ReturnCallback: "+"迴應碼:"+replyCode);
System.out.println("ReturnCallback: "+"迴應信息:"+replyText);
System.out.println("ReturnCallback: "+"交換機:"+exchange);
System.out.println("ReturnCallback: "+"路由鍵:"+routingKey);
}
});
return rabbitTemplate;
}
}
到這裏,生產者推送消息的消息確認調用回調函數已經完畢。
可以看到上面寫了兩個回調函數,一個叫 ConfirmCallback ,一個叫 RetrunCallback;
那麼以上這兩種回調函數都是在什麼情況會觸發呢?
先從總體的情況分析,推送消息存在四種情況:
①消息推送到server,但是在server裏找不到交換機
②消息推送到server,找到交換機了,但是沒找到隊列
③消息推送到sever,交換機和隊列啥都沒找到
④消息推送成功
那麼我先寫幾個接口來分別測試和認證下以上4種情況,消息確認觸發回調函數的情況:
6.2.1 消息推送到server,但是在server裏找不到交換機:
在SendMessageController中添加接口:
public String TestMessageAck() {
String messageId = String.valueOf(UUID.randomUUID());
String messageData = "message: non-existent-exchange test message ";
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String, Object> map = new HashMap<>();
map.put("messageId", messageId);
map.put("messageData", messageData);
map.put("createTime", createTime);
rabbitTemplate.convertAndSend("non-existent-exchange", "TestDirectRouting", map);
return "ok";
}
結果:
結論:
這種情況觸發的是 ConfirmCallback 回調函數。
6.2.2 消息推送到server,找到交換機了,但是沒找到隊列:
在DirectRabbitConfig類中添加:
@Bean
DirectExchange lonelyDirectExchange() {
return new DirectExchange("lonelyDirectExchange");
}
在SendMessageController中添加接口:
@GetMapping("/TestMessageAck2")
public String TestMessageAck2() {
String messageId = String.valueOf(UUID.randomUUID());
String messageData = "message: lonelyDirectExchange test message ";
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String, Object> map = new HashMap<>();
map.put("messageId", messageId);
map.put("messageData", messageData);
map.put("createTime", createTime);
rabbitTemplate.convertAndSend("lonelyDirectExchange", "TestDirectRouting", map);
return "ok";
}
結果:
結論:
這種情況觸發的是 ConfirmCallback和RetrunCallback兩個回調函數.
6.2.3 消息推送到sever,交換機和隊列啥都沒找到
在SendMessageController中添加接口:
@GetMapping("/TestMessageAck3")
public String TestMessageAck3() {
String messageId = String.valueOf(UUID.randomUUID());
String messageData = "message: non-existent-exchange test message ";
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String, Object> map = new HashMap<>();
map.put("messageId", messageId);
map.put("messageData", messageData);
map.put("createTime", createTime);
rabbitTemplate.convertAndSend("non-existent-exchange", "non-existent-routing", map);
return "ok";
}
結果:
結論:
這種情況與①一樣觸發的是 ConfirmCallback 回調函數。
6.2.4 消息推送成功
直接調用sendDirectMessage接口
結果:
結論:
這種情況觸發的是 ConfirmCallback 回調函數。
七、消息確認機制
7.1 三種模式
①自動確認, 這也是默認的消息確認情況。AcknowledgeMode.NONE
RabbitMQ成功將消息發出(即將消息成功寫入TCP Socket)中立即認爲本次投遞已經被正確處理,不管消費者端是否成功處理本次投遞。
所以這種情況如果消費端消費邏輯拋出異常,也就是消費端沒有處理成功這條消息,那麼就相當於丟失了消息。
一般這種情況我們都是使用try catch捕捉異常後,打印日誌用於追蹤數據,這樣找出對應數據再做後續處理。
② 不確認,這個不做介紹
③ 手動確認 ,這個比較關鍵,也是我們配置接收消息確認機制時,多數選擇的模式。
消費者收到消息後,手動調用basic.ack/basic.nack/basic.reject後,RabbitMQ收到這些消息後,才認爲本次投遞成功。
basic.ack用於肯定確認
basic.nack用於否定確認(注意:這是AMQP 0-9-1的RabbitMQ擴展)
basic.reject用於否定確認,但與basic.nack相比有一個限制:一次只能拒絕單條消息 消費者端以上的3個方法都表示消息已經被正確投遞,但是basic.ack表示消息已經被正確處理,但是basic.nack,basic.reject表示沒有被正確處理,但是RabbitMQ中仍然需要刪除這條消息。
7.2 在rabbitmqconsumer項目創建MessageListenerConfig.java
package com.qhr.rabbitmqconsumer.config;
import com.qhr.rabbitmqconsumer.receiver.DirectReceiver;
import com.qhr.rabbitmqconsumer.receiver.FanoutReceiverA;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author : qhr520
* @Date : 2020/1/9 16:29
* @Version : 01
* @Description :
* @ProjectName : rabbitmqconsumer
*/
@Configuration
public class MessageListenerConfig {
@Autowired
private CachingConnectionFactory connectionFactory;
@Autowired
private DirectReceiver directReceiver;//Direct消息接收處理類
//@Autowired
//FanoutReceiverA fanoutReceiverA;//Fanout消息接收處理類A
@Autowired
DirectRabbitConfig directRabbitConfig;
//@Autowired
//FanoutRabbitConfig fanoutRabbitConfig;
@Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
container.setConcurrentConsumers(1);
container.setMaxConcurrentConsumers(1);
container.setAcknowledgeMode(AcknowledgeMode.MANUAL); // RabbitMQ默認是自動確認,這裏改爲手動確認消息
container.setQueues(directRabbitConfig.TestDirectQueue());
container.setMessageListener(directReceiver);
//container.addQueues(fanoutRabbitConfig.queueA());
//container.setMessageListener(fanoutReceiverA);
return container;
}
}
7.3 修改DirectReceiver.java類
package com.qhr.rabbitmqconsumer.receiver;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* @Author : 邱杭銳
* @Date : 2020/1/9 10:52
* @Version : 01
* @Description :
* @ProjectName : rabbitmqconsumer
*/
@Component
@RabbitListener(queues = "TestDirectQueue")//監聽的隊列名稱 TestDirectQueue
public class DirectReceiver implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//因爲傳遞消息的時候用的map傳遞,所以將Map從Message內取出需要做些處理
String msg = message.toString();
String[] msgArray = msg.split("'");//可以點進Message裏面看源碼,單引號直接的數據就是我們的map消息數據
Map<String, String> msgMap = mapStringToMap(msgArray[1].trim());
String messageId=msgMap.get("messageId");
String messageData=msgMap.get("messageData");
String createTime=msgMap.get("createTime");
System.out.println("messageId:"+messageId+" messageData:"+messageData+" createTime:"+createTime);
channel.basicAck(deliveryTag, true);
// channel.basicReject(deliveryTag, true);//爲true會重新放回隊列
} catch (Exception e) {
channel.basicReject(deliveryTag, false);
e.printStackTrace();
}
}
//{key=value,key=value,key=value} 格式轉換成map
private Map<String, String> mapStringToMap(String str) {
str = str.substring(1, str.length() - 1);
String[] strs = str.split(",");
Map<String, String> map = new HashMap<String, String>();
for (String string : strs) {
String key = string.split("=")[0].trim();
String value = string.split("=")[1];
map.put(key, value);
}
return map;
}
}