#防重复处理总结
##背景
在业务开发中,我们常会面对防止重复请求的问题。当服务端对于请求的响应涉及数据的修改,或状态的变更时,可能会造成极大的危害。重复请求的后果在交易系统、售后维权,以及支付系统中尤其严重。
前台操作的抖动,快速操作,网络通信或者后端响应慢,都会增加后端重复处理的概率。
前台操作去抖动和防快速操作的措施,我们首先会想到在前端做一层控制。当前端触发操作时,或弹出确认界面,或disable入口并倒计时等等,此处不细表。
但前端的限制仅能解决少部分问题,且不够彻底,后端自有的防重复处理措施必不可少,义不容辞。
在接口实现中,我们常要求接口要满足幂等性,来保证多次重复请求时只有一次有效。
查询类的接口几乎总是幂等的,但在包含诸如数据插入,多模块数据更新时,达到幂等性会比较难,尤其是高并发时的幂等性要求。比如第三方支付前台回调和后台回调,第三方支付批量回调,慢性能业务逻辑(如用户提交退款申请,商家同意退货/退款等)或慢网络环境时,是重复处理的高发场景。
##尝试
这里针对“用户提交退款申请”的例子,说明一下尝试过的防重复处理方法的效果。
后端防重复处理的方式,我们先后尝试了三种:
####1)基于DB中退款订单状态的验证
这种方式简单直观,从DB查询出来的退款详情(包括状态)往往还可以用在后续逻辑中,没有花额外的工作专门应对重复请求的问题。
这种查询状态后进行验证的逻辑,从代码上线后就一直存在于所有含状态的业务逻辑处理中,必不可少。但对于防重复处理效果并不好:在前端添加防重复提交前,每周平均在25笔;前端优化后,每周降到7笔。这个数量占总退款申请数的3%%,一个仍然无法接受的比例。
理论上,任意次请求只要在数据状态更新之前都完成了查询操作,则业务逻辑的重复处理就会发生。如下图所示。优化的方向是减少查询到更新之间业务处理时间,可降低空档期的并发影响。极致情况下如果查询和更新变成了原子操作,则就不存在我们当前的问题。
####2)基于缓存数据状态的验证
Redis存储查询轻量快速。在request进来的时候,可以先记录在缓存中。后续进来的request每次进行验证。整个流程处理完成,清除缓存。以退款为例子:
I. 每次退款发起申请,读取缓存中是否有以orderId为key的值
II. 没有,则往缓存中写入以orderId为key的value
III.有,则说明有该订单的退款正在进行。
IV. 操作完清缓存,或者缓存存值的时候设置生命周期
与1)的发放相比,数据库换成响应更快的缓存。但是仍然不是原子操作。插入和读取缓存还是有时间间隔。在极致的情况下还是存在重复操作的情况。
此方法优化后,每周1笔重复操作。
####3)利用唯一索引机制的验证
需要原子性操作,想到了数据库的唯一索引。
新建一个TradeLock表:
CREATE TABLE `TradeLock` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`type` int(11) NOT NULL COMMENT '锁类型',
`lockId` int(11) NOT NULL DEFAULT '0' COMMENT '业务ID',
`status` int(11) NOT NULL DEFAULT '0' COMMENT '锁状态',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='Trade锁机制';
每次request进来则往表里面插入数据:
——成功,则可以继续操作(相当于获取锁);
——失败,则说明有操作在进行。
操作完成后,删除此条记录。(相当于释放锁)
目前已经上线,等待下周的数据统计。
####4)基于缓存的计数器验证:
由于数据库的操作比较消耗性能,了解到redis的计数器也是原子性操作。果断采用计数器。既可以提高性能,还不用存储,而且能提升qps的峰值。
还是以订单退款为例子:
每次request进来则新建一个以orderId为key的计数器,然后+1。
如果>1(不能获得锁): 说明有操作在进行,删除。
如果=1(获得锁): 可以操作。
操作结束(删除锁):删除这个计数器。
要了解计数器,可以参考:
##总结:
PHP语言自身没有提供进程互斥和锁定机制。因此才有了我们上面的尝试。
网上也有文件锁机制,但是考虑到我们的分布式部署,建议还是用缓存。
在大并发的情况下,程序各种情况的发生。特别是涉及到金额操作,不能有一分一毫的差距。所以在大并发要互斥的情况下可以考虑3、4两种方案。
爱迪生尝试了1600多种材料选择了钨丝发明了灯泡,实践出真知。遇到问题,和问题斗争,最后解决问题是一个最大提升自我的过程,不但加宽自己的知识广度,更加深了自己的技能深度。达到目标之后的成就感更是不言而喻。
- 作者:蘑菇街商家平台资金团队工程师 @木照
相关推荐
用注解实现redis分布式锁,防止短时间内重复请求,尤其对于请求耗时较长的方法,希望对大家有帮助
JavaScript如何实现防止重复的网络请求的示例 在开发中,经常会遇到接口重复请求导致的各种问题。 对于重复的网络请求,会导致页面更新多次,发生页面抖动的现象,影响用户体验。 例如当前页面请求还未响应完成,...
uniapp中点击按钮,请求接口时连续点击出现重复请求接口现象
vue axios拦截器常用之重复请求取消 引言 上一篇介绍了axios的简单封装,知道了axios拦截器的应用场景和方法,今天来看一下对于响应时间过长且请求次数过高的情况拦截器如何处理。 取消请求的方法 Axios使用...
asp.net 页面防止重复提交(可用)
如何优雅地处理重复(并发)请求?(csdn)————程序
在日常开发过程中,偶尔会出现一些极端问题。...以上这篇Android 快速实现防止网络重复请求&按钮重复点击的方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持软件开发网。
Axios取消重复请求的方法实例详解 目录 前言一、如何取消请求 二、如何判断重复请求 三、如何取消重复请求 3.1 定义辅助函数 3.2 设置请求拦截器 3.3 设置响应拦截器 四、CancelToken 的工作原理 五、总结 六、...
jquery easyui-1.4 请求返回无数据后会重复请求后台方法
主要介绍了Java后台防止客户端重复请求、提交表单实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
Nginx中http请求处理过程 有不少地方不是很明白 ,还望大家共同交流
同一地址多次请求导致的问题,解决方案,将网络地址放在字典key里,进行处理判断
本篇文章给大家分享了关于java并发访问重复请求过滤的相关问题以及解决方法,对此有需要的朋友参考学习下。
tomcat中server配置文件的结构,以及处理一个http请求的全过程
新建日志组件,日志组件对请求进行拦截处理,输出请求入参、出参。其他各微服务引用日志组件,对日志统一输出 日志组件如下: 工具类 1、新建TimeCostEnum 请求耗时类,用于对请求处理耗时级别定义 package ...
本文主要介绍防止重复发送 Ajax请求的方法。具有很好的参考价值,下面跟着小编一起来看下吧
前端做后台管控系统,在某些接口请求时间过长的场景下,需要防止用户反复发起请求。 假设某场景下用户点击查询按钮后,后端响应需要长时间才能返回数据。那么要规避用户返回点击查询按钮无外乎是让用户无法在合理...
首先我要说一个结论:RestTemplate请求结果异常是可以自定义处理的。在开始进行自定义的异常处理逻辑之前,我们有必要看一下异常处理的默认实现。也就是:为什么会产生上面小节提到的现象? ResponseErrorHandler...
A请求的处理函数是睡眠1秒钟, B请求的处理函数是睡眠2秒钟, C请求的处理函数是睡眠3秒钟, D请求的处理函数是睡眠4秒钟.E请求表示终止服务器的运行.服务器为A/B/C/D四类请求分别启动一个线程. 每个线程在处理完一个...