Springboot整合Rabbitmq之Confirm和Return機(jī)制
前言
之前專欄中,對(duì)Springboot
整合Rabbitmq
都有一系列的配置和說(shuō)明,但總?cè)鄙僖恍┍匾拿枋鲂畔?。?dǎo)致很多看博客的小伙伴會(huì)私信問(wèn)為什么需要這么配置的問(wèn)題。
本篇博客重點(diǎn)進(jìn)行Confirm 機(jī)制
和Return 機(jī)制
的實(shí)現(xiàn)和說(shuō)明。
為什么會(huì)有Confirm
RabbitMq
中,針對(duì)數(shù)據(jù)由消息生產(chǎn)者
向消息隊(duì)列
推送時(shí),通常情況如下所示(以 Routing 方式為例):
每個(gè)Virtual Host 虛擬機(jī)
中,都會(huì)含有各自的Exchange
和Queue
,需要在rabbitmq web
界面中針對(duì)可以訪問(wèn)該Virtual Host 虛擬機(jī)
的用戶進(jìn)行設(shè)定。
有點(diǎn)類似數(shù)據(jù)庫(kù)的概念,指定用戶只能操作指定的數(shù)據(jù)庫(kù)。
在使用交換機(jī) Exchange
時(shí),消息生產(chǎn)者需要將消息通過(guò)Channel 管道
將數(shù)據(jù)發(fā)送給MQ
,但想過(guò)一個(gè)問(wèn)題沒(méi)有:
如何 確定 消息是否真的發(fā)送到了指定的 MQ 中呢?
MQ
中,對(duì)此問(wèn)題,提出有Confirm 機(jī)制
,對(duì)其發(fā)送數(shù)據(jù)進(jìn)行監(jiān)聽,讓消息發(fā)送者知道消息的發(fā)送結(jié)果。
Springboot 整合 Mq 實(shí)現(xiàn) Confirm 監(jiān)聽機(jī)制
依賴引入
開發(fā)測(cè)試主要的SpringBoot 版本為2.1.4.RELEASE
。
此時(shí)只需要引入指定的amqp
依賴即可:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
完整的pom依賴如下所示:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>springboot-rabbitmq</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- 引入rabbitmq依賴 --> <artifactId>spring-boot-starter-amqp</artifactId> <artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.20</version> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.26</version> <artifactId>slf4j-log4j12</artifactId> </dependencies> </project>
增加配置文件,設(shè)定連接信息
增加配置文件,配置使用具體的Virtual Host
、Username
、Password
、Host
、Port
等信息。
server: port: 80 spring: rabbitmq: host: xxxxxx port: 5672 username: xiangjiao password: bunana virtual-host: /xiangjiao publisher-confirms: true #消息發(fā)送到轉(zhuǎn)發(fā)器確認(rèn)機(jī)制,是都確認(rèn)回調(diào) publisher-returns: true
配置隊(duì)列、交換機(jī),以及對(duì)其進(jìn)行綁定
指定交換機(jī)名稱為:xiangjiao.exchange
。
隊(duì)列名稱為:xiangjiao.queue
。
使用Direct 直連
模式,其中關(guān)聯(lián)的Routingkey
為:xiangjiao.routingKey
。
package cn.linkpower.config; import org.springframework.amqp.core.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MQConfiguration { //隊(duì)列名稱 public static final String QUEUQ_NAME = "xiangjiao.queue"; //交換器名稱 public static final String EXCHANGE = "xiangjiao.exchange"; //路由key public static final String ROUTING_KEY = "xiangjiao.routingKey"; //創(chuàng)建隊(duì)列 @Bean public Queue getQueue(){ // 另一種方式 //QueueBuilder.durable(QUEUQ_NAME).build(); return new Queue(QUEUQ_NAME); } //實(shí)例化交換機(jī) @Bean public DirectExchange getDirectExchange(){ //DirectExchange(String name, boolean durable, boolean autoDelete) // 另一種方式: //ExchangeBuilder.directExchange(EXCHANGE).durable(true).build(); /** * 參數(shù)一:交換機(jī)名稱;<br> * 參數(shù)二:是否永久;<br> * 參數(shù)三:是否自動(dòng)刪除;<br> */ return new DirectExchange(EXCHANGE, true, false); //綁定消息隊(duì)列和交換機(jī) public Binding bindExchangeAndQueue(DirectExchange exchange,Queue queue){ // 將 創(chuàng)建的 queue 和 exchange 進(jìn)行綁定 return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY); }
編寫mq消息發(fā)送服務(wù)
在Springboot
中,針對(duì)MQ
消息的發(fā)送,采取RabbitTemplate
模板進(jìn)行數(shù)據(jù)的發(fā)送處理操作。
手動(dòng)定義消息發(fā)送處理類
,對(duì)其RabbitTemplate
進(jìn)行其他設(shè)置。
package cn.linkpower.service; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Slf4j @Component public class RabbitmqService implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback { @Autowired private RabbitTemplate rabbitTemplate; public void sendMessage(String exchange,String routingKey,Object msg) { // 設(shè)置交換機(jī)處理失敗消息的模式 true 表示消息由交換機(jī) 到達(dá)不了隊(duì)列時(shí),會(huì)將消息重新返回給生產(chǎn)者 // 如果不設(shè)置這個(gè)指令,則交換機(jī)向隊(duì)列推送消息失敗后,不會(huì)觸發(fā) setReturnCallback rabbitTemplate.setMandatory(true); //消息消費(fèi)者確認(rèn)收到消息后,手動(dòng)ack回執(zhí) rabbitTemplate.setConfirmCallback(this); // 暫時(shí)關(guān)閉 return 配置 //rabbitTemplate.setReturnCallback(this); //發(fā)送消息 rabbitTemplate.convertAndSend(exchange,routingKey,msg); } /** * 交換機(jī)并未將數(shù)據(jù)丟入指定的隊(duì)列中時(shí),觸發(fā) * channel.basicPublish(exchange_name,next.getKey(), true, properties,next.getValue().getBytes()); * 參數(shù)三:true 表示如果消息無(wú)法正常投遞,則return給生產(chǎn)者 ;false 表示直接丟棄 * @param message 消息對(duì)象 * @param replyCode 錯(cuò)誤碼 * @param replyText 錯(cuò)誤信息 * @param exchange 交換機(jī) * @param routingKey 路由鍵 */ @Override public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) { log.info("---- returnedMessage ----replyCode="+replyCode+" replyText="+replyText+" "); * 消息生產(chǎn)者發(fā)送消息至交換機(jī)時(shí)觸發(fā),用于判斷交換機(jī)是否成功收到消息 * @param correlationData 相關(guān)配置信息 * @param ack exchange 交換機(jī),判斷交換機(jī)是否成功收到消息 true 表示交換機(jī)收到 * @param cause 失敗原因 public void confirm(CorrelationData correlationData, boolean ack, String cause) { log.info("---- confirm ----ack="+ack+" cause="+String.valueOf(cause)); log.info("correlationData -->"+correlationData.toString()); if(ack){ // 交換機(jī)接收到 log.info("---- confirm ----ack==true cause="+cause); }else{ // 沒(méi)有接收到 log.info("---- confirm ----ack==false cause="+cause); } }
編寫消息發(fā)送接口
編寫一個(gè)Controller
,將產(chǎn)生的數(shù)據(jù),通過(guò)自定義的RabbitmqService
發(fā)送至指定的Exchange交換機(jī)
中。
package cn.linkpower.controller; import cn.linkpower.config.MQConfiguration; import cn.linkpower.service.RabbitmqService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class SendMessageTx { @Autowired private RabbitmqService rabbitmqService; @RequestMapping("/sendMoreMsgTx") @ResponseBody public String sendMoreMsgTx(){ //發(fā)送10條消息 for (int i = 0; i < 10; i++) { String msg = "msg"+i; System.out.println("發(fā)送消息 msg:"+msg); // xiangjiao.exchange 交換機(jī) // xiangjiao.routingKey 隊(duì)列 rabbitmqService.sendMessage(MQConfiguration.EXCHANGE, MQConfiguration.ROUTING_KEY, msg); //每?jī)擅氚l(fā)送一次 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } return "send ok"; } }
啟動(dòng)項(xiàng)目進(jìn)行測(cè)試
正常測(cè)試
http://localhost/sendMoreMsgTx
從控制臺(tái)中可以看到消息信息如下所示:
發(fā)現(xiàn),消息信息發(fā)送,都是ACK 被確認(rèn)
的!
異常測(cè)試
異常測(cè)試,首先需要保證mq服務(wù)中沒(méi)有對(duì)應(yīng)的exchange交換機(jī)。還需要保證消息的發(fā)送者exchange信息修改。
將controller中對(duì)應(yīng)的消息發(fā)送的方式修改如下:
rabbitmqService.sendMessage("xiangjiao.exchangeError", MQConfiguration.ROUTING_KEY, msg);
重啟項(xiàng)目,重新請(qǐng)求該接口,觀察控制臺(tái)數(shù)據(jù)信息展示:
截取其中的一條信息為例:
發(fā)送消息 msg:msg0
2022-02-28 10:34:58.686 ---- [rabbitConnectionFactory1] ---- INFO cn.linkpower.service.RabbitmqService - ---- confirm ----ack=false
cause=channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND -
no exchange 'xiangjiao.exchangeError' in vhost '/xiangjiao', class-id=60, method-id=40)
當(dāng)生產(chǎn)者
向Exchange
中發(fā)送消息,如果消息并未成功發(fā)送,則會(huì)觸發(fā)RabbitmqService
中設(shè)定的confirm
處理機(jī)制。
rabbitTemplate.setConfirmCallback(this); /** * 消息生產(chǎn)者發(fā)送消息至交換機(jī)時(shí)觸發(fā),用于判斷交換機(jī)是否成功收到消息 * @param correlationData 相關(guān)配置信息 * @param ack exchange 交換機(jī),判斷交換機(jī)是否成功收到消息 true 表示交換機(jī)收到 * @param cause 失敗原因 */ @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { log.info("---- confirm ----ack="+ack+" cause="+String.valueOf(cause)); log.info("correlationData -->"+correlationData.toString()); if(ack){ // 交換機(jī)接收到 log.info("---- confirm ----ack==true cause="+cause); }else{ // 沒(méi)有接收到 log.info("---- confirm ----ack==false cause="+cause); } }
什么是Return?
上面的配置中,采取Confirm機(jī)制
,能夠更好的保證消息生產(chǎn)者確認(rèn)消息是否正常到達(dá)Exchange中
。
但是,在MQ
中,由于使用Exchange
和Queue
進(jìn)行了綁定,
如果某個(gè)隊(duì)列宕機(jī)了,Exchange并
未將消息發(fā)送
匹配 Routing Key 的隊(duì)列,那么消息就不能到達(dá)隊(duì)列中?。?!
mq
中,對(duì)此情況設(shè)有另外一種監(jiān)聽機(jī)制:Return
機(jī)制!
當(dāng)消息
由Exchange 未能傳遞到匹配的 queue 中
,則會(huì)通過(guò)ReturnCallback
根據(jù)用戶的抉擇,判斷是否需要返回給消息生產(chǎn)者。
增加 ReturnCallback 監(jiān)聽并測(cè)試
修改 RabbitmqService 配置類
package cn.linkpower.service; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Slf4j @Component public class RabbitmqService implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback { @Autowired private RabbitTemplate rabbitTemplate; public void sendMessage(String exchange,String routingKey,Object msg) { // 設(shè)置交換機(jī)處理失敗消息的模式 true 表示消息由交換機(jī) 到達(dá)不了隊(duì)列時(shí),會(huì)將消息重新返回給生產(chǎn)者 // 如果不設(shè)置這個(gè)指令,則交換機(jī)向隊(duì)列推送消息失敗后,不會(huì)觸發(fā) setReturnCallback rabbitTemplate.setMandatory(true); //消息消費(fèi)者確認(rèn)收到消息后,手動(dòng)ack回執(zhí) rabbitTemplate.setConfirmCallback(this); // return 配置 rabbitTemplate.setReturnCallback(this); //發(fā)送消息 rabbitTemplate.convertAndSend(exchange,routingKey,msg); } /** * 交換機(jī)并未將數(shù)據(jù)丟入指定的隊(duì)列中時(shí),觸發(fā) * channel.basicPublish(exchange_name,next.getKey(), true, properties,next.getValue().getBytes()); * 參數(shù)三:true 表示如果消息無(wú)法正常投遞,則return給生產(chǎn)者 ;false 表示直接丟棄 * @param message 消息對(duì)象 * @param replyCode 錯(cuò)誤碼 * @param replyText 錯(cuò)誤信息 * @param exchange 交換機(jī) * @param routingKey 路由鍵 */ @Override public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) { log.info("---- returnedMessage ----replyCode="+replyCode+" replyText="+replyText+" "); } /** * 消息生產(chǎn)者發(fā)送消息至交換機(jī)時(shí)觸發(fā),用于判斷交換機(jī)是否成功收到消息 * @param correlationData 相關(guān)配置信息 * @param ack exchange 交換機(jī),判斷交換機(jī)是否成功收到消息 true 表示交換機(jī)收到 * @param cause 失敗原因 */ @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { log.info("---- confirm ----ack="+ack+" cause="+String.valueOf(cause)); log.info("correlationData -->"+correlationData.toString()); if(ack){ // 交換機(jī)接收到 log.info("---- confirm ----ack==true cause="+cause); }else{ // 沒(méi)有接收到 log.info("---- confirm ----ack==false cause="+cause); } } }
【注意:】設(shè)置 setReturnCallback 后,如果需要保證消息未傳遞到指定的 queue,需要將消息返回生產(chǎn)者時(shí),一定要增加下面配置:
// 設(shè)置交換機(jī)處理失敗消息的模式 true 表示消息由交換機(jī) 到達(dá)不了隊(duì)列時(shí),會(huì)將消息重新返回給生產(chǎn)者 // 如果不設(shè)置這個(gè)指令,則交換機(jī)向隊(duì)列推送消息失敗后,不會(huì)觸發(fā) setReturnCallback rabbitTemplate.setMandatory(true);
測(cè)試
修改對(duì)應(yīng)的測(cè)試類,保證交換機(jī)正確,但路由key不存在對(duì)應(yīng)的隊(duì)列即可。
// xiangjiao.routingKey 存在對(duì)應(yīng)的queue // xiangjiao.routingKey_error 不存在對(duì)應(yīng)的 queue rabbitmqService.sendMessage(MQConfiguration.EXCHANGE, "xiangjiao.routingKey_error", msg);
重啟項(xiàng)目,訪問(wèn)接口,進(jìn)行測(cè)試:
消息發(fā)送給
Exchange
成功,但是通過(guò)Exchange
向Queue
中推送數(shù)據(jù)時(shí) 失敗,經(jīng)過(guò)ReturnCallback 的 returnedMessage
捕獲監(jiān)聽!
總結(jié)
通過(guò)配置ConfirmCallback
和ReturnCallback
,便能實(shí)現(xiàn)消息生產(chǎn)者到交換機(jī)
和消息由exchange到queue
這個(gè)鏈路的安全性!
都是出現(xiàn)問(wèn)題,或者正常后,給
生產(chǎn)者方
進(jìn)行反饋。
相關(guān)代碼下載
到此這篇關(guān)于Springboot整合Rabbitmq之Confirm和Return詳解的文章就介紹到這了,更多相關(guān)Springboot整合Rabbitmq內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot實(shí)現(xiàn)rabbitmq的隊(duì)列初始化和綁定
這篇文章主要介紹了springboot實(shí)現(xiàn)rabbitmq的隊(duì)列初始化和綁定,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10Spring MVC--攔截器實(shí)現(xiàn)和用戶登陸例子
本文主要介紹了Spring MVC--攔截器實(shí)現(xiàn)和用戶登陸例子,具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-03-03解決SpringBoot配置文件application.yml遇到的坑
這篇文章主要介紹了解決SpringBoot配置文件application.yml遇到的坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02如何構(gòu)建可重復(fù)讀取inputStream的request
這篇文章主要介紹了如何構(gòu)建可重復(fù)讀取inputStream的request,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03解決spring中redistemplate不能用通配符keys查出相應(yīng)Key的問(wèn)題
這篇文章主要介紹了解決spring中redistemplate不能用通配符keys查出相應(yīng)Key的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11將java普通項(xiàng)目打包成exe可執(zhí)行文件的步驟記錄
將JAVA代碼打包為exe文件,會(huì)讓程序運(yùn)行更加方便,這篇文章主要給大家介紹了關(guān)于將java普通項(xiàng)目打包成exe可執(zhí)行文件的相關(guān)資料,需要的朋友可以參考下2021-07-07Java字符串拼接+和StringBuilder的比較與選擇
Java 提供了兩種主要的方式:使用 "+" 運(yùn)算符和使用 StringBuilder 類,本文主要介紹了Java字符串拼接+和StringBuilder的比較與選擇,感興趣的可以了解一下2023-10-10java數(shù)據(jù)結(jié)構(gòu)算法稀疏數(shù)組示例詳解
這篇文章主要為大家介紹了java數(shù)據(jù)結(jié)構(gòu)算法稀疏數(shù)組示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06