SpringBoot 整合 RabbitMQ 实现消息可靠传输
消息的可靠传输是面试必问的问题之一,保证消息的可靠传输主要在生产端开启
comfirm模式,RabbitMQ开启持久化,消费端关闭自动ack模式。
环境配置
SpringBoot 整合 RabbitMQ 实现消息的发送。
- 添加
maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
- 添加 application.yml 配置文件
spring:
rabbitmq:
host: 192.168.3.19
port: 5672
username: admin
password: xxxx
- 配置交换机、队列以及绑定
@Bean
public DirectExchange myExchange() {
DirectExchange directExchange = new DirectExchange("myExchange");
return directExchange;
}
@Bean
public Queue myQueue() {
Queue queue = new Queue("myQueue");
return queue;
}
@Bean
public Binding binding() {
return BindingBuilder.bind(myQueue()).to(myExchange()).with("myRoutingKey");
}
- 生产发送消息
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/send")
public String send(String message) {
rabbitTemplate.convertAndSend("myExchange","myRoutingKey",message);
System.out.println("【发送消息】" + message)
return "【send message】" + message;
}
- 消费者接收消息
@RabbitListener(queuesToDeclare = @Queue("myQueue"))
public void process(String msg, Channel channel, Message message) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
String time = sdf.format(date);
System.out.println("【接收信息】" + msg + " 当前时间" + time);
- 调用生产端发送消息
hello,控制台输出:
【发送消息】hello
【接收信息】hello 当前时间2022-05-12 10:21:14
说明消息已经被成功接收。
消息丢失分析

一条消息的从生产到消费,消息丢失可能发生在以下几个阶段:
- 生产端丢失: 生产者无法传输到
RabbitMQ - 存储端丢失:
RabbitMQ存储自身挂了 - 消费端丢失:存储由于网络问题,无法发送到消费端,或者消费挂了,无法发送正常消费
RabbitMQ 从生产端、储存端、消费端都对可靠性传输做很好的支持。
生产阶段
生产阶段通过请求确认机制,来确保消息的可靠传输。当发送消息到 RabbitMQ 服务器 之后,RabbitMQ 收到消息之后,给发送返回一个请求确认,表示RabbitMQ 服务器已成功的接收到了消息。
- 配置
application.yml
spring:
rabbitmq:
# 消息确认机制 生产者 -> 交换机
publisher-confirms: true
# 消息返回机制 交换机 -> 队列
publisher-returns: true
配置
@Configuration
@Slf4j
public class RabbitConfig {
@Autowired
private ConnectionFactory connectionFactory;
@Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
log.info("【correlationData】:" + correlationData);
log.info("【ack】" + ack);
log.info("【cause】" + cause);
if (ack) {
log.info("【发送成功】");
} else {
log.info("【发送失败】correlationData:" + correlationData + " cause:" + cause);
}
}
});
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.warn("【消息发送失败】");
log.info("【message】" + message);
log.info("【replyCode】" + replyCode);
}
});
return rabbitTemplate;
}
}
消息从 生产者 到 交换机, 有confirmCallback 确认模式。发送消息成功后消息会调用方法confirm(CorrelationData correlationData, boolean ack, String cause),根据 ack 判断消息是否发送成功。
消息从 交换机 到 队列,有returnCallback 退回模式。
发送消息 product message 控制台输出如下:
【发送消息】product message
【接收信息】product message 当前时间2022-05-12 11:27:56
【correlationData】:null
【ack】true
【cause】null
【发送成功】
生产端模拟消息丢失
这里有两个方案:
- 发送消息后立马关闭 broke,后者把网络关闭,但是broker关闭之后控制台一直就会报错,发送消息也报500错误。
- 发送不存在的交换机:
// myExchange 修改成 myExchangexxxxx
rabbitTemplate.convertAndSend("myExchangexxxxx","myRoutingKey",message);
结果:
【correlationData】:null
【ack】false
【cause】channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'myExchangexxxxx' in vhost '/', class-id=60, method-id=40)
【发送失败】
当发送失败可以对消息进行重试
- 交换机正确,发送不存在的队列:
交换机接收到消息,返回成功通知,控制台输出:
【correlationData】:CorrelationData [id=7d468b47-b422-4523-b2a2-06b14aef073c]
【ack】true
【cause】null
【发送成功】
交换机没有找到队列,返回失败信息:
【消息发送失败】
【message】product message
【replyCode】312
RabbitMQ
开启队列持久化,创建的队列和交换机默认配置是持久化的。首先把队列和交换机设置正确,修改消费监听的队列,使得消息存放在队列里。
修改队列的持久化,修改成非持久化:
@Bean
public Queue myQueue() {
Queue queue = new Queue("myQueue",false);
return queue;
}
发送消息之后,消息存放在队列中,然后重启 RabbitMQ,消息不存在了。
设置队列持久化:
@Bean
public Queue myQueue() {
Queue queue = new Queue("myQueue",true);
return queue;
}
重启之后,队列的消息还存在。
消费端
消费端默认开始 ack 自动确认模式,当队列消息被消费者接收,不管有没有被消费端消息,都自动删除队列中的消息。所以为了确保消费端能成功消费消息,将自动模式改成手动确认模式:
修改 application.yml 文件
spring:
rabbitmq:
# 手动消息确认
listener:
simple:
acknowledge-mode: manual
消费接收消息之后需要手动确认:
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
@RabbitListener(queuesToDeclare = @Queue("myQueue"))
public void process(String msg, Channel channel, Message message) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
String time = sdf.format(date);
System.out.println("【接收信息】" + msg + " 当前时间" + time);
System.out.println(message.getMessageProperties().getDeliveryTag());
try {
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
} catch (IOException e) {
e.printStackTrace();
}
}
如果不添加:
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
发送两条消息
消息被接收后,没有确认,重新放到队列中:

重启项目,之后,队列的消息会发送到消费者,但是没有 ack 确认,还是继续会放回队列中。
加上 channel.basicAck 之后,再重启项目:

队列消息就被删除了
basicAck 方法最后一个参数 multiple 表示是删除之前的队列。
multiple 设置成 true,把后面的队列都清理掉了:

源码
https://github.com/jeremylai7/springboot-learning/tree/master/spring-rabbitmq
如果觉得文章对你有帮助的话,请点个推荐吧!
SpringBoot 整合 RabbitMQ 实现消息可靠传输的更多相关文章
- springboot整合rabbitmq,支持消息确认机制
安装 推荐一篇博客https://blog.csdn.net/zhuzhezhuzhe1/article/details/80464291 项目结构 POM.XML <?xml version= ...
- springboot整合rabbitMq实现消息延时发送
实现思路:利用mq的ttl设置消息失效时间 当达到设置时间后通过交换机到达死信队列中,消费者端绑定读取死信队列中信息来达到延时发送消息的功能. demo 如下: (1)在pom.xml 中引入rabb ...
- SpringBoot整合Rabbitmq设置消息请求头
String str = "{\"origin\":\"BBC\",\"origin_coupon_id\":51,\" ...
- SpringBoot系列八:SpringBoot整合消息服务(SpringBoot 整合 ActiveMQ、SpringBoot 整合 RabbitMQ、SpringBoot 整合 Kafka)
声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅. 1.概念:SpringBoot 整合消息服务 2.具体内容 对于异步消息组件在实际的应用之中会有两类: · JMS:代表作就是 ...
- springboot整合rabbitmq实现生产者消息确认、死信交换器、未路由到队列的消息
在上篇文章 springboot 整合 rabbitmq 中,我们实现了springboot 和rabbitmq的简单整合,这篇文章主要是对上篇文章功能的增强,主要完成如下功能. 需求: 生产者在启 ...
- RabbitMQ入门到进阶(Spring整合RabbitMQ&SpringBoot整合RabbitMQ)
1.MQ简介 MQ 全称为 Message Queue,是在消息的传输过程中保存消息的容器.多用于分布式系统 之间进行通信. 2.为什么要用 MQ 1.流量消峰 没使用MQ 使用了MQ 2.应用解耦 ...
- springboot学习笔记-6 springboot整合RabbitMQ
一 RabbitMQ的介绍 RabbitMQ是消息中间件的一种,消息中间件即分布式系统中完成消息的发送和接收的基础软件.这些软件有很多,包括ActiveMQ(apache公司的),RocketMQ(阿 ...
- 【SpringBoot系列5】SpringBoot整合RabbitMQ
前言: 因为项目需要用到RabbitMQ,前几天就看了看RabbitMQ的知识,记录下SpringBoot整合RabbitMQ的过程. 给出两个网址: RabbitMQ官方教程:http://www. ...
- 一篇学习完rabbitmq基础知识,springboot整合rabbitmq
一 rabbitmq 介绍 MQ全称为Message Queue,即消息队列, RabbitMQ是由erlang语言开发,基于AMQP(Advanced MessageQueue 高级消息队列协议 ...
随机推荐
- Python使用函数模拟“汉诺塔”过程
运行效果: 源代码: 1 # -*- coding:utf-8 -*- 2 ##汉诺塔游戏开始 3 _times=0 #用于统计移动次数 4 def hannuota(nlist,mfrom,mpas ...
- pip 和 Conda 镜像站配置
如果你经常使用 Python,那么你对 pip 和 Conda 一定不陌生,它们作为包管理器,可以非常方便的帮助我们下载需要的 Python 包,但是受限于大多 Python 包的服务器在国外,国内下 ...
- Java报错:Unable to find setter method for attribute: [x]
在学习JavaWeb JSTL与自定义标签时遇到的坑,用的老师给的代码结果直接原地报错:javax.servlet.ServletException: org.apache.jasper.Jasper ...
- MySQL启动过程详解二:核心模块启动 init_server_components()
mysqld_main() 函数中,init_server_components() 函数负责MySQL核心模块的启动,包括mdl系统,Innodb存储引擎的启动等等: 1. mdl子系统初始化. 2 ...
- 【FAQ】干货满满,接入HMS Core应用内支付服务过程中一些常见问题总结(2)来啦
HMS Core应用内支付服务(In-App Purchases,IAP)为应用提供便捷的应用内支付体验和简便的接入流程.该服务支持客户端和服务端两种开发形式,具体可以参考官方文档 上次,我们分享和总 ...
- 别像弱智一样提问 Stop-Ask-Questions-The-Stupid-Ways
https://github.com/xcr1234/Stop-Ask-Questions-The-Stupid-Ways 你真的准备好了吗? 感谢群友 for you 提供 避免 xy-proble ...
- 这3个免费PPT素材网站,一定要收藏
制作PPT,这三个网站的素材绝对够用! 1.象刀设计 https://www.101dao.com 象刀设计里面有非常多PPT模板,这个网站也是主打PPT素材. 分类很清晰,需要什么风格的素材能快速找 ...
- 《手把手教你》系列基础篇(九十二)-java+ selenium自动化测试-框架设计基础-POM设计模式简介(详解教程)
1.简介 页面对象模型(Page Object Model)在Selenium Webdriver自动化测试中使用非常流行和受欢迎,作为自动化测试工程师应该至少听说过POM这个概念.本篇介绍POM的简 ...
- SpringBoot 三层架构 Controller、Service、Dao作用和关系详解
首先创建一个springboot项目. model层 model层也叫pojo层或者entity层,个人比较喜欢pojo层. 一般数据库的一张表对应一个pojo层,并且表中所有字段都在pojo层都一一 ...
- linux创建磁盘阵例10
Linux创建RAID10 生产环境中用到的服务器一般都配备RAID阵列卡,尽管服务器的价格越来越便宜,但是我们没有必要为了做一个实验而去单独购买一台服务器,而是可以学会使用mdadm命令在Linux ...