这种解决方案在注册登录的场景下比较常见,当我们点击”发送验证码“按钮的时候,会进行手机短信验证码发送,且按钮就会有一分钟左右的置灰。
有些经验不太丰富的同学,通常会简单粗暴地把这个方案直接照搬过来。
但这种方案只能解决多次点击下单按钮的问题,对于Nginx或类似于SpringCloud Gateway的超时重试所导致的问题是无能为力的。
当然,这种方案也不是真的没有价值。它可以在高并发场景下,从浏览器端去拦住一部分请求,减少后端服务器的处理压力。
幂等性
接口幂等性是指:以相同的参数,对一个接口进行多次调用,所产生的结果和一次调用是完全相同的。
下面的情况就是幂等的:
student.setName("张三");
而这种情况就是非幂等的,因为每次调用,年龄都会增加一岁。
student.increaseAge(1);
现在我们的思路需要切换到幂等性的解决方案来。
同样是幂等性场景,“如何防止重复提交订单” 比 “如何防止订单重复支付” 的解决方案要难一些。
因为,后者在常规情况下,一个订单都是对应一笔支付单,所以orderID可以作为一个幂等性校验、防止订单重复支付的天然神器。
但这个方案在“如何防止重复提交订单”就不适用了,
该“全局唯一订单号”不能代替数据库主键,在未分库分表场景下,主键还是用数据库自增ID比较好。
优点:彻底解决了重复下单的问题;
缺点:方案复杂,前后端都有开发工作量,还要新增接口,新增字段。
网上还有同学说,要单独弄一个生成“全局唯一订单号”的服务,我觉得还是免了吧,这不是更麻烦了吗?
优点:彻底解决了重复下单的问题,且技术方案做了一定简化;
缺点:前后端仍然都有开发工作量,且需要新增字段;
订单就是某个用户用特定的价格购买了某种商品,即:用户和商品的连接。
那么,“如何防止重复提交订单”,其实就是防止在短时间内,用户和商品进行多次连接。弄明白问题本质,接下来我们就着手制定技术方案了。
可以用 ”用户ID + 分隔符 + 商品ID“ 作为唯一标识,让持有相同标识的请求在短时间内不能重复下单,不就可以了吗?而且,Redis不正是做这种解决方案的利器吗?
把”用户ID + 分隔符 + 商品ID“作为Redis key,并把”短时间所对应的秒数“设置为seconds,让它过期自动删除。
这样一来,整体业务步骤如下:
优点:彻底解决了重复下单的问题,且在技术方案上,不需要前端参与,不需要添加接口,不需要添加字段;
缺点:综合比较而言,暂无明显缺点,如果硬要找缺点的话,可能强依赖于Redis勉强可以算上吧;
结语
在真正的生产环境下,我们最终选择了 方案四 ,从订单业务的本质入手“。原因很简单,整体改动范围比较小,测试的回归范围也比较可控,且技术方案复杂度最低。这样做技术选型的话,也比较符合百度一直倡导的”简单可依赖“原则。
以上参考文章:如何防止重复提交订单? - 掘金
下面我们以防止重复提交订单为例,向大家介绍最简单的、成本最低的解决办法。
我们先来看一张图,这张图就是本次方案的核心流程图。
实现的逻辑,流程如下:
对于下单流量不算高的系统,可以采用这种请求唯一ID+数据表增加唯一索引约束的方式,来防止接口重复提交!虽然简单粗暴,但是十分有效!
这张图就是本次方案的核心流程图
实现的逻辑,流程如下:
redis
中再返回给前端,前端将唯一 ID 值埋点在页面里面redis
的分布式锁服务,对请求 ID 在限定的时间内进行加锁,如果加锁成功,继续后续流程;如果加锁失败,说明服务正在处理,请勿重复提交redis
中的请求唯一 ID 清理掉引入缓存服务,防止重复提交的大体思路如上,实践代码如下!
…….
整套方案完全基于redis来实现,同时结合redis的分布式锁来实现请求限流,之所以选择redis,是因为它是一个内存数据库,性能比关系型数据库强太多,即使每秒的下单请求量在几千,也能很好的应对,为关系型数据库起到降压作用!
特别注意的地方:使用redis的分布式锁,推荐单机环境,如果redis是集群环境,可能会导致锁短暂无效!
随着下单流量逐渐上升,通过查询数据库来检查当前服务请求是否重复提交这种方式,可能会让数据库的请求查询频率变得非常高,数据库的压力会倍增。
此时我们可以引入redis
缓存,将通过查询数据库来检查当前请求是否重复提交这种方式,转移到通过查询缓存来检查当前请求是否重复提交,可以很好的给数据库降压!
在上一篇文章中,我们详细的介绍了随着下单流量逐渐上升,为了降低数据库的访问压力,通过请求唯一ID+redis分布式锁来防止接口重复提交
每次提交的时候,需要先调用后端服务获取请求唯一ID
,然后才能提交。
对于这样的流程,不少的同学可能会感觉到非常鸡肋,尤其是单元测试,需要每次先获取submitToken
值,然后才能提交!
能不能不用这么麻烦,直接服务端通过一些规则组合,生成本次请求唯一ID呢?
答案是可以的!
今天我们就一起来看看,如何通过服务端来完成请求唯一 ID 的生成?
我们先来看一张图,这张图就是本次方案的核心流程图。
实现的逻辑,流程如下:
redis
的分布式锁服务,对请求 ID 在限定的时间内尝试进行加锁,如果加锁成功,继续后续流程;如果加锁失败,说明服务正在处理,请勿重复提交引入缓存服务后,防止重复提交的大体思路如上,实践代码如下!
……
其中最关键的一个步就是将唯一请求 ID 的生成,放在服务端通过组合来实现,在保证防止接口重复提交的效果同时,也可以显著的降低接口测试复杂度!
小结
本次方案相比于上一个方案,最大的改进点在于:将接口请求唯一 ID 的生成逻辑,放在服务端通过规则组合来实现,不需要前端提交接口的时候强制带上这个参数,在满足防止接口重复提交的要求同时,又能减少前端和测试提交接口的复杂度!
以上参考文章
如何防止用户重复提交订单?(上) - 程序员志哥 - 博客园
如何防止用户重复提交订单?(中) - 程序员志哥 - 博客园
如何防止用户重复提交订单?(下) - 程序员志哥 - 博客园