如何避免并发情况下的重复提交

如何避免并发情况下的重复提交

背景

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

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



先弄清楚啥叫幂等性。

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

以上都是幂等的。

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


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

我们谈谈后端

唯一键法

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

订单状态法

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

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

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

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

基于缓存的数据验证

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

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

利用数据库的主键唯一

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

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

缓存计数器

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

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

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

原文地址:https://www.cnblogs.com/erma0-007/p/8654950.html