幂等 - 969251639/study GitHub Wiki

幂等是一个数学名称,转到程序设计中代表着不管接口请求多少次,响应都是一样的,它最常见的场景在于消息队列中的运用,消息队列经常有重发的可能,一旦消息重发,那么下游的消费者系统必须保证幂等消费,否则很容易造成数据不一致,下面是个人能想到的几种解决方案

1. 基于DB的去重表

该方案需要在消费者端创建一张去重表作为消息去重判断,记录消息在消费者端是否已接收或已ack,一般至少包含以下两个字段
msg_id: 消息id(primary key) status: 状态(1:已接收,2:消费成功,3:消费失败)

该方案产生的主要可能的场景如下

  1. 消息已接收,但消费执行很久,还没ack给服务器,服务器认为消费失败而进行重发
  2. 消息已接收,但ack给服务器过程中因网络原因造成服务器认为消费失败而进行重发
  3. 消息已接收,但消费失败
  4. 消息已接收,消费成功,ack成功

1和2是一样的由其他因素导致失败,3和4是正常消费情况
在接收消息时都将消息写入到本地库,然后每次消费时监控状态,只有消费失败时可以重新进入逻辑代码部分,对于已接收和消费成功状态则不处理

该方案的缺点很明显会增大DB的压力,且每个消费端都需要建立一张与业务无关的去重表

2. 基于DB的unique index

该方案主要依赖数据的唯一索引来做去重,但会造成每次都会重复消费,导致加大数据库压力,另外也需要在去重的表新增一个与业务无关的去重字段做唯一索引

3. 基于DB或Cache的版本号

该方案主要可以用DB或Redis缓存做一个版本号检查,设计如下: 生产者需要已业务为单位生成一个自增的数字(可以用分布式ID做自增服务)来做版本号,该版本号与业务无关,对于消费者端,需要将该消费的版本号缓存到DB或Cache中,如果低于或等于此版本号的消息忽略,只消费大于该版本号的消息,需要注意的是对于非成功场景,需要做以下的特殊处理,使用版本号绑定状态映射到DB或Cache中
version: 版本号
status: 状态(1:已接收,2:消费成功,3:消费失败)
另外为了版本号有序,可以使用Redis的ZSet作为数据结构,version为score,value为status
如果状态为成功则删掉该版本号即可