1.什么是幂等性
幂等:是一个数学与计算机的概念,在编程领域中,一次幂等操作:用相同参数重复执行,并能获得相同结果的。
任意多次执行所产生的影响均与一次执行的影响相同,这是幂等性的核心特点。
2.业务场景
- 前端重复提交:提交订单,用户因网络延迟连续、快速提交造成后端生成多次重复的记录。
- 消息重复消费:MQ消息中间件,消息重复消费。
- 接口超时重试:对于给第三方调用的接口,为了防止网络抖动或其他原因造成请求重试,导致一个请求提交多次。
3.接口幂等性
说明:对于业务中需要考虑到幂等性操作,一般都是接口的重复请求,一个请求因特殊原因被多次请求。其中某一次处理开始或者成功了,我虽然又接收到了消息,但是这时我不处理了,即保证接口的 幂等性。
接口幂等性:指的是用户对同一操作发起了一次或多次请求的结果是一样的,不会新多次请求而对系统产生影响。这类问题多发生于接口的新增insert
和修改update
操作,查询select
与删除delete
操作具有天然幂等性,逻辑删除也具备幂等性。
- insert操作:多次请求插入多条数据
- update操作:计算式不具备幂等性
update goods set number=nember-1 where id=1
非计算式具备幂等性update goods set number = 1 where id=1
3.1 实现方式
没有最优的方案只有最适合的:
实现接口幂等的方式多种,我们在判断业务需要幂等操作时要结合具体情况使用。
防重token机制:对于需要幂等性操作的接口,在进入页面后会请求一个token
值,在用户提交表单时会携带服务端返回的token
标识,该token
在同一次请求下不会发生改变。
流程如下:
- 服务端提供生成唯一
token
的接口,并将该token
存放在redis
中。 - 携带着
token
调用业务接口,业务内部会进行token
校验。 - 业务通过查询
redis
判断token
是否存在,存在就删除token
继续执行业务(保证原子操作)。 - 判断token不存在,那么返回给客户端重复业务操作。
· 通过 Token 的机制可以解决重复下单,重复提交等场景。
唯一约束性: 利用数据库中唯一索引的约束进行去重,适用于insert
操作时的幂等性,能保证一张表中只能存在一条带该唯一性约束的数据。需要注意的是,该约束字段一般来说使用分布式 ID
充当主键,这样才能能保证在分布式环境下 ID
的全局唯一性。
分布式ID的生成可以使用雪花算法、redis
原子自增等方式生成唯一。
流程如下:
- 客户端调用创建请求接口
- 服务端执行业务,生成一个分布式
ID
,将该ID作为唯一约束字段的数据执行SQL
操作。 - 插入成功表示没有重复调用,如果抛出异常则表示存在该条数据,返回错误信息给客户端。
状态机:很多的业务是有一个业务流转状态的,每个状态都有前置和后置状态、最终状态。比如审批流程状态,订单状态。以订单为例,*已支付* 的前置状态只能是未支付 ,取消 的前置状态只能是 未支付 通过这种状态的变更我们可以控制幂等性操作。假设当前状态是已支付,这时候如果支付接口又接收到了支付请求,我们更新订单的状态,订单条件必须是未支付 ,但实际不是,则会抛异常或拒绝此次处理,达到幂等性。
乐观锁:乐观锁方案一般只能适用于执行更新操作的过程,我们可以提前在对应的数据表中多添加一个字段version
,充当当前数据的版本标识。每次对该条数据执行更新时,都会将该版本标识作为一个条件,值为上次待更新数据中的版本标识的值。它的执行流程与token机制类似,需要先获取数据的版本号,不推荐。
UPDATE table_name SET stock = stock-1,version = version+1 WHERE id = 1 AND version = 2
上面 WHERE 后面跟着条件 id = 1 AND version = 2 被执行后,id=1 的 version 被更新为 3,所以如果重复执行该条 SQL 语句将不生效,因为 id = 1 AND version = 2 的数据已经不存在,这样就能保正更新的幂等性,多次更新对结果不会产生影响。
3.2 幂等性的影响
幂等性一方面为了保证接口的安全性,从而保证了数据的一致性,另一方面也增加了业务逻辑的复杂性,增加了额外控制幂等的业务逻辑,复杂化了业务功能。所以在使用时候需要考虑是否引入幂等性的必要性,根据实际业务场景具体分析,除了业务上的特殊要求外,一般情况下不需要引入的接口幂等性。
3.3 结论
一个API拥有幂等能力的话,调用发起方就可以很安全的进行重试。这符合我们普遍的要求,也是开发当中很常见也很重要的一个需求,尤其是支付、订单等与金钱挂钩的服务,保证接口幂等性尤其重要。一个接口要不要保证幂等的条件是需不需要保证该资源的唯一性
- 类似于前端重复提交、重复下单的场景,可以通过 Token 与 Redis 配合的“防重 Token 方案”实现更为快捷。
- 对于更新订单状态等相关的更新场景操作,使用“乐观锁”实现更为简单。
- 对于下单,注册等存在唯一约束的,可以使用“唯一索引约束”的方式实现。