当前位置:   article > 正文

RocketMQ-高级原理_defaultmqpushconsumer.setmaxreconsumetimes

defaultmqpushconsumer.setmaxreconsumetimes

本节讲解下当MQ消息消费失败,或者发送不成功时如何处理消息,消息发送不成功一般存在于几种情况,网络原因,服务宕机,或者broker配置

消息发送失败

 如果是由于broker配置原因,可以通过报错提示排查原因:

无法查到路由信息,一般考虑到rocketMQ读取路由信息过程:

  • 如果Broker开启了自动创建Topic,在启动的时候会默认创建主题:并会随着Broker发送到Nameserver的心跳包汇报给Nameserver,继而从Nameserver查询路由信息时能返回路由信息。
  • 消息发送者在消息发送时首先会查本地缓存,如果本地缓存中存在,直接返回路由信息。
  • 如果缓存不存在,则向Nameserver查询路由信息,如果Nameserver存在该路由信息,就直接返回。
  • 如果Nameserver不存在该topic的路由信息,如果没有开启自动创建主题,则抛出 No route info of this topic。
  • 如果开启了自动创建主题,则使用默认主题向Nameserver查询路由信息,并使用默认Topic的路由信息为自己的路由信息,将不会抛出 No route info of this topic。
     

消息发送超时

客户端报消息发送超时,通常第一怀疑的对象是RocketMQ服务器,是不是Broker性能出现了抖动,无法抗住当前的量。那我们如何来排查RocketMQ当前是否有性能瓶颈呢?

查看rocketMQ日志:

  1. cd ~/logs/rocketmqlogs/
  2. grep -n 'PAGECACHERT' store.log | more

因为网络抖动原因出现的消息超时,通过减少消息发送的超时时间,增加重试次数,并增加快速失败的最大等待时长。具体措施如下:

  • 增加Broker端快速失败的时长,建议为1000,在broker的配置文件中增加如下配置:

    maxWaitTimeMillsInQueue=1000
  1. DefaultMQProducer producer = new DefaultMQProducer("dw_test_producer_group");
  2. producer.setRetryTimesWhenSendFailed(5);// 同步发送模式:重试次数
  3. producer.setRetryTimesWhenSendAsyncFailed(5);// 异步发送模式:重试次数
  4. producer.start();
  5. producer.send(msg,500);//消息发送超时时间

 消息重试

首先对于广播模式的消息, 是不存在消息重试的机制的,即消息消费失败后,不会再重新进行发送,而只是继续消费新的消息。而对于普通的消息,当消费者消费消息失败后,你可以通过设置返回状态达到消息重试的结果。

如何让消息进行重试 ,集群消费方式下,消息消费失败后期望消息重试,需要在消息监听器接口的实现中明确进行配置。可以有三种配置方式:

  • 返回Action.ReconsumeLater-推荐
  • 返回null
  • 抛出异常
  1. public class MessageListenerImpl implements MessageListener {
  2. @Override
  3. public Action consume(Message message, ConsumeContext context) {
  4. //处理消息
  5. doConsumeMessage(message);
  6. //方式1:返回 Action.ReconsumeLater,消息将重试
  7. return Action.ReconsumeLater;
  8. //方式2:返回 null,消息将重试
  9. return null;
  10. //方式3:直接抛出异常, 消息将重试
  11. throw new RuntimeException("Consumer Message exceotion");
  12. }
  13. }

如果希望消费失败后不重试,可以直接返回Action.CommitMessage。

  1. public class MessageListenerImpl implements MessageListener {
  2. @Override
  3. public Action consume(Message message, ConsumeContext context) {
  4. try {
  5. doConsumeMessage(message);
  6. } catch (Throwable e) {
  7. //捕获消费逻辑中的所有异常,并返回 Action.CommitMessage;
  8. return Action.CommitMessage;
  9. }
  10. //消息处理正常,直接返回 Action.CommitMessage;
  11. return Action.CommitMessage;
  12. }
  13. }

生产者消息重试策略:

如果由于网络抖动等原因,Producer程序向Broker发送消息时没有成功,即发送端没有收到Broker的ACK,导致最终Consumer无法消费消息,此时RocketMQ会自动进行重试

DefaultMQProducer可以设置消息发送失败的最大重试次数,并可以结合发送的超时时间来进行重试的处理,具体API如下:

  1. //设置消息发送失败时的最大重试次数
  2. public void setRetryTimesWhenSendFailed(int retryTimesWhenSendFailed) {
  3. this.retryTimesWhenSendFailed = retryTimesWhenSendFailed;
  4. }
  5. //同步发送消息,并指定超时时间
  6. public SendResult send(Message msg,
  7. long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
  8. return this.defaultMQProducerImpl.send(msg, timeout);
  9. }

消费者消费异常重试

异常重试:由于Consumer端逻辑出现了异常,导致返回了RECONSUME_LATER状态,那么Broker就会在一段时间后尝试重试。
超时重试:如果Consumer端处理时间过长,或者由于某些原因线程挂起,导致迟迟没有返回消费状态,Broker就会认为Consumer消费超时,此时会发起超时重试。
因此,如果Consumer端正常消费成功,一定要返回ConsumeConcurrentlyStatus.ConsumeConcurrentlyStatus状态

异常重试

RocketMQ可在broker.conf文件中配置Consumer端的重试次数和重试时间间隔,如下:

  1. messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
  2. 1

但是在大部分情况下,如果Consumer端逻辑出现异常,重试太多次也没有很大的意义,我们可以在代码中指定最大的重试次数。

        defaultMQPushConsumer.setMaxReconsumeTimes(5);

死信队列

当一条消息消费失败,RocketMQ就会自动进行消息重试。而如果消息超过最大重试次数,RocketMQ就会认为这个消息有问题。但是此时,RocketMQ不会立刻将这个有问题的消息丢弃,而会将其发送到这个消费者组对应的一种特殊队列:死信队列。

死信队列的名称是%DLQ%+ConsumGroup

死信队列的特征:

  • 一个死信队列对应一个ConsumGroup,而不是对应某个消费者实例。
  • 如果一个ConsumeGroup没有产生死信队列,RocketMQ就不会为其创建相应的死信队列。
  • 一个死信队列包含了这个ConsumeGroup里的所有死信消息,而不区分该消息属于哪个Topic。
  • 死信队列中的消息不会再被消费者正常消费。
  • 死信队列的有效期跟正常消息相同。默认3天,对应broker.conf中的fileReservedTime属性。超过这个最长时间的消息都会被删除,而不管消息是否消费过。

通常,一条消息进入了死信队列,意味着消息在消费处理的过程中出现了比较严重的错误,并且无法自行恢复。此时,一般需要人工去查看死信队列中的消息,对错误原因进行排查。然后对死信消息进行处理,比如转发到正常的Topic重新进行消费,或者丢弃。

注:默认创建出来的死信队列,他里面的消息是无法读取的,在控制台和消费者中都无法读取。这是因为这些默认的死信队列,他们的权限perm被设置成了2:禁读(这个权限有三种 2:禁读,4:禁写,6:可读可写)。需要手动将死信队列的权限配置成6,才能被消费(可以通过mqadmin指定或者web控制台)。

消息幂等

幂等的概念

在MQ系统中,对于消息幂等有三种实现语义:

  • at most once 最多一次:每条消息最多只会被消费一次
  • at least once 至少一次:每条消息至少会被消费一次
  • exactly once 刚刚好一次:每条消息都只会确定的消费一次

这三种语义都有他适用的业务场景。

  • 其中,at most once是最好保证的。RocketMQ中可以直接用异步发送、sendOneWay等方式就可以保证。
  • 而at least once这个语义,RocketMQ也有同步发送、事务消息等很多方式能够保证。
  • 而这个exactly once是MQ中最理想也是最难保证的一种语义,需要有非常精细的设计才行。RocketMQ只能保证at least once,保证不了exactly once。所以,使用RocketMQ时,需要由业务系统自行保证消息的幂等性。

关于这个问题,官网上有明确的回答:4. Are messages delivered exactly once?RocketMQ ensures that all messages are delivered at least once. In most cases, the messages are not repeated.

消息幂等的必要性

在互联网应用中,尤其在网络不稳定的情况下,消息队列 RocketMQ 的消息有可能会出现重复,这个重复简单可以概括为以下情况:

  • 发送时消息重复

当一条消息已被成功发送到服务端并完成持久化,此时出现了网络闪断或者客户端宕机,导致服务端对客户端应答失败。 如果此时生产者意识到消息发送失败并尝试再次发送消息,消费者后续会收到两条内容相同并且 Message ID 也相同的消息。

  • 投递时消息重复

消息消费的场景下,消息已投递到消费者并完成业务处理,当客户端给服务端反馈应答的时候网络闪断。 为了保证消息至少被消费一次,消息队列 RocketMQ 的服务端将在网络恢复后再次尝试投递之前已被处理过的消息,消费者后续会收到两条内容相同并且 Message ID 也相同的消息。

  • 负载均衡时消息重复(包括但不限于网络抖动、Broker 重启以及订阅方应用重启)

当消息队列 RocketMQ 的 Broker 或客户端重启、扩容或缩容时,会触发 Rebalance,此时消费者可能会收到重复消息。

处理方式

      从上面的分析中,我们知道,在RocketMQ中,是无法保证每个消息只被投递一次的,所以要在业务上自行来保证消息消费的幂等性。而要处理这个问题,RocketMQ的每条消息都有一个唯一的MessageId,这个参数在多次投递的过程中是不会改变的,所以业务上可以用这个MessageId来作为判断幂等的关键依据。但是,这个MessageId是无法保证全局唯一的,也会有冲突的情况。所以在一些对幂等性要求严格的场景,最好是使用业务上唯一的一个标识比较靠谱。例如订单ID。而这个业务标识可以使用Message的Key来进行传递。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/603814
推荐阅读
相关标签
  

闽ICP备14008679号