SpringBoot整合RabbitMQ
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
开启注解
@EnableRabbit
配置文件rabbitMQ
spring.rabbitmq.host=192.168.56.10
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
配置config
@Configuration
public class MyRabbitConfig {
// 配置使用json的方式序列化对象
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
}
创建交换机
// 注入amqpAdmin
@Autowired
AmqpAdmin amqpAdmin;
// 创建交换机
// 交换机名称 是否持久化 是否自动删除 相关参数
// String name, boolean durable, boolean autoDelete, Map<String, Object> arguments
DirectExchange directExchange = new DirectExchange("hello-java-exchange", true, false, null);
amqpAdmin.declareExchange(directExchange);
创建队列
// 注入amqpAdmin
@Autowired
AmqpAdmin amqpAdmin;
// 创建队列
// 队列名称 是否持久化 是否只能连接一个交换机 是否自动删除 相关参数
// String name, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
Queue queue = new Queue("hello-java-queue", true, false, false, null);
amqpAdmin.declareQueue(queue);
绑定交换机和队列
// 注入amqpAdmin
@Autowired
AmqpAdmin amqpAdmin;
// 绑定交换机和队列
// 将exchange指定的交换机和destination目的地进行绑定(绑定队列也可以绑定交换机)使用routingKey作为指定的路由键
// String destination, Binding.DestinationType destinationType, String exchange, String routingKey, Map<String, Object> arguments
Binding binding = new Binding("hello-java-queue", Binding.DestinationType.QUEUE, "hello-java-exchange", "hello.java", null);
amqpAdmin.declareBinding(binding);
发送消息
// 发送消息
// 交换机 路由键 消息
// String exchange, String routingKey, Object object
rabbitTemplate.convertAndSend("hello-java-exchange", "hello.java", "hello world");
接收/监听消息
- 使用@RabbitListener,要想使用必须开启@EnableRabbit
// queues:要监听的队列数组
// 监听的内容会自动封装到方法的参数上
@RabbitListener(queues = {"hello-java-queue"})
public void receiveMessage(Object message) {
System.out.println("message: " + message.toString());
System.out.println("类型:" + message.getClass());
System.out.println("接收到消息...内容...");
}
// 接收监听到的消息
message: (Body:'{"id":1,"name":"哈哈","sort":null,"status":null,"createTime":1618122170115}' MessageProperties [headers={__TypeId__=com.atguigu.gulimall.order.entity.OrderReturnReasonEntity}, contentType=application/json, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=false, receivedExchange=hello-java-exchange, receivedRoutingKey=hello.java, deliveryTag=1, consumerTag=amq.ctag-0TUO4qdSSCcnsHUQ9XZyzg, consumerQueue=hello-java-queue])
类型:class org.springframework.amqp.core.Message
接收到消息...内容...
由监听的消息类型可以看出为class org.springframework.amqp.core.Message
类型,因此可以改造参数类型
- Message message:原生消息详细信息,头+体
- T<发送的消息的类型>:OrderReturnReasonEntity content
- Channel channel:当前传输数据的通道
@RabbitListener(queues = {"hello-java-queue"})
public void receiveMessage(Message message, ContentEntity content, Channel channel) {
// 获取消息体,即json的数据 {"id":1,"name":"哈哈","sort":null,"status":null,"createTime":1618122170115}
byte[] body = message.getBody();
// 获取消息头信息 即
// [headers={__TypeId__=com.atguigu.gulimall.order.entity.ContentEntity}, contentType=application/json, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=false, receivedExchange=hello-java-exchange, receivedRoutingKey=hello.java, deliveryTag=1, consumerTag=amq.ctag-0TUO4qdSSCcnsHUQ9XZyzg, consumerQueue=hello-java-queue])
MessageProperties messageProperties = message.getMessageProperties();
System.out.println("message: " + message.toString());
System.out.println("类型:" + message.getClass());
System.out.println("转化后消息...内容...");
System.out.println("content: " + content.toString());
}
Queue队列:可以很多人都来监听。只要收到消息,队列删除消息,并且只能同时用且只有一个人接收到此消息
- 同一个消息,只能被一个客户端收到
- 只有当前消息队列处理完成,才可以接收下一个消息队列
使用场景
-
@RabbitHandler: 标记方法
-
@RabbitListener:类+方法
使用@RabbitListener标记在类上(重载区分不同的消息),说明这个类就是用来接受消息队列的方法类,在该类下的所以方法上标记@RabbitHandler,每个方法指定不同的接收参数,这样就可以接收不同类型的消息
@RabbitListener
class receive {
@RabbitHandler(queues = {"hello-java-queue"})
public void receiveMessage1(Entity1 content) {
System.out.println("content1: " + content.toString());
}
@RabbitHandler(queues = {"hello-java-queue"})
public void receiveMessage2(Entity2 content) {
System.out.println("content2: " + content.toString());
}
}
RabbitMQ消息确认机制-可靠抵达
- 保证消息不丢失,可靠抵达,可以使用事务消息,性能下降250倍,为此引入确认机制
- publisher confirmCallback 确认模式(触发时机:服务端将消息发送给RabbitMQ所在的服务器)
- publisher returnCallback 未投递到queue退出模式(触发时机:RabbitmQ所在的服务器调用交换机投递给对应队列)
- consumer ack机制(触发机制:消费端成功获取到消息队列的消息)
可靠抵达-服务端确认(confirmCallback 、returnCallback )
- 开启发送端确认
# 开启发送端确认
spring.rabbitmq.publisher-confirms=true
- 开启发送端消息抵达队列的确认
# 开启发送端消息抵达队列的确认
spring.rabbitmq.publisher-returns=true
# 只要发送端消息抵达队列,以异步方式优先回调这个returnConfirm(绑定一起使用)
spring.rabbitmq.template.mandatory=true
- 定制RabbitTemplate自定义confirmCallback 、returnCallback 触发方法
/**
* 定制RabbitTemplate
* 1. MQ服务器收到消息就回调
* 1. spring.rabbitmq.publisher-confirms=true
* 2. 设置回调确认confirmCallback
* 2. 消息正确抵达队列进行回调
* 1. spring.rabbitmq.publisher-returns=true
* 2. spring.rabbitmq.template.mandatory=true
* 3. 设置回调确认returnCallback
*/
// PostConstruct: 当MyRabbitConfig对象创建完再执行该方法
@PostConstruct
public void initRabbitTemplate() {
// 设置MQ服务器收到消息回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* 只要消息抵达MQ服务器ack就为true
* @param correlationData:当前消息的唯一关联数据(这个是消息的唯一id)即发送时传的CorrelationData参数
* @param b:ack,消息是否成功还是失败
* @param s:失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
System.out.println("correlationData: " + correlationData);
System.out.println("ack: " + b);
System.out.println("s: " + s);
}
});
// 设置消息抵达队列回调
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
* 只要消息没有投递给指定的队列,就触发这个失败回调
* @param message:投递失败的消息详细信息
* @param i:回复的状态码
* @param s:回复的文本内容
* @param s1:当时这个消息发送给哪个交换机
* @param s2:当时这个消息发送给哪个路由键
*/
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
System.out.println("fail message: " + message);
System.out.println("i: " + i);
System.out.println("s: " + s);
System.out.println("s1: " + s1);
System.out.println("s2: " + s2);
}
});
}
可靠抵达-消费端确认(ack)
保证每个消息被正确消费,此时才可以MQ删除这个消息
- basic.ack 用于肯定确认;MQ服务器会移除此消息
- basic.nack用于否定确认;可以指定MQ服务器是否丢弃此消息,可以批量
- basic.reject用于否定确认;跟nack使用一样,但是不能批量
- 默认是自动ack,只要消息接收到,客户端会自动确认,服务端就会移除这个消息,如果客户端在处理消息时候宕机则会丢失消息,因此要手动确认,保证消息不丢失。当客户端宕机后,消息会从unacked状态变成ready状态,当下一次新的客户端连接进来再将消息重新发送给客户端
# 设置客户端手动确认接受到消息
spring.rabbitmq.listener.simple.acknowledge-mode=manual
@RabbitListener(queues = {"hello-java-queue"})
public void receiveMessage1(Message message, Content content, Channel channel) {
System.out.println("content1: " + content.toString());
// 通道内按顺序自增
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
// 确认消息接收成功,非批量签收模式
// long deliveryTag, boolean multipe (当前消息的标签,是否批量签收)
channel.basicAck(deliveryTag, false);
// 消息接收成功,但是拒绝签收消息
// long deliveryTag, boolean multipe, boolean requeue (当前消息的标签,是否批量签收,是否重新入队(false丢掉消息,true将消息重新入队))
channel.basicNack(deliveryTag,false,false);
} catch (IOException e) {
// 网络中断
}
}