死信队列
关于RabbitMQ死信队列
死信队列 听上去像 消息“死”了 其实也有点这个意思,死信队列 是 当消息在一个队列 因为下列原因:
消息被拒绝(basic.reject/ basic.nack)并且不再重新投递 requeue=false
消息超期过期 (rabbitmq Time-To-Live -> messageProperties.setExpiration())
队列超载:
变成了 “死信” 后 被重新投递(publish)到另一个Exchange 该Exchange 就是DLX 然后该Exchange 根据绑定规则 转发到对应的 队列上 监听该队列 就可以重新消费 说白了 就是 没有被消费的消息 换个地方重新被消费
生产者 --> 消息 --> 交换机 --> 队列 --> 变成死信 --> DLX交换机 -->队列 --> 消费者
一 、队列的创建和绑定
public static final String EXCHANGE = "zw.dead.exchange";
public static final String ROUTING_KEY = "zw.dead.routingKey";
public static final String NORMAL_QUEUE = "zw.dead.normal.queue";
public static final String NORMAL_EXCHANGE = "zw.dead.normal.exchange";
public static final String NORMAL_ROUTING_KEY = "zw.dead.normal.routingKey";
public static final String REAL_DEAD_QUEUE = "zw.dead.queue";
public static final int QUEUE_EXPIRE = 6000000;
public static final int EXPIRE = 30000;
/**
* 创建普通交换机
*/
@Bean
public Exchange normalExchange() {
return new TopicExchange(NORMAL_EXCHANGE, true, false);
}
/**
* 绑定普通队列和普通交换机
*/
@Bean
public Binding normalBind() {
return BindingBuilder.bind(normalQueue()).to(normalExchange()).with(NORMAL_ROUTING_KEY).noargs();
}
/**
* 创建死信交换机
*/
@Bean
public Exchange deadExchange() {
return new TopicExchange(EXCHANGE, true, false);
}
/**
* 创建真实存储死信的队列,当死信队列中消息过期后,转发到此队列,真实存储死信队列需要绑定死信交换机和路由
*/
@Bean
public Queue realDeadQueue() {
return new Queue(REAL_DEAD_QUEUE, true, false, false);
}
/**
* 绑定真实存储死信的队列与死信交换机
*/
@Bean
public Binding realBindDead() {
return BindingBuilder.bind(realDeadQueue()).to(deadExchange()).with(ROUTING_KEY).noargs();
/**
* 普通的队列,但是指定了死信交换机和路由,在这个队列中的消息过期后会由死信交换机分发到真正的死信队列
*/
@Bean
public Queue normalQueue() {
Map<String, Object> args = new HashMap<>();
//设置超时时间
args.put("x-message-ttl",QUEUE_EXPIRE);
//设置死信参数
//指定死信交换机
args.put("x-dead-letter-exchange", EXCHANGE);
//指定死信路由
args.put("x-dead-letter-routing-key", ROUTING_KEY);
return new Queue(NORMAL_QUEUE, true, false, false, args);
}
}
二、消费者消费,消费失败之后的逻辑
@Component
@Log4j2
@RabbitListener(queues = REAL_DEAD_QUEUE)
public class PayOrderConsumer {
@Resource
OrderMapper orderMapper;
@Resource
RabbitTemplate rabbitTemplate;
@RabbitHandler
public void receiveDeadMsg(String msg, Channel channel, Message message) throws IOException {
JSONObject msgJson = JSON.parseObject(msg);
int count = Integer.parseInt(msgJson.getString("count"));
Order order=null;
try {
log.info("接收到消息:{}",msg);
//业务逻辑
....
channel.basicAck(message.getMessageProperties().getDeliveryTag(), Boolean.FALSE);
}catch (Exception e){
if (count>2){
log.info("重试次数超过三次失败的消息:{}",msg)
//发生异常三次重试都失败的时候的业务逻辑
....
channel.basicAck(message.getMessageProperties().getDeliveryTag(), Boolean.FALSE);
throw e;
}else {
//消费消息失败的时候的逻辑
log.error("消息{}即将再次返回队列处理...",msg);
msgJson.put("count",count+1); //计数器
rabbitTemplate.convertAndSend(NORMAL_EXCHANGE, NORMAL_ROUTING_KEY, msgJson.toString(), new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
MessageProperties messageProperties = message.getMessageProperties();
//设置过期时间TTL
messageProperties.setExpiration(String.valueOf(EXPIRE));
return message;
}
});
}
channel.basicAck(message.getMessageProperties().getDeliveryTag(), Boolean.FALSE);
}
}
}
三、发送消息
JSONObject jsonObject = new JSONObject();
jsonObject.put("msg",orderNumber);
jsonObject.put("count",0);
rabbitTemplate.convertAndSend(PayOrderRabbitConfig.NORMAL_EXCHANGE,PayOrderRabbitConfig.NORMAL_ROUTING_KEY,jsonObject);
log.info("生成订单{}成功",orderNumber);