如何防订单重复提交策略方法

背景

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

前台操作的抖动,快速操作,网络通信或者后端响应慢,都会增加后端重复处理的概率。前台操作去抖动和防快速操作的措施,我们首先会想到在前端做一层控制。当前端触发操作时,或弹出确认界面,或disable入口并倒计时等等,此处不细表。但前端的限制仅能解决少部分问题,且不够彻底,后端自有的防重复处理措施必不可少,义不容辞。

在接口实现中,我们常要求接口要满足幂等性,来保证多次重复请求时只有一次有效。

查询类的接口几乎总是幂等的,但在包含诸如数据插入,多模块数据更新时,达到幂等性会比较难,尤其是高并发时的幂等性要求。比如第三方支付前台回调和后台回调,第三方支付批量回调,慢性能业务逻辑(如用户提交退款申请,商家同意退货/退款等)或慢网络环境时,是重复处理的高发场景。

尝试

这里针对“用户提交退款申请”的例子,说明一下尝试过的防重复处理方法的效果。后端防重复处理的方式,我们先后尝试了三种:

(1)基于DB中退款订单状态的验证

这种方式简单直观,从DB查询出来的退款详情(包括状态)往往还可以用在后续逻辑中,没有花额外的工作专门应对重复请求的问题。

这种查询状态后进行验证的逻辑,从代码上线后就一直存在于所有含状态的业务逻辑处理中,必不可少。但对于防重复处理效果并不好:在前端添加防重复提交前,每周平均在25笔;前端优化后,每周降到7笔。这个数量占总退款申请数的3%%,一个仍然无法接受的比例。

理论上,任意次请求只要在数据状态更新之前都完成了查询操作,则业务逻辑的重复处理就会发生。如下图所示。优化的方向是减少查询到更新之间业务处理时间,可降低空档期的并发影响。极致情况下如果查询和更新变成了原子操作,则就不存在我们当前的问题。

1021265-20180917173747789-386317460.png

(2)基于缓存数据状态的验证

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

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

与1)的发放相比,数据库换成响应更快的缓存。但是仍然不是原子操作。插入和读取缓存还是有时间间隔。在极致的情况下还是存在重复操作的情况。此方法优化后,每周1笔重复操作。

1021265-20180917173805925-1949274645.png

(3)利用唯一索引机制的验证

需要原子性操作,想到了数据库的唯一索引。新建一个TradeLock表:


 
 
  1. CREATE TABLE `TradeLock` (
  2. `id` int( 11) unsigned NOT NULL AUTO_INCREMENT,
  3. `type` int( 11) NOT NULL COMMENT '锁类型',
  4. `lockId` int( 11) NOT NULL DEFAULT '0' COMMENT '业务ID',
  5. `status` int( 11) NOT NULL DEFAULT '0' COMMENT '锁状态',
  6. PRIMARY KEY ( `id`)
  7. ) ENGINE= InnoDB AUTO_INCREMENT= 1 DEFAULT CHARSET=utf8 COMMENT= 'Trade锁机制';
  • 每次request进来则往表里面插入数据:

    成功,则可以继续操作(相当于获取锁);
    失败,则说明有操作在进行。

  • 操作完成后,删除此条记录。(相当于释放锁)。

目前已经上线,等待下周的数据统计。

1021265-20180917173814544-1871115286.png

(4)基于缓存的计数器验证

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

还是以订单退款为例子:

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

    如果>1(不能获得锁): 说明有操作在进行,删除。
    如果=1(获得锁): 可以操作。

  • 操作结束(删除锁):删除这个计数器。

要了解计数器,可以参考:http://www.redis.cn/commands/incr.html

1021265-20180917173829338-1797651582.png

总结:

PHP语言自身没有提供进程互斥和锁定机制。因此才有了我们上面的尝试。网上也有文件锁机制,但是考虑到我们的分布式部署,建议还是用缓存。在大并发的情况下,程序各种情况的发生。特别是涉及到金额操作,不能有一分一毫的差距。所以在大并发要互斥的情况下可以考虑3、4两种方案。

爱迪生尝试了1600多种材料选择了钨丝发明了灯泡,实践出真知。遇到问题,和问题斗争,最后解决问题是一个最大提升自我的过程,不但加宽自己的知识广度,更加深了自己的技能深度。达到目标之后的成就感更是不言而喻。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在电商系统中,订单重复提交是一个关键任务,主要是为了保护用户的权益,重复计费。以下是几种常见的策略: 1. **验证唯一标识**:每个用户下单前,先检查其订单号或者购物车中的商品信息,确保未在短时间内重复提交相同的订单。 2. **锁定表单**:当用户开始填写订单信息时,可以暂时锁定订单表单,止他们在提交过程中刷新页面或直接复制URL重新提交。 3. **前端校验**:在前端使用JavaScript进行实时验证,例如在提交按钮上添加点击事件,只有在验证所有必填字段都正确无误后才允许提交。 4. **后端校验**:服务器端也需要检查订单是否存在,如果发现相同订单已经存在,应返回错误消息,并阻止再次提交。 5. **设置提交时间窗**:设定一个合理的提交间隔时间,比如用户提交后的一段时间内不允许再次提交新的订单。 6. **数据库事务**:如果有多个并发请求,可以在数据库层面开启事务处理,确保订单的原子性和一致性。 7. **缓存控制**:对于高并发场景,可以利用缓存技术存储订单状态,减少数据库查询,但如果遇到缓存失效或清空,可能会导致重复提交,这时就需要有后端清除缓存机制。 以上策略可以根据实际情况选择组合使用。同时,保持良好的用户提示也是必要的,告知他们为何无法提交重复订单
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值