python rabbitmq 消息重试机制_rabbitMQ消息重试机制

消费端在处理消息过程中可能会报错,此时该如何重新处理消息呢?解决方案有以下两种。

在redis或者数据库中记录重试次数,达到最大重试次数以后消息进入死信队列或者其他队列,再单独针对这些消息进行处理;

使用spring-rabbit中自带的retry功能;

第一种方案我们就不再详细说了,我们主要来看一下第二种方案,老规矩,先上代码:

spring:

rabbitmq:

listener:

simple:

acknowledge-mode: auto # 自动ack

retry:

enabled: true

max-attempts: 5

max-interval: 10000 # 重试最大间隔时间

initial-interval: 2000 # 重试初始间隔时间

multiplier: 2 # 间隔时间乘子,间隔时间*乘子=下一次的间隔时间,最大不能超过设置的最大间隔时间

此时我们的消费者代码如下所示:

@RabbitHandler

@RabbitListener(queues = {"${platform.queue-name}"},concurrency = "1")

public void msgConsumer(String msg, Channel channel, Message message) throws IOException {

log.info("接收到消息>>>{}",msg);

int temp = 10/0;

log.info("消息{}消费成功",msg);

}

此时启动程序,发送消息后可以看到控制台输出内容如下:

可以看到重试次数是5次(包含自身消费的一次),重试时间依次是2s,4s,8s,10s(上一次间隔时间*间隔时间乘子),最后一次重试时间理论上是16s,但是由于设置了最大间隔时间是10s,因此最后一次间隔时间只能是10s,和配置相符合。

注意:

重试并不是RabbitMQ重新发送了消息,仅仅是消费者内部进行的重试,换句话说就是重试跟mq没有任何关系;

因此上述消费者代码不能添加try{}catch(){},一旦捕获了异常,在自动ack模式下,就相当于消息正确处理了,消息直接被确认掉了,不会触发重试的;

死信队列

除了可以采用上述RepublishMessageRecoverer,还可以采用死信队列的方式处理重试失败的消息。

首先创建死信交换机、死信队列以及两者的绑定

死信交换机

@return

*/

@Bean

public DirectExchange dlxExchange(){

return new DirectExchange(dlxExchangeName);

}

/**

死信队列

@return

*/

@Bean

public Queue dlxQueue(){

return new Queue(dlxQueueName);

}

/**

死信队列绑定死信交换机

@param dlxQueue

@param dlxExchange

@return

*/

@Bean

public Binding dlcBinding(Queue dlxQueue, DirectExchange dlxExchange){

return BindingBuilder.bind(dlxQueue).to(dlxExchange).with(dlxRoutingKey);

}

业务队列的创建需要做一些修改,添加死信交换机以及死信路由键的配置

@return

*/

@Bean

public Queue queue(){

Map params = new HashMap<>();

params.put("x-dead-letter-exchange",dlxExchangeName);//声明当前队列绑定的死信交换机

params.put("x-dead-letter-routing-key",dlxRoutingKey);//声明当前队列的死信路由键

return QueueBuilder.durable(queueName).withArguments(params).build();

//return new Queue(queueName,true);

}

此时启动服务,可以看到同时创建了业务队列以及死信队列

在业务队列上出现了DLX以及DLK的标识,标识已经绑定了死信交换机以及死信路由键,此时调用生产者发送消息,消费者在重试5次后,由于MessageCover默认的实现类是RejectAndDontRequeueRecoverer,也就是requeue=false,又因为业务队列绑定了死信队列,因此消息会从业务队列中删除,同时发送到死信队列中。

注意:

如果ack模式是手动ack,那么需要调用channe.nack方法,同时设置requeue=false才会将异常消息发送到死信队列中

retry使用场景

上面说了什么是重试,以及如何解决重试造成的数据丢失,那么怎么来选择重试的使用场景呢?

是否是消费者只要发生异常就要去重试呢?其实不然,假设下面的两个场景:

http下载视频或者图片或者调用第三方接口

空指针异常或者类型转换异常(其他的受检查的运行时异常)

很显然,第一种情况有重试的意义,第二种没有。

对于第一种情况,由于网络波动等原因造成请求失败,重试是有意义的;

对于第二种情况,需要修改代码才能解决的问题,重试也没有意义,需要的是记录日志以及人工处理或者轮询任务的方式去处理。

retry最佳实践

对于消费端异常的消息,如果在有限次重试过程中消费成功是最好的,如果有限次重试之后仍然失败的消息,不管是采用RejectAndDontRequeueRecoverer还是使用私信队列都是可以的,同时也可以采用折中的方法,先将消息从业务队列中ack掉,再将消息发送到另外的一个队列中,后续再单独处理异常数据的队列。

另外,看到有人说retry只能在自动ack模式下使用,经过测试在手动ack模式下retry也是生效的,只不过不能使用catch捕获异常,即使在自动ack模式下使用catch捕获异常也是会导致不触发重试的。当然,在手动ackm模式下要记得确认消息,不管是确认消费成功还是确认消费失败,不然消息会一直处于unack状态,直到消费者进程重启或者停止。

如果一定要在手动ack模式下使用retry功能,最好还是确认在有限次重试过程中可以重试成功,否则超过重试次数,又没办法执行nack,就会出现消息一直处于unack的问题,我想这也就是所说的retry只能在自动ack模式下使用的原因,测试代码如下:

@RabbitHandler

@RabbitListener(queues = {"${platform.queue-name}"},concurrency = "1")

public void msgConsumer(String msg, Channel channel, Message message) throws IOException {

log.info("接收到消息>>>{}",msg);

if(msg.indexOf("0")>-1){

throw new RuntimeException("抛出异常");

}

log.info("消息{}消费成功",msg);

channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值