当前位置:网站首页>如何防止重复下单?
如何防止重复下单?
2022-08-01 15:36:00 【三分恶】
大家好,我是老三,上一篇我们聊了 如何防止订单重复支付 这篇和大家聊聊如何防止重复下单,文章很短,大概只需要几分钟阅读。
用户下单流程
我们从用户浏览商品开始,看看用户下单的简要过程:

- 浏览商品:用户查看商品详情
- 加购/结算:用户可以选择直接购买商品,也可以先加入购物车,用户购买的这一步就是结算
- 确认下单:结算完成,就进入了下单页面,
提交订单,这一步就会生成一个订单,然后进入付款页面
我们可以看到,下单是发生在结算之后,下单之后,会生成唯一的订单号,接下里,客户端需要用这个订单号去完成支付。
那接下来先看看,为什么发生重复下单?
为什么会重复下单
为什么会重复下单,对于订单服务而言,就是接到了多个下单的请求,原因可能有很多,最常见的是这两种:
- 用户重复提交
- 网络原因导致的超时重试

如何防止重复下单
防止用户提交,最常规的做法,就是客户端点击下单之后,在收到服务端响应之前,按钮置灰。
当然,防止重复下单,肯定不能只依靠客户端,可能会因为一些网络的抖动,导致仍然有重复的请求到达服务端,所以还是要在服务端做防重/幂等的处理。
PS:这里额外插入一点我对防重和幂等的理解:防重指的是防止重复提交,幂等指的是多次请求如一次,简单说,就是防重可以给对重复请求抛异常,幂等是对重复的请求响应第一次的结果,在我们讨论的这个场景里,幂等就是响应唯一的订单号。

防重第一步,需要识别请求是否重复,这一步,需要客户端配合实现。
为什么呢?大家想一下,下单的时候,服务端怎么去判断这个下单请求是否唯一呢?金额?商品?优惠券?……万一用户就是喜欢,又下了一个一摸一样的单呢?
所以,需要客户端在请求下单接口的时候,需要生成一个唯一的请求号:requestId,服务端拿这个请求号,判断是否重复请求。
那么,接下来,压力就给到服务端了,看看服务端怎么实现防重/幂等吧!
利用数据库实现幂等
可以在订单表t_order里添加一个字段:requestId,添加唯一索引:

这样一来,如果是重复的请求,在落库的时候就会报错,为了保证幂等性,我们可以catch住这个异常,根据requestId获取订单号,然后向客户端响应订单号。
大概的代码如下:
PlaceOrderResVO placeOrder(PlaceOrderReqVO reqVO) {
try {
//下单业务逻辑
……
//生成订单号
String oid=generateOid();
……
//订单落库
Order order = orderMapper.saveOrder(orderDO);
//响应订单
resVO.setOid(order.getOid());
return resVO;
} catch(UniqueKeyViolationException e) {
// 发生了重复异常
// 根据请求号获取订单
Order order = getOrderByRequestId(reqVO.getRequestId());
resVO.setOid(order.getOid());
return resVO;
} catch (Exception e) {
}
}
当然,这里不太好的地方是,拿异常来做了业务判断。
利用Redis防重
另外一个办法,就是下单请求的时候要加锁了,通常我们的服务都是集群部署,所以一般都是用Redis实现分布式锁。
大概的逻辑:
- 就是以
requestId为维度,进行加锁,如果获取锁失败,就抛一个自定义的重复下单异常。 - 如果获取到锁,先check一下,是否已经下单,为了提高性能,下单完成后,也把下单的结果放在Redis缓存里。

大概的代码如下:
public PlaceOrderResVO placeOrder(PlaceOrderReqVO reqVO) {
//加锁
RLock orderLock = redissonClient.getLock(RedisConstant.PLACE_ORDER_LOCK_KEY + reqVO.getRequestId());
//获取锁失败,抛出重复下单异常
if(orderLock.isExistes){
throw new OrderRepeatException();
}
// 加锁
orderLock.lock();
try {
//检查是否已经下单
RBucket<PlaceOrderResVO> orderCache = redissonClient.getBucket(RedisConstant.PLACE_ORDER_LOCK_KEY+reqVO.getRequestId());
if(orderCache.isExistes){
return orderCache.get();
}
//下单业务逻辑
……
//落库
//订单落库
Order order = orderMapper.saveOrder(orderDO);
……
//缓存结果
orderCache.put(resVO);
return resVO;
}
} catch (Exception e) {
//……
} finally {
orderLock.unlock();
}
return resVO;
}
这里再说明一下:
- 为什么获取不到锁的时候要抛异常呢?
因为下单里面其实还有一些其它的业务流程,比如锁库存、清优惠券……而此时,获取到锁的请求的下单流程还没有结束,下单的结果还获取不到,没法完成响应,也就没办法做幂等。
客户端,也可以根据响应的状态码,进行特殊处理,比如这个异常先不提示,但是允许用户再次点击下单按钮,来提升用户的体验。
好了,这一篇简单的小文章就这样结束了,最近工作实在太忙了,基本上每天都是九点多、十点多下班,写新的文章,还有《面渣逆袭手册》的维护,都会努力抽时间去做,谢谢大家的理解和支持!
参考:
[1]. 订单系统设计 —— 重复下单
[2]. 用幂等防止重复订单
关注下方公众号,回复「666」,领取七百多页独家原创的面试手册!
边栏推荐
猜你喜欢
随机推荐
pynlpir更新license Error: unable to fetch newest license解决方案
便携烙铁开源系统IronOS,支持多款便携DC, QC, PD供电烙铁,支持所有智能烙铁标准功能
如何使用 Mashup 技术在 SAP Cloud for Customer 页面嵌入自定义 UI
30分钟成为Contributor|如何多方位参与OpenHarmony开源贡献?
动态模型中嵌入静态模型实践
Digicert EV证书签名后出现“证书对于请求用法无效”的解决方案
kubelet node pressure eviction
2.8K 120Hz touch dual-screen blessing Lingyao X dual-screen Pro 2022 makes the office without fear of imagination
urlopen error errno111(英雄联盟报错error)
反序列化漏洞详解
【无标题】
预定义和自定义
测试如何拓展自己的知识面?
Timezone setting in MySQL
选择合适的 DevOps 工具,从理解 DevOps 开始
Eslint syntax error is solved
IronOS, an open source system for portable soldering irons, supports a variety of portable DC, QC, PD powered soldering irons, and supports all standard functions of smart soldering irons
Spark: Cluster Computing with Working Sets
表白代码vbs不同意无法关闭(vbs表白代码不同意按键会跑)
利用UIRecorder做页面元素巡检









