接口的幂等性设计


参考资料:

【IT老齐256】工作常用的四种接口幂等性方案_哔哩哔哩_bilibili

接口的幂等性设计和防重保证,详细分析幂等性的几种实现方法-阿里云开发者社区

设计核心接口的防重幂等性

接口幂等性——防止并发重复插入数据_内存数据表 version 并发插入数据-CSDN博客

1 定义

1.1 接口的幂等性

数学上,下图所示的公式就是幂等函数。

在程序上我们也定义成Handle(Handle(Request)) = Handle(Request)。也就是对同一个请求执行一次或者多次对数据结果的影响和返回结果的影响是一致的。

这个的关键点有点:

  1. 同一个请求
  2. 对数据的影响
  3. 返回结果

所以要保证接口是幂等接口,肯定要可以判定是否是同一个请求、以及如何保证对数据的影响是一致的、以及如何保证返回结果是一样的。

1.2 接口的防重

同一个请求的多次执行,接口可以保证数据在业务上是一致的。

1.3 接口并发冲突问题

同时需要处理同一个 共享数据 时,怎么样才能保证数据处理不出错误。

例如两个线程要把同一个账户的余额减小10元,如果采用先获取余额,减去10元后再set到DB中的方式,就可能出现数据不一致。

2 区别

我也不知道啥区别,网上说的太乱,感觉也不对。

3 产生多次请求的场景

注意:接口是否需要幂等性需要根据业务分析。

也许是我理解的不够深,我感觉能对重复请求正确处理就好了,程序返回值【多次的返回结果一致】无法满足接口的幂等性的定义。例如假设A和a是同一个请求,A处理到一半的时候,a检测到已经有A在处理,此时a能咋办呢?

1.直接返回。直接返回的话到底是返回成功还是失败呢?因为不知道A的处理结果是啥,所以返回成功或者失败都是错的。
2.等待A完成后返回。那就要一种机制可以获取到A的处理结果。

对于业务中需要考虑幂等性的地方一般都是接口的重复请求,重复请求是指同一个请求因为某些原因被多次提交。导致这个情况会有几种场景:

  • 前端重复提交:提交订单,用户快速重复点击多次,造成后端生成多个内容重复的订单。
  • 接口超时重试:对于给第三方调用的接口,为了防止网络抖动或其他原因造成请求丢失,这样的接口一般都会设计成超时重试多次。
  • 消息重复消费:MQ消息中间件,消息重复消费。
  • 网络问题导致的重复请求:如果是由于网络问题导致的请求重复发送。

注意重复请求时间维度上的两种场景:

  1. 几乎同一时刻的重复请求。例如10点的一个请求,10点过1ms的请求。
  • 这种情况服务端几乎是在同时处理两个相同的请求。
  1. 不同时刻的请求。例如10点整的一个请求,10点过1s的请求。
  • 这种情况服务端几乎是在不同时间处理两个相同的请求。

4 如何保证接口幂等性

4.1 token机制

  • 先请求token,把token存到redis中,并设置一个合理的过期时间,防止仅仅获取token,却不请求接口,导致redis中存储大量没有用的数据。合理的过期时间也是需要远大于处理业务的时间的。
  • 带上token请求接口,删除redis中的token,删除失败则直接返回,删除成功则进行下一步操作。
    • 例如删除不存在的key返回0。删除存在的key返回1。
4.1.1 流程

  1. 服务端提供了发送token的接口,我们在分析业务的时候,哪些是存在幂等问题的,就必须在执行业务前,前去获取token,服务器会把token保存到redis中;
  2. 然后调用业务接口请求时,把token携带过去,一般反正请求头部;
  3. 服务器判断token是否存在redis中,存在表示第一次请求,可以继续执行业务,业务完成后,需要把redis中的token删掉
  4. 如果判断token不存在redis中,就表示是重复操作,直接返回重复标记给client,这样就保证了业务代码,不被重复执行。

上面的方式是检验token存在后,先进行业务处理,再删除token。

  • 问题1 是如果进行业务处理成功后,删除redis中的token失败了,这样就导致了有可能会发生重复请求,因为token没有被删除。
  • 问题2 是高并发场景下,多个请求都可以判断出key是存在的。因此,这个方法不可用。

还有一种方式是直接删除token。删除不存在的key返回0。删除存在的key返回1。

  • 问题是先删除token,如果出现系统问题导致业务处理出现异常,业务处理没有成功,接口调用方也没有获取到明确的结果,然后进行重试,但token已经删除掉了,服务端判断token不存在,认为是重复请求,就直接返回了,无法进行业务处理了。
4.1.2 token机制缺点

业务请求每次请求,都会有额外的请求(一次获取token请求、判断token是否存在的业务)。其实真实的生产环境中,1万请求也许只会存在10个左右的请求会发生重试,为了这10个请求,我们让9990个请求都发生了额外的请求。(当然redis性能很好,耗时不会太明显)

4.2 去重表机制

往去重表里插入数据的时候,利用数据库的唯一索引特性,保证唯一的逻辑。唯一序列号可以是一个字段,也可以是多字段的唯一性组合。

这里要注意的是,去重表和业务表应该在同一库中,这样就保证了在同一个事务,即使业务操作失败了,也会把去重表的数据回滚。这个很好的保证了数据一致性

4.2.1 缺点

使用数据库防重表的方式它有个严重的缺点,那就是系统容错性不高,如果幂等表所在的数据库连接异常或所在的服务器异常,则会导致整个系统幂等性校验出问题。

但是如果发生了这样的问题,可能问题的重点已经不是这个接口了,而是整个系统了

4.3 乐观锁/状态机【注意需要再请求参数里就携带状态或者版本】

乐观锁解决了计算赋值型的修改场景。例如:

UPDATE user SET point = point + 20, version = version + 1 WHERE userid = 1 AND version = 1

UPDATE user SET detail = detail, status = 'CREATED' WHERE userid = 1 AND status = 'NEW'

加上了版本号后,就让此计算赋值型业务,具备了幂等性。

4.3.1 乐观锁缺点

在发起请求前,需要先查询出当前的version版本。

4.3.2 错误的乐观锁方案:

该方案只能处理并发冲突,接口防重和接口幂等性都解决不了。

4.4 唯一索引【和去重表原理一样】

4.5 错误的方案

4.5.1 insert前先select

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

该方案可能是我们平时在防止产生重复数据时,使用最多的方案。但是该方案不适用于并发场景,在并发场景中,要配合其他方案一起使用(比如分布式锁),否则同样会产生重复数据。

4.5.2 悲观锁以及不正确的使用乐观锁

这两个可以防止同一个时刻的并发操作,但是不可以防止不同时刻的数据更改。

如下两种方法。

5. 案例:修改微信用户的昵称

假设一个微信用户一天可以修改10次。

为了防止出现ABA的现象,DB使用版本号的乐观锁形式实现。用户修改次数单独使用redis存储,string存储,过期时间大于1天。

同时可能存在网络问题导致重复提交,因此乐观锁形式实现也能解决。
画板

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值