死信队列
什么是死信
死信队列,英文缩写DLX。Dead Letter Exchange(死信交换机),当消息成为Dead message(死信信息)后,可以被重新发送到另一个交换机上,这就是DLX
消息成为死信的三种情况
1.队列消息长度到达限制;
2.消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
3.原队列存在消息过期设置,消息到达超时时间未被消费;
使用死信
队列绑定死信交换机:
给队列设置参数: x-dead-letter-exchange 和 x-dead-letter-routing-key
在idea中使用配置文件创建
在配置类中编写代码,确保程序启动配置类中的实例对象可以被加载
@Configuration
public class ExchangeAndQueue {
private final String QUEUE="queue";//普通队列
private final String DEAD_QUEUE="dead_queue";//死信队列
private final String EXCHANGE="exchange";//普通交换机
private final String DEAD_EXCHANGE="dead_exchange";//死信交换机
//java代码创建普通的队列
@Bean
public Queue queue(){
return QueueBuilder
.durable(QUEUE)//持久化队列(参数为队列名称)
.withArgument("x-message-ttl",20000)//设置超时时间
.withArgument("x-max-length",10)//最大个数
.withArgument("x-dead-letter-exchange",DEAD_EXCHANGE)//设置死信队列
.withArgument("x-dead-letter-routing-key","error")//设置死信路由key
.build();//创建
}
//创建死信队列
@Bean
public Queue dead_queue() {
/**
* durable(DEAD_QUEUE)持久化队列(参数为队列名称)
* build()创建
*/
return QueueBuilder.durable(DEAD_QUEUE).build();
}
//创建普通交换机
@Bean
public Exchange exchange(){
/**
* directExchange(EXCHANGE)交换机类型(交换机名称)
* build()创建
*/
return ExchangeBuilder.directExchange(EXCHANGE).build();
}
//创建死信交换机
@Bean
public Exchange dead_exchange(){
/**
* directExchange(DEAD_EXCHANGE)交换机类型(交换机名称)
* build()创建
*/
return ExchangeBuilder.directExchange(DEAD_EXCHANGE).build();
}
//绑定普通队列和普通1交换机
@Bean
public Binding binding(){
/**
* bind(queue())要绑定的队列对象
* to(exchange())绑定到的交换机对象
* with("error")路由key
* noargs();没有额外参数
* @return
*/
return BindingBuilder.bind(queue()).to(exchange()).with("error").noargs();
}
//绑定死信队列和死信交换机
@Bean
public Binding dead_binding(){
/**
* bind(dead_queue())要绑定的队列对象
* to(dead_exchange())绑定到的交换机对象
* with("error")路由key
* noargs();没有额外参数
* @return
*/
return BindingBuilder.bind(dead_queue()).to(dead_exchange()).with("error").noargs();
}
}
在可视化web创建
1.创建普通交换机
2.创建死信交换机
3.创建死信队列
4.创建普通队列
5.绑定队列和交换机
挨个绑定普通队列、交换机和死信队列、交换机
延迟队列
普通队列中设置失效时间,当普通队列中的信息超出时间未被消费时,就会进入死信队列中,
案例:超出30分钟未支付就删除订单并且让库存回滚
消息幂等性保障
幂等性: 无论执行多少次,得到的结果和第一次都是相同的。 LOL中的英雄大招技能,在冷却好了到放过等待大招cd过程中,只有一次点击有效。这就是幂等性
防止在消费者进行回应时因为一系列问题未响应给队列,从而不删除此信息,导致再次消费,保证了消息不被重复消费。
实现步骤
这里使用redis完成
1.开启redis
2.引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
3.配置application.yml文件
#配置rabbitmq的IP
spring:
rabbitmq:
host: 192.168.11.222
#开启手动确认
listener:
simple:
acknowledge-mode: manual
#配置redis的端口和IP
redis:
host: 192.168.11.222
port: 6379
server:
port: 8002
4.代码实现
在配置类中写
@Component
public class MyListener {
//注入redisTemplate用来操作redis数据库
@Autowired
private RedisTemplate redisTemplate;
//@RabbitListener(queues = "queue")监听rabbitmq的那个队列
@RabbitListener(queues = "queue")
/**
*Message msg 要消费的信息
*Channel channel 信道对象
*/
public void listener(Message msg, Channel channel) throws Exception{
//先连接redis数据库查看是否有用此信息的唯一标识为key的记录
Object o = redisTemplate.opsForValue().get(msg.getMessageProperties().getDeliveryTag());
//判断是否取到值,取到就说明已经进行过操作,没有就是第一次进行业务代码
if(o==null){
//业务代码
try {
System.out.println("完成业务功能");
//完成业务后通过redisTemplate.opsForValue().set方法往redis中存放key为此信息的唯一标识为key的记录 ,方便下次如果在重复操作此信息时进行判断
redisTemplate.opsForValue().set(msg.getMessageProperties().getDeliveryTag(), "ykq");
//完成后手动把信息从队列中消费删除掉
channel.basicAck(msg.getMessageProperties().getDeliveryTag(), true);
}catch (Exception e){
//如果出现异常就从队列中删除掉此信息,并且要求生产者重新发送信息到队列中
channel.basicNack(msg.getMessageProperties().getDeliveryTag(),true,true);
}
}else{
//否则就是重复操作,就直接从队列中删除掉此信息
channel.basicAck(msg.getMessageProperties().getDeliveryTag(),true);
}
}
}