29. 消息幂等:如何保证消息不被重复消费

2023-07-13  本文已影响0人  木叶苍蓝

应用的幂等是在分布式系统设计时必须要考虑的一个方面。在消息队列应用中,如何处理因为重复投递等原因导致的幂等问题。

对业务幂等的理解

HTTP 协议中定义了交互的不同方法
比如 GET和POST以及 PUT , DELETE 等
其中 GET, DELETE 等方法都是幂等的,而 POST 方法不是。

注意:业务上的幂等指的是操作不影响资源本身,并不是每次读取的结构都保证一致

比如通过 GET 接口查询一条订单记录,在多次查询的时间段内订单状态可能会有新的更新而发生变化,查询到的数据可能不同,但是读接口本身仍然是一个幂等的操作。

在业务开发中对数据的操作主要是 CRUD
即在做数据处理时的 Create,Read,Update,Delete 这几种操作
Create 操作不是幂等的
Update 操作可能幂等也可能不幂等

UPDATE order SET status=1 WHERE id = 100;   # 幂等操作

UPDATE order SET price  = price + 1 WHERE id = 100;  # 不是幂等操作

各类中间件对幂等性的处理

使用 binlog 分发进行数据同步,如果数据库更新消息被多次消费可能导致数据的不一致。

远程服务调用的幂等问题

远程服务调用出现失败,一般都是通过配置重试,保证请求调用成功率,提高整体服务的可用性
Apache Dubbo 支持多种集群容错的方式,可以针对业务特性,配置不同的失败重试机制
包括 Failover 失败自动切换,Failsafe 失败安全,Failfast 快速失败等

Dubbo 的容错机制考虑了多种业务场景的需求
根据不同的业务场景,可以选择不同的容错机制,进而有不同的重试策略,保证业务正确性。

消息消费中的重试问题

消息队列的消息发送重试和微服务中的失败调用重试
都是通过重试的方式,解决网络抖动,传输不稳定等导致的偶发调用失败
需要从中间件和业务的不同层面,来保证服务调用的幂等性

消息投递的几种语义

在消费端也可以定义类似的消费语义
比如消费端保证最多被消费异常,至少被消费一次
这两种语义可以认为是同意给级别的两种描述

不同消息队列支持的投递方式

RocketMQ 支持 At least once 的投递语义
通过消费端消费的 ACK 机制来实现:
在消息消费过程中,消费端在消息消费完成后,返回 ACK
如果消息已经 pull 到本地,但还没消费,则不会返回 ACK 响应

在业务上应用 RocketMQ 时,可以根据不同的业务场景实现其他级别的投递语义,比如最多送达一次等。

业务上如何处理幂等

消息消费的幂等本质上是已给系统设计的问题
消息队列是为了实现系统目标而引入的手段之一
并且分布式消息队列天然存在消费时序,消息失败重发等问题

天然幂等不需要额外设计

有部分业务是天然幂等的
这部分业务,允许重复调用,即允许重试
在配置消息队列时,可以通过合理的重试,来提高请求的成功率

利用数据库进行去重

要根据订单流转的消息在数据库中写一张订单 Log 表
可以把订单 ID 和修改时间戳做一个唯一索引进行约束
当消费端消费消息出现重复投递时,会多次去订单 Log 表中进行写入
除第一条之外,后面的都会失败

设置全局唯一消息 ID 或者任务 ID

在消息投递时,给每个业务消息附加一个唯一的消息 ID
就可以在消费端利用类似分布式锁的机制,实现唯一性的消费
消息被消费后,在缓存中设置一个 Key 对应的唯一 ID,代表数据已经被消费
当其他的消费端去消费时,可以根据这天记录,来判断是否已经处理过

上一篇 下一篇

猜你喜欢

热点阅读