幂等性的定义
接口存在的问题
现如今的系统大多数拆分成分布式、微服务。一套系统由几个子系统组成,而子系统也会进行互相调用,既然进行互相调用,有可能出现服务器返回结果时挂掉,客户端很久没有反应,这时候会选择点击多次,发送请求,如果这样做的话,我们的初衷是数据处理的结果是一致的,但事实真的是如此吗?
什么是接口幂等性
接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了不同的结果。举个例子,当人们购买商品付款时,服务器接收请求,钱包扣款成功,但却在返回客户端时失败了,这时候人们的选择是再付一次款,设定这次成功,但却扣了两次款,产生了两条流水记录,这便违反了接口的幂等性。
需要幂等的场景
- 网络波动的原因,重复请求
- 用户的重复操作
- 未关闭的重试机制,例如Nginx重试或业务层重试
“天然”幂等以及“人工”幂等
“天然”幂等
CRUD分析
| 操作 | 幂等性 |
|---|---|
| 新增类请求(C) | 数据库自增主键,不具备幂等性 |
| 查询类动作(R) | 重复查询不会产生或变更新的数据,因此查询是天然具备幂等性 |
| 基于主键的计算式更新(U) | 不具备幂等性,即:UPDATE goods SET number=number-1 WHERE id=1 |
| 基于主键的非计算式更新(U) | 具备幂等性,即:UPDATE goods SET number=3 WHERE id=1 |
| 基于主建的删除(D) | 具备幂等性 |
| 业务层面都是逻辑删除(即Update操作)(U) | 具备幂等性 |
RESTful
| 方法 | 幂等性 | 对应CRUD操作 |
|---|---|---|
| POST | 不幂等 | c |
| GET | 幂等 | R |
| PUT | 幂等 | U |
| DELETE | 幂等 | D |
| PATCH | 不幂等 | U |
GET、PUT、DELETE都是幂等操作,而POST不是,以下进行分析:
首先GET请求很好理解,是对资源进行多次查询。
PUT请求可以认为将A修改成B,接下来一直修改,都是为B,与之前结果一致,属于基于主键的非计算机更新。
DELETE请求则是将资源删除后,后面多次删除这条数据,结构一致。
PATCH请求是基于主键的计算式更新。
“人工”幂等
POST请求不是幂等操作,一次请求就添加一次数据,如果需要在POST请求时加上人为的幂等,下面我就来说说幂等实现的方法。
幂等实现方法
全局唯一ID
首先先暴露一个获取token的接口,在需要保证幂等的每次请求前创建一个token,存入redis,设置失效时间,请求时将token放入header或者作为参数传入接口,接口判断是否存在token以及是否失效,若存在并且有效,则正常处理业务逻辑,并从redis删除此token,这时一定要记住之后要判断是否删除成功,否则可能出现先返回,再删除的情况,如果重复请求的话,由于token已经被删除,则不能通过校验,返回请勿重复操作。
但由于上面方法虽然优美,但实现起来比较麻烦,推荐使用Spring AOP进行切面编程。
去重表
这种方法适用于业务中有唯一标识的场景。假定以上场景,一个订单对应一个唯一的订单号,一个订单支付一次,这时候订单号可作为唯一标识,并且把订单号作为唯一索引(查出来只取符合条件的一条数据),将订单号写入去重表的操作,放入一个事务当中,如果订单重复创建,数据库会抛出唯一约束,事务进行回滚。这其实也用到的唯一ID,但与全局唯一ID相比,只是单单针对一个业务流程而已。
状态机控制
状态机是一种数学模型,通常体现为一个状态转换图,可以理解为自动门有两个状态,open和closed,closed状态下,如果读取开门信号,那么状态就会切换为open。open状态下如果读取关门信号,状态就会切换为closed。
我们可以设置状态字段时,使用int类型,并且通过值类型的大小来做幂等,比如订单的创建为0,付款成功为1,付款失败为-1。在做状态机更新时,我们就这可以这样控制:
update goods_order set status = #{status} where id = #{id} and status = #{status}
以上就是保证接口幂等性的一些方法,但在并发下仍旧会有问题,需要使用锁来控制同步访问。
总结
幂等性不能脱离业务讨论,要结合具体情况,具体分析。
以上就是我的学习成果,如果有什么我没有思考到的地方或是文章内存在错误,欢迎与我分享。