• RocketMQ(11) 消息重试机制和死信队列


    七、消息发送重试机制

    1 说明

    Producer对发送失败的消息进行重新发送的机制,称为消息发送重试机制,也称为消息重投机制。

    对于消息重投,需要注意以下几点:

    • 生产者在发送消息时,若采用同步或异步发送方式,发送失败会重试,但oneway消息发送方式 发送失败是没有重试机制的

    • 只有普通消息有发送重试机制,顺序消息是没有的(只有默认自带的发送选择才有这个功能,若手动实现选择器,则无法实现重试避错机制,也不需要)

    • 消息重投机制可以保证消息尽可能发送成功、不丢失,但可能会造成消息重复。消息重复在 RocketMQ中是无法避免的问题

    • 消息重复在一般情况下不会发生,当出现消息量大、网络抖动,消息重复就会成为大概率事件

    • producer主动重发、consumer负载变化(发生Rebalance,不会导致消息重复,但可能出现重复 消费)也会导致重复消息

    • 消息重复无法避免,但要避免消息的重复消费。

    • 避免消息重复消费的解决方案是,为消息添加唯一标识(例如消息key),使消费者对消息进行消 费判断来避免重复消费

    • 消息发送重试有三种策略可以选择:同步发送失败策略、异步发送失败策略、消息刷盘失败策略

    2 同步发送失败策略

    对于普通消息,消息发送默认采用round-robin策略来选择所发送到的队列。如果发送失败,默认重试2 次。但在重试时是不会选择上次发送失败的Broker,而是选择其它Broker。当然,若只有一个Broker其 也只能发送到该Broker,但其会尽量发送到该Broker上的其它Queue。

    // 创建一个producer,参数为Producer Group名称
    DefaultMQProducer producer = new DefaultMQProducer("pg");
    // 指定nameServer地址
    producer.setNamesrvAddr("rocketmqOS:9876");
    // 设置同步发送失败时重试发送的次数,默认为2次
    producer.setRetryTimesWhenSendFailed(3);
    // 设置发送超时时限为5s,默认3s
    producer.setSendMsgTimeout(5000);
    

    同时,Broker还具有失败隔离功能,使Producer尽量选择未发生过发送失败的Broker作为目标 Broker。其可以保证其它消息尽量不发送到问题Broker,可以降低发送失败的概率,提升消息发送效率,降低消息发送耗时。

    思考:让我们自己实现失败隔离功能,如何来做?

    1)方案一:Producer中维护某JUC的Map集合,其key是发生失败的时间戳,value为Broker实 例。Producer中还维护着一个Set集合,其中存放着所有未发生发送异常的Broker实例。选择目 标Broker是从该Set集合中选择的。再定义一个定时任务,定期从Map集合中将长期未发生发送 异常的Broker清理出去,并添加到Set集合。

    2)方案二:为Producer中的Broker实例添加一个标识,例如是一个AtomicBoolean属性。只要该 Broker上发生过发送异常,就将其置为true。选择目标Broker就是选择该属性值为false的 Broker。再定义一个定时任务,定期将Broker的该属性置为false。

    3)方案三:为Producer中的Broker实例添加一个标识,例如是一个AtomicLong属性。只要该 Broker上发生过发送异常,就使其值增一。选择目标Broker就是选择该属性值最小的Broker。若 该值相同,采用轮询方式选择。

    如果超过重试次数,则抛出异常,由Producer去保证消息不丢。当然当生产者出现 RemotingException、MQClientException和MQBrokerException时,Producer会自动重投消息。

    3 异步发送失败策略

    异步发送失败重试时,异步重试不会选择其他broker,仅在同一个broker上做重试,所以该策略无法保 证消息不丢。

    DefaultMQProducer producer = new DefaultMQProducer("pg");
    producer.setNamesrvAddr("rocketmqOS:9876");
    producer.setRetryTimesWhenSendAsyncFailed(3);
    

    4 消息刷盘失败策略

    消息刷盘超时(Master或Slave)或slave不可用(slave在做数据同步时向master返回状态不是 SEND_OK)时,默认是不会将消息尝试发送到其他Broker的。不过,对于重要消息可以通过在Broker 的配置文件设置retryAnotherBrokerWhenNotStoreOK属性为true来开启。

    八、消息消费重试机制

    1 顺序消息的消费重试

    对于顺序消息,需要严格保证拉取消费的顺序性,所以不会错过任何一个消息的消费,所以当Consumer消费消息失败后,为了保证消息的顺序性,其会自动不断地进行消息重 试,直到消费成功。消费重试默认间隔时间为1000毫秒。重试期间应用会出现消息消费被阻塞的情 况。

    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("cg");
    // 顺序消息消费失败的消费重试时间间隔,单位毫秒,默认为1000,其取值范围为[10,30000]
    consumer.setSuspendCurrentQueueTimeMillis(100);
    

    由于对顺序消息的重试是无休止的,不间断的,直至消费成功,所以,对于顺序消息的消费, 务必要保证应用能够及时监控并处理消费失败的情况,避免消费被永久性阻塞。

    注意,顺序消息没有发送失败重试机制,但具有消费失败重试机制

    2 无序消息的消费重试

    对于无序消息(普通消息、延时消息、事务消息),当Consumer消费消息失败时,可以通过设置返回状态达到消息重试的效果。不过需要注意,无序消息的重试只对集群消费方式生效,广播消费方式不提供失败重试特性。即对于广播消费,消费失败后,失败消息不再重试,继续消费后续消息。

    对于无序消息集群消费下的重试消费,每条消息默认最多重试16次,但每次重试的间隔时间是不同的,会逐渐变长。每次重试的间隔时间如下表。

    重试次数 与上次重试的间隔时间 重试次数 与上次重试的间隔时间
    1 10秒 9 7分钟
    2 30秒 10 8分钟
    3 1分钟 11 9分钟
    4 2分钟 12 10分钟
    5 3分钟 13 20分钟
    6 4分钟 14 30分钟
    7 5分钟 15 1小时
    8 6分钟 16 2小时

    若一条消息在一直消费失败的前提下,将会在正常消费后的第4小时46分后进行第16次重试。 若仍然失败,则将消息投递到死信队列

    修改消费重试次数

    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("cg");
    // 修改消费重试次数
    consumer.setMaxReconsumeTimes(10);
    
    

    对于修改过的重试次数,将按照以下策略执行:

    • 若修改值小于16,则按照指定间隔进行重试
    • 若修改值大于16,则超过16次的重试时间间隔均为2小时

    对于Consumer Group,若仅修改了一个Consumer的消费重试次数,则会应用到该Group中所有 其它Consumer实例。若出现多个Consumer均做了修改的情况,则采用覆盖方式生效。即最后被 修改的值会覆盖前面设置的值。

    3. 重试队列

    对于需要重试消费的消息,并不是Consumer在等待了指定时长后再次去拉取原来的消息进行消费,而 是将这些需要重试消费的消息放入到了一个特殊Topic的队列中,而后进行再次消费的。这个特殊的队 列就是重试队列。

    当出现需要进行重试消费的消息时,Broker会为每个消费组都设置一个Topic名称 为%RETRY%consumerGroup@consumerGroup 的重试队列。

    1)这个重试队列是针对消息才组的,而不是针对每个Topic设置的(一个Topic的消息可以让多 个消费者组进行消费,所以会为这些消费者组各创建一个重试队列)

    2)只有当出现需要进行重试消费的消息时,才会为该消费者组创建重试队列

    注意,消费重试的时间间隔延时消费的延时等级十分相似,除了没有延时等级的前两个时间 外,其它的时间都是相同的

    Broker对于重试消息的处理是通过延时消息实现的。先将消息保存到SCHEDULE_TOPIC_XXXX延迟队 列中,延迟时间到后,会将消息投递到%RETRY%consumerGroup@consumerGroup重试队列中。

    4. 消费重试配置方式

    集群消费方式下,消息消费失败后若希望消费重试,则需要在消息监听器接口的实现中明确进行如下三 种方式之一的配置:

    • 方式1:返回ConsumeConcurrentlyStatus.RECONSUME_LATER(推荐)
    • 方式2:返回Null
    • 方式3:抛出异常

    image-20220111232349353

    5. 消费不重试配置方式

    image-20220111232300838

    集群消费方式下,消息消费失败后若不希望消费重试,则在捕获到异常后同样也返回与消费成功后的相 同的结果,即ConsumeConcurrentlyStatus.CONSUME_SUCCESS,则不进行消费重试。

    九、死信队列

    1 什么是死信队列

    当一条消息初次消费失败,消息队列会自动进行消费重试;达到最大重试次数后,若消费依然失败,则 表明消费者在正常情况下无法正确地消费该消息,此时,消息队列不会立刻将消息丢弃,而是将其发送 到该消费者对应的特殊队列中。这个队列就是死信队列(Dead-Letter Queue,DLQ),而其中的消息 则称为死信消息(Dead-Letter Message,DLM)。

    死信队列是用于处理无法被正常消费的消息的。

    2 死信队列的特征

    死信队列具有如下特征:

    • 死信队列中的消息不会再被消费者正常消费,即DLQ对于消费者是不可见的

    • 死信存储有效期与正常消息相同,均为 3 天(commitlog文件的过期时间),3 天后会被自动删除

    • 死信队列就是一个特殊的Topic,名称为%DLQ%consumerGroup@consumerGroup ,即每个消 费者组都有一个死信队列

    • 如果⼀个消费者组未产生死信消息,则不会为其创建相应的死信队列

    3 死信消息的处理

    实际上,当⼀条消息进入死信队列,就意味着系统中某些地方出现了问题,从而导致消费者无法正常消 费该消息,比如代码中原本就存在Bug。因此,对于死信消息,通常需要开发人员进行特殊处理。最关 键的步骤是要排查可疑因素,解决代码中可能存在的Bug,然后再将原来的死信消息再次进行投递消 费

  • 相关阅读:
    Apache 性能配置优化
    大数据企业备份
    Docker限制容器可用的CPU
    docker运行容器后agetty进程cpu占用率100%
    Ubuntu下crontab命令的用法
    set -x与set +x指令
    CentOS设置程序开机自启动的方法
    maven 打JAR包资源文件指定路径与文件读取
    hadoop-maven项目打包成可执行的jar
    如何将maven项目打包成可执行的jar
  • 原文地址:https://www.cnblogs.com/xjwhaha/p/15868408.html
Copyright © 2020-2023  润新知