欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【问题】如何避免并发情况下的重复提交

程序员文章站 2022-06-02 18:33:25
...

背景

在业务开发中,我们常会面对防止重复请求的问题。当服务端对于请求的响应涉及数据的修改,或状态的变更时,可能会造成极大的危害。重复请求的后果在交易系统、售后维权,以及支付系统中尤其严重。

重复请求的一致性问题又称幂等性问题。



先弄清楚啥叫幂等性。

比如
1. 一个用户把他的性别设置为男,无论他设置多少次,他的性别都是男。
2. 比如我们查询余额(假设并没有任何使余额发生变化的行为),那么我们点击多少次查询余额得到的结果都应该是一样的。

以上都是幂等的。

但是,如果我们进行一笔交易,这笔交易实际已经正常的插入了数据库,但由于前台操作的抖动,快速操作,网络通信或者后端响应慢等原因,又来了一次。导致用户平白无故支付了两次。 这种情况我们该如何避免呢??


首先前端优化是必不可少的,这里暂且不谈。。。

我们谈谈后端

唯一键法

并发并不意味着每个request都处理的很快,也不意味着机器之间就不共享数据了。可以把所有带有副作用的task都给一个GUID,最后写进数据库之前查询一下这个GUID是否已经被执行过了。

订单状态法

用户调用支付,扣款成功后,更新对应订单状态,然后再保存流水。

(支付状态:未支付,已支付)

步骤:
1、查询订单支付状态
2、如果已经支付,直接返回结果
3、如果未支付,则支付扣款并且保存流水
4、返回支付结果

理论上,只要在数据状态更新前完成了查询操作,则业务逻辑的重复处理就依旧会发生。

基于缓存的数据验证

Redis存储查询轻量快速。在request进来的时候,可以先记录在缓存中。后续进来的request每次进行验证。整个流程处理完成,清除缓存。

I.  每次交易发起申请,读取缓存中是否有以orderId为key的值
II. 没有,则往缓存中写入以orderId为key的value
III.有,则说明有该订单正在进行。
IV. 操作完清缓存,或者缓存存值的时候设置生命周期

利用数据库的主键唯一

同一笔订单进来的话,数据库会报唯一索引的错误。这个时候后台对这个异常进行处理并返回给前端提示。

(那么如何保证误操作的交易的订单号都是一个订单号呢,如何和正常的多次交易情况区分开?)

缓存计数器

由于数据库的操作比较消耗性能,了解到redis的计数器也是原子性操作。果断采用计数器。既可以提高性能,还不用存储,而且能提升qps的峰值。
还是以支付为例子:

每次request进来则新建一个以orderId为key的计数器,然后+1。

如果>1(不能获得锁): 说明有操作在进行,删除。
如果=1(获得锁): 可以操作。
操作结束(删除锁):删除这个计数器。