幂等性的介绍以及在支付上的使用
什么是幂等性
在同一笔数据处理操作中,不管执行多少次,它所造成的后果都一样。
幂等性的设计
创建订单的设计
创建订单是用户通过表单或接口传递过来一系列支付所需要的信息,后台服务器根据这些信息创建一条订单记录。
这是一般的用法,但是在一些网络不好,或者客户端条件不确定的一些情况下,用户会连续重复的传递多条一样的数据过来,如果不做处理,就会给该用户创建多条订单记录,这显然是不合理的。
要解决这种问题,可以参考下面的思路:
- 在用户确认订单页面时,服务器端就生成一个订单编号传递给前端;
- 在生成订单的接口处把该订单编号带上,订单编号可以用统一的id生成器或者自定义的规则生成;
- 数据库表订单编号设置唯一索引;
- 后端在校验使用前端传过来的订单编号后,就将该编号记录下数据库中。当用户重复提交通过订单信息时,可以利用数据库唯一索引的特性来避免产生多个订单。
伪代码:
1 | try: |
支付回调的设计
在接入第三方支付,例如微信,支付宝支付时,处理回调结果是必须的操作。
以支付宝为例,在支付宝回调中,会返回 out_trade_no【商户订单号】,trade_no【支付宝交易号】,out_trade_no在我们自己的系统中唯一,trade_no在支付宝中唯一。
在处理回调时,一般有以下处理方式:
普通方式
- 接收到支付宝的回调请求,在校验结果成功后,拿到out_trade_no和trade_no
- 根据trade_no查询系统中该订单是否以及处理过
1
select * from t_order where order_no = out_trade_no;
- 已处理,直接返回;未处理,继续下面操作
- 开启事务
- 处理订单信息,修改订单状态
- 提交事务
上面的操作在正常情况下不会出现问题,但是由于网络或其他问题,支付宝发送的多条通知,这是就会出现问题。当两条通知同时到达第2步,同时查到该订单未处理过,这样这两个通知就会继续向下执行,造成的不好的结果。
为了解决上面的问题,有下面的处理方式:
悲观锁方式
- 接收到支付宝的回调请求
- 开启事务
- 查询订单并加悲观锁
1
select * from t_order where order_no = out_trade_no for update;
- 判断该订单是否以及处理过
- 已处理,直接返回;未处理,继续下面操作
- 处理订单信息,修改订单状态
- 提交事务
这里和上面方式的区别在于for update
select … for update 是常用的手动加锁语句,当执行for update时,数据库会对当前记录加锁。在其他线程执行到这行代码时,会等待上一个线程释放锁,才会重新获取锁,才能够继续执行。而在事务结束后,获取的锁会自动释放。
该方法能够解决上面一个方法产生的问题,但是在并发条件下会产生一些问题:
当多个线程同时触达时,只有第一个会获取锁,其他线程只能处于等待的过程中,这些等待线程就相当于浪费了,消耗了线程资源,不利于并发的场景。
乐观锁方式
- 接收到支付宝的回调请求
- 判断该订单是否以及处理过
- 已处理,直接返回;未处理,继续下面操作
- 开启事务
- 处理订单信息,修改订单状态
这里的修改订单状态是重点,采用乐观锁来判断1
2
3
4
5
6
7update t_order set status = 1 where order_no = out_trade_no where status = 0;
# 上面的update操作会返回影响的行数num
if num==1:
# 成功修改
commit
else:
rollback - 提交事务
这里采用了乐观锁的方式,在update时,mysql会对该记录产生行锁,当多个线程同时到达时,update语句会排队执行,因此最终只有一条update执行成功,返回更新条数,其他的更新条数为0。然后就根据更新条数来判断是否成功更新订单状态。
唯一约束方式
唯一约束的方式可以采用mysql数据库来实现
要依赖数据库来实现唯一性,首先要创建一个表
1 | CREATE TABLE `t_unique` ( |
- 接收到支付宝的回调请求
- 查询
t_unique
表,根据trade_no查询是否已经处理过该订单 - 已处理,直接返回;未处理,继续下面操作
- 开启事务
- 处理订单信息,修改订单状态
- 向
t_unique
表插入数据,插入成功则提交事务;插入失败则回滚事务同一请求时,由于支付宝支付号一致,由于数据库唯一索引的特性,插入数据只有一条数据成功;插入错误时回滚操作,保持了幂等性。1
2
3
4
5try:
insert into t_uq_dipose (trade_no) values (trade_no)
commit
except:
rollback
这种方式在业务量大,并发高的情况下,插入数据会成为瓶颈。
总结
上面几种方式中,乐观锁 > 唯一约束 > 悲观锁,可以根据实际情况选择使用方式。