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的更多相关文章

  1. rabbitmq学习(七) —— springboot下的可靠使用

    前面的学习都是基于原生的api,下面我们使用spingboot来整合rabbitmq springboot对rabbitmq提供了友好支持,极大的简化了开发流程 引入maven <depende ...

  2. RabbitMQ学习总结(4)-消息处理机制

    1. 正常的消息流程 上面这张图,是一个正常的消息从生产到消息流程.在上一篇文章RabbitMQ学习总结(3)-集成SpringBoot中,代码里使用消息确认,消息回退机制,现在详细说一下. 2.1 ...

  3. CAS学习笔记五:SpringBoot自动/手动配置方式集成CAS单点登出

    本文目标 基于SpringBoot + Maven 分别使用自动配置与手动配置过滤器方式实现CAS客户端登出及单点登出. 本文基于<CAS学习笔记三:SpringBoot自动/手动配置方式集成C ...

  4. Spring security OAuth2.0认证授权学习第四天(SpringBoot集成)

    基础的授权其实只有两行代码就不单独写一个篇章了; 这两行就是上一章demo的权限判断; 集成SpringBoot SpringBoot介绍 这个篇章主要是讲SpringSecurity的,Spring ...

  5. RabbitMQ学习系列四-EasyNetQ文档跟进式学习与实践

    EasyNetQ文档跟进式学习与实践 https://www.cnblogs.com/DjlNet/p/7603554.html 这里可能有人要问了,为什么不使用官方的nuget包呐:RabbitMQ ...

  6. RabbitMQ学习笔记五:RabbitMQ之优先级消息队列

    RabbitMQ优先级队列注意点: 1.只有当消费者不足,不能及时进行消费的情况下,优先级队列才会生效 2.RabbitMQ3.5以后才支持优先级队列 代码在博客:RabbitMQ学习笔记三:Java ...

  7. RabbitMQ学习系列(四): 几种Exchange 模式

    上一篇,讲了RabbitMQ的具体用法,可以看看这篇文章:RabbitMQ学习系列(三): C# 如何使用 RabbitMQ.今天说些理论的东西,Exchange 的几种模式. AMQP协议中的核心思 ...

  8. RabbitMQ学习系列(三): C# 如何使用 RabbitMQ

    上一篇已经讲了Rabbitmq如何在Windows平台安装,还不了解如何安装的朋友,请看我前面几篇文章:RabbitMQ学习系列一:windows下安装RabbitMQ服务 , 今天就来聊聊 C# 实 ...

  9. RabbitMQ学习总结 第三篇:工作队列Work Queue

    目录 RabbitMQ学习总结 第一篇:理论篇 RabbitMQ学习总结 第二篇:快速入门HelloWorld RabbitMQ学习总结 第三篇:工作队列Work Queue RabbitMQ学习总结 ...

随机推荐

  1. JavaScript图片预览

    预览选中的图片文件 jQuery $("#selectImage").change(function(){ $("#image").attr("src ...

  2. 二分查找&二叉排序树

    首先我们先来复习一下二分查找的算法 对于正向序列的二分查找 递归实现: bool binary_search(vector<int> &sort_arry,int begin,in ...

  3. day04总结

    print("陈少最帅!!!") 输出结果: 陈少最帅!!! 可以变,不可变数据类型#1.可变类型:list,dict#在值改变的情况下,id号不变,也就是说内存地址不变,证明就是 ...

  4. day07 流程控制

    灵魂三问: 什么是?为什么要有?怎么用? 目录 一 分支结构 1.1 什么是分支结构 1.2 为什么要有分支结构 1.3 怎么用分支结构 1.3.1 if语法 二 循环结构 2.1while循环 一 ...

  5. scala 数据结构(七 ):集 Set

    集是不重复元素的结合.集不保留顺序,默认是以哈希集实现 默认情况下,Scala 使用的是不可变集合,如果你想使用可变集合,需要引用 scala.collection.mutable.Set 包 1 集 ...

  6. redis(二):Redis 命令

    Redis 命令用于在 redis 服务上执行操作. 要在 redis 服务上执行命令需要一个 redis 客户端.Redis 客户端在我们之前下载的的 redis 的安装包中. 语法 Redis 客 ...

  7. 将python3打包成为exe可执行文件(pyinstaller)

    我们工作中可能会遇到,客户需要一个爬虫或者其他什么功能的python脚本. 这个时候,如果我们直接把我们的python脚本发给客户,会有两个问题: 1.客户的电脑或者服务器可能并没有安装python环 ...

  8. js 或Jquery操作定位元素

    属性过滤常用javascript后去DOM对象 id是定位到的是单个element元素对象,其它的都是elements返回的是list对象 1.通过id获取 document.getElementBy ...

  9. VMware虚拟机网络配置详解

    VMware网络配置:三种网络模式简介 安装好虚拟机以后,在网络连接里面可以看到多了两块网卡: 其中VMnet1是虚拟机Host-only模式的网络接口,VMnet8是NAT模式的网络接口,这些后面会 ...

  10. v-bind v-on 缩写

    Vue.js 为两个最为常用的指令提供了特别的缩写: