当前位置:网站首页>如何防止重复下单?
如何防止重复下单?
2022-08-04 15:29:00 【InfoQ】
大家好,我是老三,上一篇我们聊了
如何防止订单重复支付
这篇和大家聊聊如何防止重复下单,文章很短,大概只需要几分钟阅读。
用户下单流程
我们从用户浏览商品开始,看看用户下单的简要过程:
- 浏览商品:用户查看商品详情
- 加购/结算:用户可以选择直接购买商品,也可以先加入购物车,用户购买的这一步就是结算
- 确认下单:结算完成,就进入了下单页面,
提交订单
,这一步就会生成一个订单,然后进入付款页面
我们可以看到,下单是发生在结算之后,下单之后,会生成唯一的订单号,接下里,客户端需要用这个订单号去完成支付。
那接下来先看看,为什么发生重复下单?
为什么会重复下单
为什么会重复下单,对于订单服务而言,就是接到了多个下单的请求,原因可能有很多,最常见的是这两种:
- 用户重复提交
- 网络原因导致的超时重试
如何防止重复下单
防止用户提交,最常规的做法,就是客户端点击下单之后,在收到服务端响应之前,按钮置灰。
当然,防止重复下单,肯定不能只依靠客户端,可能会因为一些网络的抖动,导致仍然有重复的请求到达服务端,所以还是要在服务端做防重/幂等的处理。
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;
}
这里再说明一下:
- 为什么获取不到锁的时候要抛异常呢?
因为下单里面其实还有一些其它的业务流程,比如锁库存、清优惠券……而此时,获取到锁的请求的下单流程还没有结束,下单的结果还获取不到,没法完成响应,也就没办法做幂等。
客户端,也可以根据响应的状态码,进行特殊处理,比如这个异常先不提示,但是允许用户再次点击下单按钮,来提升用户的体验。
<big><b>参考:</b></big>
[1].
订单系统设计 —— 重复下单
[2].
用幂等防止重复订单
边栏推荐
猜你喜欢
随机推荐
聊聊与苹果审核员的爱恨情仇
SAP ABAP SteamPunk 蒸汽朋克的最新进展 - 嵌入式蒸汽朋克
JVM调优-GC基本原理和调优关键分析
Nuget 通过 dotnet 命令行发布
Go 事,如何成为一个Gopher ,并在7天找到 Go 语言相关工作,第1篇
leetcode: 250. Count subtrees of equal value
图解 SQL,这也太形象了吧!
长期更新的一些 pytorch 知识点总结
AAAI‘22 推荐系统论文梳理
7 天学个Go,Go 结构体 + Go range 来学学
有哪些好用的IT资产管理平台?
Taurus.MVC WebAPI 入门开发教程2:添加控制器输出Hello World。
leetcode: 259. Smaller sum of three numbers
【云原生 | 从零开始学Kubernetes】kubernetes之StatefulSet详解
弄懂#if #ifdef #if defined
你一定从未看过如此通俗易懂的YOLO系列(从v1到v5)模型解读
##ansible自动化运维架构与简介
苏秋贵:揭秘绿联科技用5年时间从0做到6亿,如何一枝独秀?
普法教育结合VR全景,直观感受和学习法治精神
using关键字学习