RabbitMQ学习总结(3)-集成SpringBoot
1. pom.xml引用依赖
SpringBoot版本可以自由选择,我使用的是2.1.6.RELEASE,使用starter-web是因为要使用Spring的相关注解,所以要同时加上。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- RabbitMQ引用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
starter-amqp是RabbitMQ的主要依赖,这个包内都依赖了以下内容:
[INFO] +- org.springframework.boot:spring-boot-starter-amqp:jar:2.1.6.RELEASE:compile
[INFO] | +- org.springframework:spring-messaging:jar:5.1.8.RELEASE:compile
[INFO] | \- org.springframework.amqp:spring-rabbit:jar:2.1.7.RELEASE:compile
[INFO] | +- org.springframework.amqp:spring-amqp:jar:2.1.7.RELEASE:compile
[INFO] | | \- org.springframework.retry:spring-retry:jar:1.2.4.RELEASE:compile
[INFO] | +- com.rabbitmq:amqp-client:jar:5.4.3:compile
[INFO] | \- org.springframework:spring-tx:jar:5.1.8.RELEASE:compile
因为同时依赖了spring-rabbit.jar和amqp-client.jar,所以可以使用Spring方式继承,可以使用原始API调用MQ。下面我只讲继承Spring的方法。
2. 消息生产者代码
2.1 基础配置类(这里配置的交换机、队列、绑定关系,如果RabbitMQ中不存在会被创建。如果已存在但是和你配置的不一样会再启动时报错)
/**
* RabbitMQ的配置类
*/
@Configuration
public class RabbitMqConfig { /**
* 创建RabbitMQ的连接工厂
* @return ConnectionFactory
*/
@Bean
public ConnectionFactory connectionFactory(){
CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
// 虚拟地址,类型与MySQL的库,是相互独立的,可以在MQ管理后台创建
factory.setVirtualHost("myhosts");
//是否开启消息确认机制:消息确认、失败回调
factory.setPublisherConfirms(true);
return factory;
} /**
* 创建RabbitTemplate,用于访问RabbitMQ
* 如果有多个回调业务,要把此方法设置为非单例模式,在使用@PostConstruct在调用时注入ConfirmCallback方法
* @return RabbitTemplate
*/
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate template = new RabbitTemplate();
template.setConnectionFactory(connectionFactory);
// 1. 消息发送确认,这块处理的是消息到交换机之间的逻辑
template.setConfirmCallback(new MyConfirmCallback());
// 2. 开启失败回调,处理的是交换机到队列之间的逻辑
template.setMandatory(true);
template.setReturnCallback(new MyReturnCallback());
// 消息转换器,可以统一处理消息格式
template.setMessageConverter(new MyMessageConverter());
return template;
} /**
* 默认交换机
* @return DirectExchange
*/
@Bean
public DirectExchange defaultExchange() {
Map<String, Object> map = new HashMap<>();
// 声明备用交换机,只能在声明交换机时,才能声明备用交换机
// 如果被交换机没有被路由到,就会启用备用交换机
// 备用交换机需要提前创建
map.put("alternate-exchange", "exchangeTest");
// 参数1:交换机名称 参数2:是否持久化 参数3:是否自删除 参数4: 配置参数
return new DirectExchange("directExchange", false, false, map);
} /**
* 默认队列
* @return
*/
@Bean
public Queue queue() {
// 参数:队列名称,是否持久化,是否排他队列,是否自动删除,设置参数
return new Queue("testQueue", true, false, false, null);
} /**
* 绑定关系
* @return
*/
@Bean
public Binding binding() {
//绑定一个队列 to: 绑定到哪个交换机上面 with:绑定的路由建(routingKey)
return BindingBuilder.bind(queue()).to(defaultExchange()).with("direct.key");
} }
2.2 消息发送确认
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component; /**
* 消息发送回调方法(用于确保消息是否发送到交换机)
*/
@Component
public class MyConfirmCallback implements RabbitTemplate.ConfirmCallback { /**
* 消息发送确认
* @param correlationData SpringBoot提供的业务标识对象(建议在发送消息时,提供这个对象,方便失败时处理)
* @param ack 有没有成功发送到MQ
* @param cause 失败原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
// 按照业务处理具体业务逻辑,比如错误消息入库, 重新发送等。
System.out.println("Confirm===== data=" + correlationData.toString() + " ,ack=" + ack + ", cause="+cause);
} }
2.3 消息失败回调
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component; /**
* 失败回调(出现这个问题是消息没用从交换机进入到队列里)*/
@Component
public class MyReturnCallback implements RabbitTemplate.ReturnCallback { /**
* 返回消息:失败回调出现了的都是比较严重的问题,所以返回信息比较多(失败原因可能是路由键不存在,通道未绑定等等)
* @param message 发送消息 "hello" + 发送消息的配置
* @param replyCode 状态码:成功是200
* @param replyText 失败的信息
* @param exchange 交换机
* @param routingKey 路由键
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
StringBuffer buffer = new StringBuffer();
buffer.append("returnedMessage=======" + new String(message.getBody()));
buffer.append(", replyCode=" + replyCode);
buffer.append(", replyText=" + replyText);
buffer.append(", exchange=" + exchange);
buffer.append(",routingKey=" + routingKey);
System.out.println(buffer.toString());
}
}
2.5 消息格式化
因为RabbitMQ默认的消息格式是byte类型,Spring容器在消费者消费消息时,会自动把byte转成String类型,如果用String类型接收消息,可以直接输出。但如果是Message类型接收消息,就会显示乱码。所以要统一封装成Message。
import com.alibaba.fastjson.JSON;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.amqp.support.converter.MessageConverter; /**
* 消息格式化
*/
public class MyMessageConverter implements MessageConverter { /**
* 发送消息,要重写的方法,把Object转成Message
* @param o 消息内容
* @param messageProperties 消息参数
* @return Message
* @throws MessageConversionException
*/
@Override
public Message toMessage(Object o, MessageProperties messageProperties) throws MessageConversionException {
// 默认格式SpringBoot会转换一次,所以直接接受String输出的是字节码。设置text格式后,转换时会默认用UTF-8转码数据
messageProperties.setContentType("text/xml");
messageProperties.setContentEncoding("UTF-8");
byte[] bytes;
if (o instanceof String){
bytes = ((String) o).getBytes();
} else {
bytes = JSON.toJSONBytes(o);
}
Message message = new Message(bytes, messageProperties);
return message;
} /**
* 接收消息,要重写的方法,可以把Message转成成任意类型
* @param message 消息内容
* @return Object
* @throws MessageConversionException
*/
@Override
public Object fromMessage(Message message) throws MessageConversionException {
return null;
}
}
2.4 发送者代码
@Component
public class MessageProducer { @Autowired
RabbitTemplate rabbitTemplate; public void testSend() {
// 业务标识,发送失败时才有用。
CorrelationData data = new CorrelationData("order_no_1111");
// 参数1:交换机名字 参数2:路由键 参数3:消息内容
rabbitTemplate.convertAndSend("directExchange", "direct.key", "hello", data);
} }
使用这个类测试发送消息,消息发送成功的日志:Confirm===== data=CorrelationData [id=order_no_1111] ,ack=true, cause=null
把消息生产者的routingKey修改一个不存在,再次测试,就可以看到消息失败回调的日志。
消息失败回调的日志:returnedMessage=======hello, replyCode=312, replyText=NO_ROUTE, exchange=directExchange, routingKey=direct.key3
不论是消息发送确认失败,还是消息失败回调,都需要按照自己的业务逻辑把信息记录下来,再准备补偿机制处理。
3. 消息消费者代码
3.1 基础配置类
基础配置类与消息生产者代码是一样,只是RabbitTemplate方法中,不再需要消息确认、失败回调,删除即可。
3.2 监听器的容器工厂
这个配置类是可以直接定义消费者的一些行为。在@RabbitListener注解上使用。
/**
* 监听消费者行为
* @return SimpleRabbitListenerContainerFactory
*/
@Bean
public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
// 确认方式: NONE-不确认,MANUAL-手动确认, AUTO-自动确认
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
// 消息预期数量
factory.setPrefetchCount(1);
return factory;
}
3.3 消费者的消息监听器
使用@RabbitListener注解指定要监听的队列。如果有2个监听器监听一个队列,RabbitMQ默认会以轮训的方式把消息分发给两个监听器。(比如消费者的服务,部署了2台)
关于接受的消息格式问题,Listener支持两种2格式:String和Message
String类型:因为RabbitMQ默认的消息格式是byte,但使用String类型可以直接消息,是因为Spring给用户做了一次new String(byte[])的操作。
Message类型:如果直接使用Message类型接收参数,会出现数字乱码,因为消息内容已经被Spring把message转成String处理过一次。所以在发送消息时,使用MessageConverter格式化之后,message.getBody()得到的byte[] 在转成String类型即可。
/**
* 消息监听器
*
* 预取数量:(假设有两个消费者,100条消息,队列会按照轮训的规则,把所有消息轮训发给2个消费者,如果一个消费快,一个消费慢,就造成了消费快的服务一直很闲,消费慢的进程压力过大。)
* 现象就是队列已经清零了,消费慢的线程还在慢慢的消费剩余的消息
*
* @author he.zhang
* @date 2020/3/29 23:26
*/
@Component
public class MessageListener { /**
* 消费者,监听testQueue队列
* @param message 消息内容,此处接收的是Message对象,需要把body转码。也可以使用MessageConverter 来自动统一处理
* @throws Exception
*/
@RabbitListener(queues = "testQueue", containerFactory = "simpleRabbitListenerContainerFactory")
public void get(Message message, Channel channel) throws Exception {
System.out.println("消费者1:" + new String(message.getBody(), "UTF-8"));
// 消费预取数量,一次性拉取多少条数据。最多同时接收多少条数据。必须手动确认,不能自动确认。
// 可以和批量确认一起使用
channel.basicQos(10); // 假设消息是下订单的内容,下单成功的消息,手动确认
if (placeOrder()){
// 手动确认配置 参数1:消息唯一标识 参数2:是否批量确认
// 建议开通批量确认,唯一标识是MQ自动生成的,传最后一次确认的ID,会把之前的全都确认
// 开启批量确认需要提供个判断条件,并且要做好幂等性处理。(因为在未确认之前,连接中断的话,会造成重复消费的问题)
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
// 下单失败的消息,采用消息退回策略
else {
// 批量消息退回
// 参数1:消息标识
// 参数2:是否批量退回,如果想要单条退回就用false
// 参数3:是否回到消息队列,如果要废弃消息使用false,如果退回的可能会有重复消费可能,需要同时开通其他消费者处理。
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
// 单条消息退回,最早按照AMQP定义的接口,现在不经常用了
// channel.basicReject();
}
}/**
* 消费者2,监听testQueue队列,如果两个监听器同时监听一个通道,两个线程轮训消费消息
* @param message 消息内容,String类型,直接输出
* @throws Exception
*/
@RabbitListener(queues = "testQueue")
public void get2(String message) throws Exception {
System.out.println("消费者2:" + message);
} }
RabbitMQ学习总结(3)-集成SpringBoot的更多相关文章
- rabbitmq学习(七) —— springboot下的可靠使用
前面的学习都是基于原生的api,下面我们使用spingboot来整合rabbitmq springboot对rabbitmq提供了友好支持,极大的简化了开发流程 引入maven <depende ...
- RabbitMQ学习总结(4)-消息处理机制
1. 正常的消息流程 上面这张图,是一个正常的消息从生产到消息流程.在上一篇文章RabbitMQ学习总结(3)-集成SpringBoot中,代码里使用消息确认,消息回退机制,现在详细说一下. 2.1 ...
- CAS学习笔记五:SpringBoot自动/手动配置方式集成CAS单点登出
本文目标 基于SpringBoot + Maven 分别使用自动配置与手动配置过滤器方式实现CAS客户端登出及单点登出. 本文基于<CAS学习笔记三:SpringBoot自动/手动配置方式集成C ...
- Spring security OAuth2.0认证授权学习第四天(SpringBoot集成)
基础的授权其实只有两行代码就不单独写一个篇章了; 这两行就是上一章demo的权限判断; 集成SpringBoot SpringBoot介绍 这个篇章主要是讲SpringSecurity的,Spring ...
- RabbitMQ学习系列四-EasyNetQ文档跟进式学习与实践
EasyNetQ文档跟进式学习与实践 https://www.cnblogs.com/DjlNet/p/7603554.html 这里可能有人要问了,为什么不使用官方的nuget包呐:RabbitMQ ...
- RabbitMQ学习笔记五:RabbitMQ之优先级消息队列
RabbitMQ优先级队列注意点: 1.只有当消费者不足,不能及时进行消费的情况下,优先级队列才会生效 2.RabbitMQ3.5以后才支持优先级队列 代码在博客:RabbitMQ学习笔记三:Java ...
- RabbitMQ学习系列(四): 几种Exchange 模式
上一篇,讲了RabbitMQ的具体用法,可以看看这篇文章:RabbitMQ学习系列(三): C# 如何使用 RabbitMQ.今天说些理论的东西,Exchange 的几种模式. AMQP协议中的核心思 ...
- RabbitMQ学习系列(三): C# 如何使用 RabbitMQ
上一篇已经讲了Rabbitmq如何在Windows平台安装,还不了解如何安装的朋友,请看我前面几篇文章:RabbitMQ学习系列一:windows下安装RabbitMQ服务 , 今天就来聊聊 C# 实 ...
- RabbitMQ学习总结 第三篇:工作队列Work Queue
目录 RabbitMQ学习总结 第一篇:理论篇 RabbitMQ学习总结 第二篇:快速入门HelloWorld RabbitMQ学习总结 第三篇:工作队列Work Queue RabbitMQ学习总结 ...
随机推荐
- 「JLOI2015」城池攻占 可并堆
传送门 分析 如果直接暴力枚举的话肯定会超时 我们可以从下往上遍历,维护一个小根堆 每次到达一个节点把战败的骑士扔出去 剩下的再继续向上合并,注意要维护一下其实的战斗力 可以像线段树那样用一个lazy ...
- PowerJob 的故事开始:“玩够了,才有精力写开源啊!”
本文适合有 Java 基础知识的人群 作者:HelloGitHub-Salieri HelloGitHub 推出的<讲解开源项目>系列.经过几番的努力和沟通,终于邀请到分布式任务调度与计算 ...
- day28 封装
目录 一.什么是封装 二.将封装的属性进行隐藏操作 1 如何隐藏: 1.1 强行访问: 1.2 内部逻辑 三.为何要封装 一.什么是封装 封装是面向对象的三大特性中最核心的一个特性 封装<==& ...
- 午间邂逅 | post 和 get 的兄弟情深
前言 本文已经收录到我的 Github 个人博客,欢迎大佬们光临寒舍: 我的 Github 博客 学习导图: image-20200710142453734 image-202007101431049 ...
- 机器学习实战基础(二十三):sklearn中的降维算法PCA和SVD(四) PCA与SVD 之 PCA中的SVD
PCA中的SVD 1 PCA中的SVD哪里来? 细心的小伙伴可能注意到了,svd_solver是奇异值分解器的意思,为什么PCA算法下面会有有关奇异值分解的参数?不是两种算法么?我们之前曾经提到过,P ...
- Python之 爬虫(二十三)Scrapy分布式部署
按照上一篇文章中我们将代码放到远程主机是通过拷贝或者git的方式,但是如果考虑到我们又多台远程主机的情况,这种方式就比较麻烦,那有没有好用的方法呢?这里其实可以通过scrapyd,下面是这个scrap ...
- Java实现导入导出Excel:POI和EasyExcel
文章与CSDN同步,欢迎访问:https://blog.csdn.net/qq_40280582/article/details/107300081 代码地址:https://gitee.com/il ...
- 响应式布局rem、rem方法封装、移动端响应式布局
相信大家在做移动端的时候都会做各个手机的适配这种适配就是响应式布局在之前做网站的响应式从pc到手机用的是媒体查询 @media screen and (max-width: 300px){} 最大宽度 ...
- 网易实战分享|云信IM SDK接口设计实践
引语 IM (Instant Messaging)是网络上最流行的通信方式,与日常生活息息相关.IM软件也层出不穷,例如:微信.QQ.易信等.通过多年深耕和技术沉淀,云信产出了一套成熟稳定的IM SD ...
- bzoj2295【POJ Challenge】我爱你啊*
bzoj2295[POJ Challenge]我爱你啊 题意: 求一个字符串中有多少个"luvletter"(不包括引号).字符串长度≤100000. 题解: 连kmp都不用…… ...