使用RabbitMq的简单记录
小流程
导pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
改yml
spring:
rabbitmq:
host: 8.131.119.30
port: 5672
username: root
password: root
publisher-returns: true #开启队列接收确认回调
publisher-confirms: true #开启mq服务器接收确认回调
template:
mandatory: true #只要抵达队列,以异步发送优先回调returns
listener:
simple:
acknowledge-mode: manual #消费端开启手动确认(确认收货)
创建交换机、队列、绑定
1、通过AmqpAdmin创建
public String createExchange() {
/**
* 创建交换机
* String name:名称, boolean durable:是否持久化, boolean autoDelete:是否自动删除
*/
DirectExchange exchange = new DirectExchange("java-exchange",true,false);
amqpAdmin.declareExchange(exchange);
System.out.println("创建交换机");
return "";
}
public void createQueue() {
/**
* 创建队列
* String name:名称, boolean durable:持久化, boolean exclusive:排他性, boolean autoDelete:自动删除
*/
Queue queue = new Queue("java-queue",true,false,false);
amqpAdmin.declareQueue(queue);
System.out.println("创建队列");
}
public void createBinding() {
/**
* 创建绑定
* String destination:目的地, Binding.DestinationType destinationType:目的地类型,
* String exchange:交换机, String routingKey:路由键, @Nullable Map<String, Object> arguments:自定义参数
*/
Binding binding = new Binding("java-queue",
Binding.DestinationType.QUEUE,"java-exchange","java-test",null);
amqpAdmin.declareBinding(binding);
System.out.println("创建绑定");
}
2、通过加入IOC容器,Spring自动创建
直接注入bean的形式,让spring自动创建,若rabbitMq中没有则创建,有则不创建,并且rabbitmq中存在时,springbean形式创建的不能覆盖mq中的。
@Bean
public Exchange orderExchange() {
TopicExchange exchange = new TopicExchange("order-exchange",true,false);
return exchange;
}
@Bean
public Queue orderReleaseQueue() {
return new Queue("order.release.queue",
true, false, false);
}
@Bean
public Binding orderReleaseBinding() {
return new Binding("order.release.queue",
Binding.DestinationType.QUEUE,
"order-exchange",
"order.release.order",
null);
}
消息的生产与消费
以下均使用springboot自动装配好的RabbitTemplate
生产
/**
* 消息发送
* String exchange:交换机
* String routingKey:路由键
* Object object:传递的值(可以为对象)
* CorrelationData correlationData:唯一关联
*/
public void send() {
User user = new User();
user.setAge(22);
user.setName("name");
rabbitTemplate.convertAndSend("order-exchange",
"order.delay.order",
user,new CorrelationData(UUID.randomUUID().toString()));
}
消费
@RabbitListener设置监听、该注解使用时必须开启@EnableRabbit
/**
* 消息接收
* @RabbitListener作用:只要监听的队列中有消息,则会自动注入到参数中
* 消息返回的内容:Body+headers形式
* 也可以使用消息的类型直接接收,spring可以自动注入到类中
* Channel:当前连接的通道,消息传输通道
*
* Queue可以被很多客户端监听,客户端收到消息,队列中删除
* 1、一个消息只能被一个客户端收到,
* 2、一个消息完全处理完才能接收下一个消息
*
* @RabbitListener:可标注在类和方法上
* @RabbitHandler:可标注在方法上
*可联合使用@RabbitHandler(标注方法)+@RabbitListener(标注类)的形式
* 用@RabbitHandler标注在不同方法上,重载监听不同队列
*/
@RabbitListener(queues = {"java-queue"})
public void receive(Message msg, User user, Channel channel) {
//...
}
消息确认机制
生产端确认
- 开启confirms回调确认:spring.rabbitmq.publisher-confirms= true
- 当服务端Broker接收到消息后,会回调rabbitTemplate.setConfirmCallback方法
//设置发送端确认回调方法
rabbitTemplate.setConfirmCallback((v1,v2,v3)->{
/**
* 消息成功抵达mq中broker服务器则触发
* v1:当前消息的唯一关联数据(发送时的correlationData值)
* v2:消息是否成功收到
* v3:失败的原因
*/
System.out.println(v1+"-----"+v2+"---------"+v3);
});
队列抵达确认
- 开启returns回调:spring.rabbitmq.publisher-returns= true
- 配合spring.rabbitmq.template.mandatory: true (只要抵达队列,以异步发送优先回调returns)
- 当消息从交换机Exchange到队列Queue失败时,回调该方法
rabbitTemplate.setReturnCallback((msg,code,text,exchange,routKey)->{
/**
* 消息没有投递给指定队列则触发这个失败回调
* msg:失败消息的详细信息
* code:回复的状态码
* text:回复的文本内容
* exchang:消息发送给那个交换机
* routKey:消息的路由键
*/
System.out.println(msg+"\n"+code+"\n"+text+"\n"+exchange+"\n"+routKey);
});
消费端确认
开启手动ack
ack:acknowledge(告知已收到)
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual #消费端开启手动确认(确认收货)
在消费消息时,手动确认,避免消息丢失
/**
*3、消费端确认(保证每个消息被正确消费,broker才会删除该消息)
* 默认是自动确认的,消息被接收,客户端自动确认,服务端便会移除这个消息
* 问题:收到很多消息,自动回复给服务器ack,只有一个消息处理成功,宕机,发生了消息丢失
* 因此需要给客户端设置手动确认,处理完成在给服务端确认
* 手动确认模式:
* spring.rabbitmq.listener.simple.acknowledge-mode: manual #消费端开启手动确认(确认收货)
* channel.basicAck(deliveryTag,false);使用通道手动确认,消息处理完成后确认,服务端删除消息
*/
@RabbitListener(queues = {"java-queue"}) //该注解使用时必须开启@EnableRabbit
public void receive(Message msg, User user, Channel channel) {
long deliveryTag = msg.getMessageProperties().getDeliveryTag();//当前通道内自增数
try {
//手动ack(签收货物) 参数2:是否批量
channel.basicAck(deliveryTag,false);
} catch (IOException e) {
try {
//退货 v1:自增数 v2:是否批量 v3:重新入队 也可用channel.basicReject();
channel.basicNack(deliveryTag,false,true);
} catch (IOException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}
}
死信队列(延时队列)的简单使用
简单介绍
- 死信队列:DLX:dead-letter-exchange
- 利用DLX,当消息在一个队列中变成死信 (dead message)
之后,它能被重新publish到另一个Exchange,这个Exchange就是DLX
消息成为死信的几种情况
- 队列达到最大长度以后的消息
- 消息TTL过期
- 被消费端basicNack拒收,重新入队的消息
处理过程
- DLX也是一个正常的Exchange,和一般的Exchange没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性。
- 当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange上去,进而被路由到另一个队列。
- 可以监听这个队列中的消息做相应的处理。
简单使用
这里我们使用死信队列做延时策略,不监听死信队列,当死信队列中的消息超时后在去消费消息
@Bean
public Exchange orderExchange() {
TopicExchange exchange = new TopicExchange("order-exchange",true,false);
return exchange;
}
//死信队列、创建队列时,设置死信交换机,路由键,超时时间等
@Bean
public Queue orderDelayQueue() {
Map<String,Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange","order-exchange");
arguments.put("x-dead-letter-routing-key","order.release.order");
arguments.put("x-message-ttl",60000);
return new Queue("order.delay.queue",
true, false, false,arguments);
}
//普通队列
@Bean
public Queue orderReleaseQueue() {
return new Queue("order.release.queue",
true, false, false);
}
@Bean
public Binding orderDelayBinding() {
return new Binding("order.delay.queue",
Binding.DestinationType.QUEUE,
"order-exchange",
"order.delay.order",
null);
}
@Bean
public Binding orderReleaseBinding() {
return new Binding("order.release.queue",
Binding.DestinationType.QUEUE,
"order-exchange",
"order.release.order",
null);
}