MQ消息幂等

MQ消息幂等性设计

一、缘起

MQ消息必达,架构上有两个核心设计点:

(1)消息落地

(2)消息超时、重传、确认

 

再次回顾消息总线核心架构,它由

发送端、服务端、固化存储、接收端

四大部分组成。

为保证消息的可达性,超时、重传、确认机制可能导致消息总线、或者业务方收到重复的消息,从而对业务产生影响。

举个栗子:

购买会员卡,上游支付系统负责给用户扣款,下游系统负责给用户发卡,通过MQ异步通知。不管是上半场的ACK丢失,导致MQ收到重复的消息,还是下半场ACK丢失,导致购卡系统收到重复的购卡通知,都可能出现,上游扣了一次钱,下游发了多张卡。

消息总线的幂等性设计至关重要,是本文将要讨论的重点。

二、上半场的幂等性设计

 

MQ消息发送上半场,即上图中的1-3

1,发送端MQ-client将消息发给服务端MQ-server

2,服务端MQ-server将消息落地

3,服务端MQ-server回ACK给发送端MQ-client

如果3丢失,发送端MQ-client超时后会重发消息,可能导致服务端MQ-server收到重复消息。

此时重发是MQ-client发起的,消息的处理是MQ-server,为了避免步骤2落地重复的消息,对每条消息,MQ系统内部必须生成一个inner-msg-id,作为去重和幂等的依据,这个内部消息ID的特性是:

(1)全局唯一

(2)MQ生成,具备业务无关性,对消息发送方和消息接收方屏蔽

有了这个inner-msg-id,就能保证上半场重发,也只有1条消息落到MQ-server的DB中,实现上半场幂等。

三、下半场的幂等性设计

 

MQ消息发送下半场,即上图中的4-6

4,服务端MQ-server将消息发给接收端MQ-client

5,接收端MQ-client回ACK给服务端

6,服务端MQ-server将落地消息删除

需要强调的是,接收端MQ-client回ACK给服务端MQ-server,是消息消费业务方的主动调用行为,不能由MQ-client自动发起,因为MQ系统不知道消费方什么时候真正消费成功。

如果5丢失,服务端MQ-server超时后会重发消息,可能导致MQ-client收到重复的消息。

此时重发是MQ-server发起的,消息的处理是消息消费业务方,消息重发势必导致业务方重复消费(上例中的一次付款,重复发卡),为了保证业务幂等性,业务消息体中,必须有一个biz-id,作为去重和幂等的依据,这个业务ID的特性是:

(1)对于同一个业务场景,全局唯一

(2)由业务消息发送方生成,业务相关,对MQ透明

(3)由业务消息消费方负责判重,以保证幂等

最常见的业务ID有:支付ID,订单ID,帖子ID等。

具体到支付购卡场景,发送方必须将支付ID放到消息体中,消费方必须对同一个支付ID进行判重,保证购卡的幂等。

有了这个业务ID,才能够保证下半场消息消费业务方即使收到重复消息,也只有1条消息被消费,保证了幂等。

三、总结

MQ为了保证消息必达,消息上下半场均可能发送重复消息,如何保证消息的幂等性呢?

上半场

MQ-client生成inner-msg-id,保证上半场幂等。

这个ID全局唯一,业务无关,由MQ保证。

下半场

业务发送方带入biz-id,业务接收方去重保证幂等。

这个ID对单业务唯一,业务相关,对MQ透明。

结论:幂等性,不仅对MQ有要求,对业务上下游也有要求。



作者:duzhongli
链接:https://www.jianshu.com/p/8b77d4583bab
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

### MQ 消息队列中的等性处理 为了确保消息队列(MQ)中消息等性,即防止重复消费带来的副作用,在设计系统时需考虑多种技术手段。当前流行的几种MQ如RocketMQ、Kafka、RabbitMQ、ActiveMQ等都采用至少一次交付机制(at least once delivery)[^1],这意味着每条消息会被成功发送给订阅者至少一次,但也可能多次。 #### 使用 Redis 缓存作为去重工具 一种常见的做法是在接收端引入外部存储服务——例如Redis缓存——用于记录已处理过的消息ID或其他唯一标识字段。当接收到新消息时先查询此缓存;如果存在,则认为该消息已被处理过而忽略之;反之则正常执行业务逻辑并更新缓存状态[^3]。 ```java // Java伪代码展示如何利用Redis实现简单的等性检查 public void processMessage(String messageId, Message message){ Jedis jedis = new Jedis("localhost"); String key = "processed:" + messageId; if (!jedis.exists(key)){ try { // 执行实际业务操作... // 设置key有效期为一天 jedis.setex(key, 86400, "true"); } finally { jedis.close(); } } } ``` #### 数据库层面设置唯一键约束 对于某些特定类型的业务数据来说,可以在数据库表结构上直接设定相应的唯一索引来达到相同的效果。每当尝试插入一条新的交易记录之前都会自动触发数据库级别的验证流程,以此杜绝因网络波动等原因造成的重复提交现象发生。 ```sql CREATE TABLE orders ( id INT NOT NULL AUTO_INCREMENT, order_no VARCHAR(50), ... PRIMARY KEY (id), UNIQUE INDEX idx_order_unique(order_no) ); ``` #### 应用层内嵌入等令牌(Token) 除了依赖第三方组件外,还可以在应用程序内部构建一套完整的等控制系统。通常情况下会涉及到生成唯一的等Token,并将其附带至每次请求当中。服务器端在接受到客户端发来的指令之后便会依据这个特殊参数来进行判断是否存在未完成的任务实例正在等待确认或是已经完成了相应的工作流节点。 ```python import uuid def generate_idempotent_token(): return str(uuid.uuid4()) class IdempotencyHandlerFactory: handlers = {} @staticmethod def register_handler(event_type, handler_class): IdempotencyHandlerFactory.handlers[event_type] = handler_class @staticmethod def get_handler(event_type): return IdempotencyHandlerFactory.handlers.get(event_type) ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值