最近想着给公司优化下接口,发现好多连幂等性都没有做处理,特别是下单、支付、退款这些接口,瑟瑟发抖。准备改造一波!
什么是幂等性
幂等性:(Idempotence)。首先幂等性是数学和计算机科学中某些操作的特性,什么特性呢?就是如果使用相同的输入参数多次调用它,则不会产生额外的影响。
A request method is considered “idempotent” if the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request. (RFC 7231)
举个例子:
对于转账,因为种种原因对同一个请求进行了多次发送多次执行,其结果只会成功转账一次,其他都不会生效。
幂等性带来优劣
实现幂等性带来的好处: 可以解决因为某些原因请求多次提交,产生多次影响结果的情况。比如 下单、请求支付、转账、消息发送等等场景
实现幂等性带来的坏处: 增加了实现的复杂度,复杂了原来的业务逻辑同时还增加了运维的成本
如何实现
以我们公司的退款(退积分)实现为例来讲下怎么实现幂等性操作。
网上提供了好多种方式,大概总结了下,分别说下每种实现存在的一些问题
使用悲观锁
实现:请求进入的时候开启事务,然后查询订单并加锁,然后判断订单是否符合条件等等,符合进行账户余额累加这里也要加锁,在全部执行完提交或回滚
缺点:中间环节可能会执行时间很长,容易产生锁住整表服务挂掉等
使用乐观锁
实现:乐观锁在大部分时间是不会锁表的,只有在更新的时候才会锁表。一般是通过版本号来实现的。
比如在账户表中增加 version
字段当作版本号。在进行退款的时候传递 version 字段给服务器。然后退款操作中判断订单状态,符合进行退款,在更新账户余额的时候 使用 update table set amout=amout+xxx,version=version+1 where uuid = xxx and version = xxx
。
即使多个重复请求进来,因为进来的version是一样的,而此时数据库中对应数据的version已经变了,已经被上一个成功执行的结果给+1了,所以那个修改条件是不成立的。也就不会更新数据进行多次增加金额
缺点:要记得使用主键或唯一索引来更新,这时是行锁不然容易变成表锁。
防重表
实现:建立一张防重表,比如可以使用订单号为唯一索引。在请求退款的时候根据订单号向防重表中添加一条订单退款记录。因为是唯一索引,所以当重发的请求进来的时候添加订单记录的话就会失败,这个时候就可以返回操作失败给前端。而第一个进入的可以完成退款操作,在退款完成后删除防重表中的记录。
缺点:多维护一个表,业务逻辑变的复杂许多。
使用token
实现:在每次退款之前需要去申请一个token,然后把token缓存起来,在发起退款的时候携带token回来,服务器校验token是否存在,存在的话就删除token,然后进行退款,流程结束。而多进入的请求因为token失效而不会执行退款操作。
缺点:流程比上面防重表更复杂
实现过程
本着不折腾不快乐的精神,我决定使用 Token+Redis 的是实现方式来改进代码.下面是一些伪代码实现
1.实现生成Token方法
|
|
2.修改退款逻辑
|
|
参考
What is an idempotent operation?