阿里Java二面:电商业务中如何防止重复下单?

张开发
2026/5/6 10:32:30 15 分钟阅读
阿里Java二面:电商业务中如何防止重复下单?
用户下单流程我们从用户浏览商品开始看看用户下单的简要过程用户下单简要过程浏览商品用户查看商品详情加购/结算用户可以选择直接购买商品也可以先加入购物车用户购买的这一步就是结算确认下单结算完成就进入了下单页面提交订单这一步就会生成一个订单然后进入付款页面我们可以看到下单是发生在结算之后下单之后会生成唯一的订单号接下来客户端需要用这个订单号去完成支付。那接下来先看看为什么发生重复下单为什么会重复下单为什么会重复下单对于订单服务而言就是接到了多个下单的请求原因可能有很多最常见的是这两种用户重复提交网络原因导致的超时重试重复下单原因如何防止重复下单防止用户提交最常规的做法就是客户端点击下单之后在收到服务端响应之前按钮置灰。当然防止重复下单肯定不能只依靠客户端可能会因为一些网络的抖动导致仍然有重复的请求到达服务端所以还是要在服务端做防重/幂等的处理。PS这里额外插入一点我对防重和幂等的理解防重指的是防止重复提交幂等指的是多次请求如一次简单说就是防重可以给对重复请求抛异常幂等是对重复的请求响应第一次的结果在我们讨论的这个场景里幂等就是响应唯一的订单号。防重和幂等防重第一步需要识别请求是否重复这一步需要客户端配合实现。为什么呢大家想一下下单的时候服务端怎么去判断这个下单请求是否唯一呢金额商品优惠券……万一用户就是喜欢又下了一个一模一样的单呢所以需要客户端在请求下单接口的时候需要生成一个唯一的请求号requestId服务端拿这个请求号判断是否重复请求。那么接下来压力就给到服务端了看看服务端怎么实现防重/幂等吧利用数据库实现幂等可以在订单表t_order里添加一个字段requestId添加唯一索引唯一请求字段这样一来如果是重复的请求在落库的时候就会报错为了保证幂等性我们可以catch住这个异常根据requestId获取订单号然后向客户端响应订单号。大概的代码如下PlaceOrderResVO placeOrder(PlaceOrderReqVO reqVO) { try { //下单业务逻辑 …… //生成订单号 String oidgenerateOid(); …… //订单落库 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缓存里。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 { //检查是否已经下单 RBucketPlaceOrderResVO orderCache redissonClient.getBucket(RedisConstant.PLACE_ORDER_LOCK_KEYreqVO.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; }这里再说明一下为什么获取不到锁的时候要抛异常呢因为下单里面其实还有一些其它的业务流程比如锁库存、清优惠券……而此时获取到锁的请求的下单流程还没有结束下单的结果还获取不到没法完成响应也就没办法做幂等。客户端也可以根据响应的状态码进行特殊处理比如这个异常先不提示但是允许用户再次点击下单按钮来提升用户的体验。

更多文章