后端怎样防止重复提交订单?
后端怎样防止重复提交订单?
1.加乐观锁
既然悲观锁有性能问题,为了提升接口性能,我们可以使用乐观锁。需要在表中增加一个timestamp或者version字段,这里以version字段为例。
在更新数据之前先查询一下数据:
select id,amount,version from user id=123;
如果数据存在,假设查到的version等于1,再使用id和version字段作为查询条件更新数据:
update user set amount=amount+100,version=version+1
where id=123 and version=1;
更新数据的同时version+1,然后判断本次update操作的影响行数,如果大于0,则说明本次更新成功,如果等于0,则说明本次更新没有让数据变更。
由于第一次请求version等于1是可以成功的,操作成功后version变成2了。这时如果并发的请求过来,再执行相同的sql:
update user set amount=amount+100,version=version+1
where id=123 and version=1;
该update操作不会真正更新数据,最终sql的执行结果影响行数是0,因为version已经变成2了,where中的version=1肯定无法满足条件。但为了保证接口幂等性,接口可以直接返回成功,因为version值已经修改了,那么前面必定已经成功过一次,后面都是重复的请求。
具体流程如下:
具体步骤:
- 先根据id查询用户信息,包含version字段
- 根据id和version字段值作为where条件的参数,更新用户信息,同时version+1
- 判断操作影响行数,如果影响1行,则说明是一次请求,可以做其他数据操作。
- 如果影响0行,说明是重复请求,则直接返回成功。
2.根据状态判断
很多时候业务表是有状态的,比如订单表中有:1-下单、2-已支付、3-完成、4-撤销等状态。如果这些状态的值是有规律的,按照业务节点正好是从小到大,我们就能通过它来保证接口的幂等性。
假如id=123的订单状态是已支付,现在要变成完成状态。
update `order` set status=3 where id=123 and status=2;
第一次请求时,该订单的状态是已支付,值是2,所以该update语句可以正常更新数据,sql执行结果的影响行数是1,订单状态变成了3。
后面有相同的请求过来,再执行相同的sql时,由于订单状态变成了3,再用status=2作为条件,无法查询出需要更新的数据,所以最终sql执行结果的影响行数是0,即不会真正的更新数据。但为了保证接口幂等性,影响行数是0时,接口也可以直接返回成功。
具体流程图如下:
具体步骤:
- 用户通过浏览器发起请求,服务端收集数据。
- 根据id和当前状态作为条件,更新成下一个状态
- 判断操作影响行数,如果影响了1行,说明当前操作成功,可以进行其他数据操作。
- 如果影响了0行,说明是重复请求,直接返回成功。
3.加分布式锁
其实前面介绍过的加唯一索引或者加防重表,本质是使用了数据库的分布式锁,也属于分布式锁的一种。但由于数据库分布式锁的性能不太好,我们可以改用:redis或zookeeper。
鉴于现在很多公司分布式配置中心改用apollo或nacos,已经很少用zookeeper了,我们以redis为例介绍分布式锁。
目前主要有三种方式实现redis的分布式锁:
- setNx命令
- set命令
- Redission框架
每种方案各有利弊,具体实现细节我就不说了,有兴趣的朋友可以加我微信找我私聊。
具体流程图如下:
具体步骤:
- 用户通过浏览器发起请求,服务端会收集数据,并且生成订单号code作为唯一业务字段。
- 使用redis的set命令,将该订单code设置到redis中,同时设置超时时间。
- 判断是否设置成功,如果设置成功,说明是第一次请求,则进行数据操作。
- 如果设置失败,说明是重复请求,则直接返回成功。
需要特别注意的是:分布式锁一定要设置一个合理的过期时间,如果设置过短,无法有效的防止重复请求。如果设置过长,可能会浪费redis的存储空间,需要根据实际业务情况而定。