##背景
在业务开拓中,我们常会面对防止重复要求的问题。当做事端对付要求的相应涉及数据的修正,或状态的变更时,可能会造成极大的危害。重复要求的后果在交易系统、售后维权,以及支付系统中尤其严重。
前台操作的抖动,快速操作,网络通信或者后端相应慢,都会增加后端重复处理的概率。
前台操作去抖动和防快速操作的方法,我们首先会想到在前端做一层掌握。当前端触发操作时,或弹出确认界面,或disable入口并倒计时等等,此处不细表。
但前真个限定仅能办理少部分问题,且不足彻底,后端自有的防重复处理方法必不可少,义不容辞。
在接口实现中,我们常哀求接口要知足幂等性,来担保多次重复要求时只有一次有效。
查询类的接口险些总是幂等的,但在包含诸如数据插入,多模块数据更新时,达到幂等性会比较难,尤其是高并发时的幂等性哀求。比如第三方支付前台回调和后台回调,第三方支付批量回调,慢性能业务逻辑(如用户提交退款申请,商家赞许退货/退款等)或慢网络环境时,是重复处理的高发场景。
##考试测验
这里针对“用户提交退款申请”的例子,解释一下考试测验过的防重复处理方法的效果。
后端防重复处理的办法,我们先后考试测验了三种:
####1)基于DB中退款订单状态的验证
这种办法大略直不雅观,从DB查询出来的退款详情(包括状态)每每还可以用在后续逻辑中,没有花额外的事情专门应对重复要求的问题。
这种查询状态后进行验证的逻辑,从代码上线后就一贯存在于所有含状态的业务逻辑处理中,必不可少。但对付防重复处理效果并不好:在前端添加防重复提交前,每周均匀在25笔;前端优化后,每周降到7笔。这个数量占总退款申请数的3%%,一个仍旧无法接管的比例。
理论上,任意次要求只要在数据状态更新之前都完成了查询操作,则业务逻辑的重复处理就会发生。如下图所示。优化的方向是减少查询到更新之间业务处理韶光,可降落空档期的并发影响。极致情形下如果查询和更新变成了原子操作,则就不存在我们当前的问题。
####2)基于缓存数据状态的验证
Redis存储查询轻量快速。在request进来的时候,可以先记录在缓存中。后续进来的request每次进行验证。全体流程处理完成,打消缓存。以退款为例子:
I. 每次退款发起申请,读取缓存中是否有以orderId为key的值 II. 没有,则往缓存中写入以orderId为key的value III.有,则解释有该订单的退款正在进行。 IV. 操作完清缓存,或者缓存存值的时候设置生命周期12345
与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锁机制';12345678912345678
每次request进来则往表里面插入数据:
——成功,则可以连续操作(相称于获取锁);——失落败,则解释有操作在进行。123
操作完成后,删除此条记录。(相称于开释锁)
目前已经上线,等待下周的数据统计。
####4)基于缓存的计数器验证:
由于数据库的操作比较花费性能,理解到redis的计数器也是原子性操作。果断采取计数器。既可以提高性能,还不用存储,而且能提升qps的峰值。
还是以订单退款为例子:
每次request进来则新建一个以orderId为key的计数器,然后+1。
如果>1(不能得到锁): 解释有操作在进行,删除。如果=1(得到锁): 可以操作。1234
操作结束(删除锁):删除这个计数器。
要理解计数器,可以参考:
link
##总结:
PHP措辞自身没有供应进程互斥和锁定机制。因此才有了我们上面的考试测验。
网上也有文件锁机制,但是考虑到我们的分布式支配,建议还是用缓存。
在大并发的情形下,程序各种情形的发生。特殊是涉及到金额操作,不能有一分一毫的差距。以是在大并发要互斥的情形下可以考虑3、4两种方案。