当我们消息消费失败的时候,可以进行重试,虽然SpringAMQP集成了retry机制,但是发现使用过程有点坑,等会细说
重试机制使用场景:
1.如果是业务代码,比如空指针之类的异常那重试机制其实没什么用
2.如果是调用第三方系统,网络抖动之类的那重试机制就有用
配置使用重试机制
spring.rabbitmq.listener.simple.retry.enabled=true 是否开启消费者重试(为false时重试不生效)
spring.rabbitmq.listener.simple.retry.max-attempts=3 最大重试次数(包含本身消费的1次)
spring.rabbitmq.listener.simple.retry.initial-interval=5000 重试间隔时间(单位毫秒)
spring.rabbitmq.listener.simple.default-requeue-rejected=false 重试次数超过上面的设置之后是否丢弃(false不丢弃时需要写相应代码将该消息加入死信队列)
坑的地方来了:
不管消息被消费了之后是手动确认还是自动确认,代码中不能使用try/catch捕获异常,否则重试机制失效
所以这里要使用重试机制就有2种情况了:
1.如果消息是自动确认,由于异常,多次重试还是失败,消息被自动确认,那消息就丢失了
2.如果消息是手动确认,由于异常,多次重试还是失败,消息没被确认,也无法nack,就一直是unacked状态,导致消息积压
第一种情况代码:
@RabbitListener(queues = "order-queue")
public void processOrder(Message message, Channel channel, Map map) throws Exception {
System.out.println("收到订单队列消息:" + message.toString());
//产生异常,导致订单处理失败
throw new Exception();
}
可以看到重试了3次,但是最终还是失败了,消息被自动确认了,丢失了,如果是涉及到金钱之类的就惨了。
第二种情况代码:
@RabbitListener(queues = "order-queue")
public void processOrder(Message message, Channel channel, Map map) throws Exception {
System.out.println("收到订单队列消息:" + message.toString());
//产生异常,导致订单处理失败
int a = 1/0;
//确认消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
同样是重试三次之后,还是失败
这里可以看到消息会一直是unacked的状态,因为异常无法ack,然后代码中也无法nack
如何既保证重试又能不丢失消息呢?这只是我的构思:
首先是手动确认,然后在catch中throw异常触发重试机制,然后定义一个局部变量错误次数retryCount,失败后++,当retryCount=重试次数,则nack并且requeue=false到死信队列中进行入库操作,方便后续人工补偿。
//重试次数
private int retryCount = 0;
@RabbitListener(queues = "dead-queue")
public void process(Message message, Channel channel, Map map) throws IOException {
System.out.println("收到进入死信队列消息:" + message.toString());
System.out.println("消息入库,后续补偿");
//确认消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
}
@RabbitListener(queues = "order-queue")
public void processOrder(Message message, Channel channel, Map map) throws Exception {
try {
System.out.println("收到订单队列消息:" + message.toString());
int a = 1 / 0;
//确认消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
retryCount++;
//重新抛出异常 触发重试机制
throw e;
} finally {
//重试次数达到限制
if (retryCount == 3) {
System.out.println("处理订单消息异常,nack消息到死信队列");
//不重新入队,发送到死信队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
}
}
}
可以看到重试3次之后,进入死信队列中。