幂等实现方案

2023/2/17

# 背景

基本上所有业务系统中的幂等都是各自进行处理,也不是说不能统一处理,统一处理的话需要考虑的内容会比较多。 核心的业务还是适合业务方自己去处理,比如订单支付,会有个支付记录表,一个订单只能被支付一次,通过支付记录表就可以达到幂等的效果。还有一些不是核心的业务,但是也有幂等的需求。比如网络问题,多次重试。用户点击多次等场景。这种场景下还是需要一个通用的幂等框架来处理,会让业务开发更加简单

# 幂等性定义

用户对于同一操作发起的一次请求或者多次请求的结果是一致的 有些操作是天然幂等的:

// 查询SQL
// 删除SQL
// 部分更新SQL(如:UPDATE tab1 SET col1 = 1 WHERE col2 = 2)

有些操作要注意幂等:

// 插入SQL
// 部分更新SQL(如:UPDATE tab1 SET col1 = col1 + 1 WHERE col2 = 2)

# 幂等场景

  • 有时我们在填写某些form表单时,保存按钮不小心快速点了两次,表中竟然产生了两条重复的数据,只是id不一样
  • 我们在项目中为了解决接口超时问题,通常会引入了重试机制。第一次请求接口超时了,请求方没能及时获取返回结果(此时有可能已经成功了),为了避免返回错误的结果,于是会对该请求重试几次,这样也会产生重复的数据
  • mq消费者在读取消息时,有时候会读取到重复消息,如果处理不好,也会产生重复的数据

# 幂等方案

# insert前先select

通常情况下,在保存数据的接口中,我们为了防止产生重复数据,一般会在insert前,先根据name或code字段select一下数据。如果该数据已存在,则执行update操作,如果不存在,才执行insert操作

该方案可能是平时在防止产生重复数据时,使用最多的方案。但是该方案不适用于并发场景

# 锁 + select

# 唯一索引

# 使用去重表

例如唯一码收货,每扫一个唯一码,就发送事件给采购,收货单中就会多一个商品,然后通知交易已收货;如果采购长时间不通知交易,交易会重试,会有幂等问题;收货单中增加一个商品的同时,向去重表中插入一个唯一码,去重表唯一码是唯一索引

# 状态机

很多时候业务表是有状态的,比如订单表中有:1-下单、2-已支付、3-完成、4-撤销等状态。如果这些状态的值是有规律的,按照业务节点正好是从小到大,我们就能通过它来保证接口的幂等性

# token方案(update可以用这种方案)

这种方式分成两个阶段:申请token阶段和支付阶段。

  • 第一阶段:在进入到提交订单页面之前,需要订单系统根据用户信息向支付系统发起一次申请token的请求,支付系统将token保存到Redis缓存中,为第二阶段支付使用
  • 第二阶段:订单系统拿着申请到的token发起支付请求,支付系统会检查Redis中是否存在该token,如果存在,表示第一次发起支付请求,删除缓存中token后开始支付逻辑处理;如果缓存中不存在,表示非法请求