幂等:一次操作和多次操作的结果是一致的。
接口幂等性
是指用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。
防重设计主要为了避免产生重复数据,对接口返回没有太多要求。而幂等设计除了避免产生重复数据之外,还要求每次请求都返回一样的结果。
解决方案:
1、悲观锁
for update
for update 会从数据库中查询最新的数据,并且加上排他锁。其他没竞争到的会等待重试。
普通的select是查快照数据,不符合(说明钱已经转走了,被人加锁update过了),直接结束。
2、乐观锁
表中增加一个timestamp
或者version
字段,这里以version
字段为例。
先查询要修改表的版本号version,
再用包含这个version的请求去update数据库,并把version+1。
3、唯一索引
表中加唯一索引字段
4、建防重表
根据业务场景定义幂等的字段,如该表可以只包含两个字段:id
和 唯一索引
,唯一索引可以是多个字段比如:name、code等组合起来的唯一标识,例如:susan_0001。
5、状态机
很多时候业务表是有状态的,比如订单表中有:1-下单、2-已支付、3-完成、4-撤销等状态。
如在支付场景中,订单的状态只能从上一级状态修改到下一级状态。
update操作,数据库会加上悲观锁(行锁),锁住当前行。(update要改的数据前会先select * from ........ for update,此时只有一个请求能获取到这把锁)
例如有多个请求过来,只有一个请求能获取到这把锁,再把数据更新为2 -> 3,释放锁。后面的请求就查不到id123 且status为2的数据了。
这个与redis分布式锁很相似。
6、分布式锁
数据库的分布式锁性能不好,比如可以用redis实现。
比如可以吧唯一标识的订单号、token、唯一索引这些放入redis的中,实现分布式锁。
7、token + redis
分为两步操作
1、 先获取token并存放到redis中
2、redis查询toeken信息是否存在,存在表示是第一次操作,进行业务操作(可以选择在进行业务前后进行token删除。先删除token会好点,失败的业务用户可以重新获取token补偿)。这里应该还会用当前的token做分布式锁,返回时删除token信息,释放分布式锁。
参考:
https://www.zhihu.com/question/27744795