抢微信红包的时候我们都知道: 一个红包一旦你抢过之后, 以后无论你点多少次都是一样的结果. 红包会提示你已经抢过此红包, 而不会让你再抢一次.
抢红包接口就是一个非常典型的幂等接口, 抢一次和抢多次具有一样的效果. 类似的接口在我们的开发过程中会有很多, 比如在电商的下单过程中:
订单创建接口, 第一次调用返回超时了, 重试机制一般会再次调用这个接口, 此时我们不能因为这个接口被调了两次就创建两个一样的订单;
库存扣减接口, 支付接口也是类似的逻辑;
今天的文章就来讲讲什么是接口的幂等性, 并介绍几种实现接口幂等性的方案.
什么是幂等
幂等原先是数学中的一个概念, 表示进行 1 次变换和进行 N 次变换产生的效果相同.
当我们讨论接口的幂等性时一般是在说: 以相同的请求调用这个接口一次和调用这个接口多次, 对系统产生的影响是相同的. 如果一个接口满足这个特性, 那么我们就说这个
接口是一个幂等接口. 比如上面的抢红包接口.
PS: 这边顺带说下幂等和防止重复提交的区别.
防止重复提交更多的是不让用户发起多次一样的请求. 比如说用户在线购物下单时点了提交订单按钮, 但是由于网络原因响应很慢, 此时用户比较心急多次点击了订单提交按钮.
这种情况下就可能会造成多次下单. 一般防止重复提交的方案有: 将订单按钮置灰, 跳转到结果页等. 主要还是从客户端的角度来解决这个问题.
幂等更多的是在重复请求已经发生, 或是无法避免的情况下, 采取一定的技术手段让这些重复请求不给系统带来副作用.
什么情况下需要幂等
并不是所有接口都需要保证幂等性. 以相同的请求调用这个接口一次或多次, 需要给调用方返回一致的结果时, 就要考虑将这个接口设计成幂等接口.
实现幂等的几种方案
在我们设计幂等接口时重点关注新增接口和更新接口. 因为查询和删除操作天生是幂等的 (根据 id 查询和根据 id 删除多次对系统的影响是一致的), 不需要我们提供额外的
技术手段来保证幂等性.(??)
对于新增和更新接口, 大致有以下几种方案可以保证接口幂等性.
来源加序列号
这是一种比较好理解, 通用的方案.
当调用接口时, 参数中必须传入 source 字段和 seq 字段 (这边举了一个我们项目中的列子, 其实并不一定要传两个字段, 传一个唯一的序列号 uuid 也能达到一样的效果). 服务端接收到请求, 先判断自己是否是一个幂等接口, 如果不是幂等接口就正常处理请求.
如果是一个幂等接口, 就将 source 和 seq 组成联合主键去数据库表中或者是 Redis 中查询, 如果没有查询到, 说明没处理过这个请求, 然后正常处理请求就行了. 处理完之后将处理结果和 source 和 seq 信息一个存入数据库或 Redis 中.
如果根据 source 和 seq 能查询到, 说明已经处理过这个请求了, 直接将处理的结果返回即可.
我们发现这种方案非常简单, 而且易于理解, 通用. 但是如果请求量很大的话, 存放请求记录的表会很大, 这个时候可以将一段时间之前的记录删除, 以提升性能.
唯一索引 (唯一字段)
这种方案适合用于执行新增操作的接口.
比如说新增用户接口. 我们将用户表中的身份证字段加上唯一索引. 当同一个请求调用两次时, 我们可以先根据身份证字段查询下用户是否存在, 不存在的话再新增. 存在的话就返回新增失败.
或者直接新增也行, 数据库会抛异常, 我们对异常处理返回前台就行了.
PS: 大家可能会有一个疑问, 我同一个请求调用两次, 第一返回新增成功, 第二次返回失败, 返回的结果不同啊. 这个接口还是幂等接口么?
这边我要重申下概念, 幂等强调的是接口一次调用和多次调用产生的效果是一样的. 这边调用一次和调用多次都是新增了一个对象, 所以还是满足幂等的.
乐观锁
这种方案适用于执行更新操作的接口.
乐观锁只是在更新数据那一刻锁表, 其他时间不锁表, 所以相对于悲观锁, 效率更高. 我们一般通过数据库来实现乐观锁, 比较通用的做法是增加一个时间戳字段.
update table_xxx set name=#name#, timestamp = now where id=#id# and timestamp=#timestamp# -- 这个值由前端到数据中查询出来, 再传过来
参考
分布式系统的接口幂等性设计
如何避免下重复订单 https://www.jianshu.com/p/e618cc818432
公众号推荐
来源: https://www.cnblogs.com/54chensongxia/p/12598944.html