例如在电商系统下单支付过程中,点击支付按钮由于网络等原因导致请求结果未及时返回,这时用户又重复点击,导致最后在用户同一订单支付两次。为了防止重试对数据状态的改变,需要将接口的设计为幂等的。

保证幂等策略
幂等需要通过唯一的业务单号来保证

先检查,后操作。

实现幂等很简单:
先检查订单是否已经支付过,如果已经支付过,则返回支付成功;
如果没有支付,进行支付流程

但上述先检查后更新为非原子性操作有线程安全问题。剩下就是解决线程安全的问题了。

1、分布式锁【推荐】

Redis分布式锁。使用唯一业务单号进行加锁,

请求加锁成功:则说明订单未支付,然后去支付,支付结果成功/失败、异常都释放锁。

请求加锁失败:说明订单处于支付过程中,返回请勿重复支付

注意:
其实这里还会涉及到减库存问题。因为支付结果通知通常是异步。所以加锁最多加到发起支付成功回流便要释放锁,等不到支付结果。

会导致正在三方支付的过程中,业务代码释放锁,这时用户重复提交。还是会传该单号,这里三方支付都会处理,会校验重复单号支付问题。所以这个不用考虑。

2、数据库乐观锁(特殊情况下乐观锁存在失效的情况ABA问题)

如果只是更新已有的数据,没有必要对业务进行加锁,设计表结构时使用乐观锁,一般通过version来做乐观锁,这样既能保证执行效率,又能保证幂等。例如:

UPDATE tab1 SET col1=1,version=version+1 WHERE version=#version#

不过,乐观锁存在失效的情况,就是常说的ABA问题,不过如果version版本一直是自增的就不会出现ABA的情况。

3、数据库悲观锁(不推荐,会造成锁表的情况)

4、利用数据库的唯一索引来保证幂等

使用业务订单号orderNo做为去重表的唯一索引,利用数据库的唯一索引添加记录成功与否,来判断是否为重复支付

5、token令牌

这种方式分成两个阶段:申请token阶段和支付阶段。

第一步:在支付之前,需要订单系统根据用户信息向支付系统发起一次申请token的请求,支付系统将token保存到Redis缓存中,为第二阶段支付使用。

第二步:订单系统拿着申请到的token发起支付请求,支付系统会检查Redis中是否存在该token,如果存在,表示第一次发起支付请求,删除缓存中token后开始支付逻辑处理;如果缓存中不存在,表示非法请求。