消息重试
首先明确之前说过的,消息重试只针对集群消费模式,广播消费没有消息重试的特性,消费失败之后,只会继续消费下一条消息。这也是为什么我们一再强调,推荐大家使用集群消费模式,其消息重试的特性能给开发者带来极大的方便。
那么什么是消息重试呢?简单来说,就是当消费者消费消息失败后,broker 会重新投递该消息,直到消费成功。在 RocketMQ 中,当消费者使用集群消费模式时,消费者接收到消息并进行相应的逻辑处理之后,最后都要返回一个状态值给 broker。这样 broker 才知道是否消费成功,需不需要重新投递消息。也就是说,我们可以通过设置返回的状态值来告诉 broker 是否重新投递消息。
到这里,可能大家会有一个疑问,那如果这条消息本身就是一条脏数据,就算你消费 100 次也不会消费成功,难道还是一直去重试嘛?其实 RocketMQ 并不会无限制地重试下去,默认每条消息最多重试 16 次,而每次重试的间隔时间如下表所示:
第几次重试 | 每次重试间隔时间 |
---|---|
1 | 10 秒 |
2 | 30 秒 |
3 | 1 分钟 |
4 | 2 分钟 |
5 | 3 分钟 |
6 | 4 分钟 |
7 | 5 分钟 |
8 | 6 分钟 |
9 | 7 分钟 |
10 | 8 分钟 |
11 | 9 分钟 |
12 | 10 分钟 |
13 | 20 分钟 |
14 | 30 分钟 |
15 | 1 小时 |
16 | 2 小时 |
那么如果消息重试 16 次之后还是消费失败怎么办呢?那么消息就不会再投递给消费者,而是将消息放到相对应的死信队列中。这时候我们就需要对死信队列的消息做一些人工补偿处理,因为这些消息可能本身就有问题,也有可能和消费逻辑调用的服务有关等,所以需要人工判断之后再进行处理。
到这里不知道大家有没有一个疑问,那就是什么样的情况才叫消费失败呢?可以分为 3 种情况:
-
返回 ConsumeConcurrentlyStatus.RECONSUME_LATER
-
返回 null
-
抛出异常
前两种情况都比较好理解,就是前面说过的设置状态值,也就是说,只需要消费者返回 ConsumeConcurrentlyStatus.RECONSUME_LATER 或者 null,就相当于告诉 broker 说,这条消息我消费失败了,你给我重新投递一次。而对于抛出异常这种情况,只要在你处理消费逻辑的地方抛出了异常,那么 broker 也重新投递这条消息。注意一点,如果是被捕获的异常,则不会进行消息重试。
消息幂等
首先什么是消费幂等呢?简单来说就是对于一条消息的处理结果,不管这条消息被处理多少次,最终的结果都一样。比如说,你收到一条消息是要更新一个商品的价格为 6.8 元,那么当这条消息执行 1 次,还是执行 100 次,最终在数据库里的该商品价格就是 6.8 元,这就是所谓的幂等。 那么为什么消费需要幂等呢?因为在实际使用中,尤其在网络不稳定的情况下,RocketMQ 的消息有可能会出现重复,包括两种情况:
-
发送时消息重复;
-
投递时消息重复;
第一种情况是生产者发送消息的场景,消息已成功发送到 broker ,但是此时可能发生网络闪断或者生产者宕机了,导致 broker 发回的响应失败。这时候生产者由于没有收到响应,认为消息发送失败,于是尝试再次发送消息给 broker。这样一来,broker 就会再收到一条一摸一样内容的消息,最终造成了消费者也收到两条内容一摸一样的消息。
第二种情况是消费者消费消息的场景,消息已投递到消费者并完成消费逻辑处理,当消费者给 broker 反馈消费状态时可能发生网络闪断。broker 收不到消费者的消费状态,为了保证至少消费一次的语义,broker 将在网络恢复后再次尝试投递之前已经被处理过的消息,最终造成消费者收到两条内容一摸一样的消息。
当然对于一些允许消息重复的场景,大可以不必关心消费幂等。但是对于那些不允许消息重复的业务场景来说,处理建议就是通过业务上的唯一标识来作为幂等处理的依据。
总结
消息重试,保证了消费消息的容错性,即使消费失败,也不需要开发者自己去编写代码来做补偿,大大提高了开发效率,同时也是 RocketMQ 相较于其他 MQ 的一个非常好的特性。而消费幂等主要是针对那些不允许消息重复的场景,应该说大部分 MQ 都需要幂等处理,这属于代码逻辑或者说业务上的需要,最好的处理方式就是前面所说的根据业务上唯一标识来作为幂等处理的依据。