<返回更多

老生常谈的问题:电商业务中如何防止重复下单?

2022-07-26  微信公众号  三分恶
加入收藏

用户下单流程

我们从用户浏览商品开始,看看用户下单的简要过程:

用户下单简要过程

我们可以看到,下单是发生在结算之后,下单之后,会生成唯一的订单号,接下来,客户端需要用这个订单号去完成支付。

那接下来先看看,为什么发生重复下单?

为什么会重复下单

为什么会重复下单,对于订单服务而言,就是接到了多个下单的请求,原因可能有很多,最常见的是这两种:

重复下单原因

如何防止重复下单

防止用户提交,最常规的做法,就是客户端点击下单之后,在收到服务端响应之前,按钮置灰。

当然,防止重复下单,肯定不能只依靠客户端,可能会因为一些网络的抖动,导致仍然有重复的请求到达服务端,所以还是要在服务端做防重/幂等的处理。

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实现分布式锁。

大概的逻辑:

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;
    }

这里再说明一下:

因为下单里面其实还有一些其它的业务流程,比如锁库存、清优惠券……而此时,获取到锁的请求的下单流程还没有结束,下单的结果还获取不到,没法完成响应,也就没办法做幂等。

客户端,也可以根据响应的状态码,进行特殊处理,比如这个异常先不提示,但是允许用户再次点击下单按钮,来提升用户的体验。

原文链接:
https://mp.weixin.qq.com/s/Dc_4taB6Boojdw_0mngroQ

作者:三分恶

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>