rocketMQ如何避免消息重复消费

RocketMQ在特定情况下可能出现消息重复,如网络闪断导致的生产者重试和消费者确认失败后的重新投递。为确保幂等性,业务端需利用MessageId或自定义业务唯一标识来判断是否重复消费。一种策略是使用消息表,通过主键避免重复插入。另一种方法是在消息持久化前验证业务唯一标识,确保消息唯一。此外,还可以借助分布式中间件生成全局唯一ID。

前言

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

  • produce发送到Broker时消息重复

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

  • Broker投递消息到Consumer时消息重复

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

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

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

幂等性解决方案

幂等性:就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。

从上面的分析中,我们知道,在RocketMQ中,是无法保证每个消息只被投递一次的,所以要在业务上自行来保证消息消费的幂等性。而要处理这个问题,RocketMQ的每条消息都有一个唯一的MessageId,这个参数在多次投递的过程中是不会改变的,所以业务上可以用这个MessageId来作为判断幂等的关键依据。

但是,这个MessageId是无法保证全局唯一的,也会有冲突的情况。所以在一些对幂等性要求严格的场景,最好是使用业务上唯一的一个标识比较靠谱。这个id可以使用分布式中间件redis,zookeeper等去生成。例如订单ID。而这个业务标识可以使用Message的Key来进行传递。

同时,建立一个消息表,拿到这个消息做数据库的insert操作。给这个消息做一个唯一主键(primary key)或者唯一约束,那么就算出现重复消费的情况,就会导致主键冲突,那么就不再处理这条消息。

消息持久层做消息唯一性的策略(该方案未经证实)

  1. 持久化过程中业务唯一标识验证,每个消息具有业务唯一标识,在消息最终持久化之前通过验证唯一性标识保证消息的唯一性。消息持久化位置如果出现同样的消息,系统就不做处理,期间无任何传递过程,保证消息的唯一性。
  2. 使用过程中业务唯一标识验证,使用过程中如果出现同样的消息,系统进行相应的异常处理。
### RocketMQ 消息重复消费的最佳实践 #### 1. 使用事务消息机制 为了确保消息不会被重复消费,可以采用 RocketMQ 的事务消息机制。该特性允许发送方在提交消息之前执行本地事务逻辑,从而保证消息的幂等性[^1]。 ```java public class TransactionProducer { public static void main(String[] args) throws MQClientException { // 实例化事务生产者并指定组名 TransactionMQProducer producer = new TransactionMQProducer("please_rename_unique_group_name"); // 设置NameServer地址 producer.setNamesrvAddr("localhost:9876"); // 注册本地事务监听器 producer.setTransactionListener(new CustomTransactionListener()); // 启动生产者实例 producer.start(); try { // 创建消息对象 Message msg = new Message("TopicTest", "TagA", "Hello".getBytes(RemotingHelper.DEFAULT_CHARSET)); // 发送半消息(即准备阶段的消息) SendResult sendResult = producer.sendMessageInTransaction(msg, null); System.out.printf("%s%n", sendResult); } catch (MQClientException | UnsupportedEncodingException e) { e.printStackTrace(); } } private static class CustomTransactionListener implements TransactionListener { @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { // 执行本地业务操作... return LocalTransactionState.COMMIT_MESSAGE; } @Override public boolean checkLocalTransaction(MessageExt msg) { // 检查本地状态... return true; } } } ``` #### 2. 开启严格顺序消息模式 当开启严格顺序消息模式时,RocketMQ 可以按照特定键来保持消息传递顺序不变。这有助于减少由于乱序而导致的消息重复现象。 ```properties # consumer.properties 文件中的设置项 consumer.orderly=true ``` #### 3. 设定合理的试策略 通过调整消费者的 `maxReconsumeTimes` 参数,控制最大试次数;同时合理配置 `suspendCurrentQueueTimeMillis` 来延缓下一次拉取时间间隔,降低因网络抖动等原因造成的短暂不可达情况下的误判概率。 ```xml <!-- rocketmq-client-config.xml 中的相关节点 --> <bean id="defaultMQPushConsumerImpl" class="org.apache.rocketmq.client.consumer.DefaultMQPushConsumer"> <!-- ...其他属性... --> <property name="maxReconsumeTimes" value="10"/> </bean> ``` #### 4. 应用端实现幂等设计 即使采取上述措施也无法完全杜绝所有可能引起重复的情况,在应用层面上也应当考虑如何处理潜在存在的重复事件。比如可以通过唯一标识符记录已处理过的请求,并忽略后续相同ID的数据包[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值