• 如何防止重复下单?


    大家好,我是老三,上一篇我们聊了 如何防止订单重复支付 这篇和大家聊聊如何防止重复下单,文章很短,大概只需要几分钟阅读。

    用户下单流程

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

    用户下单简要过程

    • 浏览商品:用户查看商品详情
    • 加购/结算:用户可以选择直接购买商品,也可以先加入购物车,用户购买的这一步就是结算
    • 确认下单:结算完成,就进入了下单页面, 提交订单,这一步就会生成一个订单,然后进入付款页面

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

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

    为什么会重复下单

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

    • 用户重复提交
    • 网络原因导致的超时重试

    重复下单原因

    如何防止重复下单

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

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

    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) {
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    当然,这里不太好的地方是,拿异常来做了业务判断。

    利用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 {
                //检查是否已经下单
                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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    这里再说明一下:

    • 为什么获取不到锁的时候要抛异常呢?

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

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


    好了,这一篇简单的小文章就这样结束了,最近工作实在太忙了,基本上每天都是九点多、十点多下班,写新的文章,还有《面渣逆袭手册》的维护,都会努力抽时间去做,谢谢大家的理解和支持!



    参考:

    [1]. 订单系统设计 —— 重复下单

    [2]. 用幂等防止重复订单

    关注下方公众号👇,回复「666」,领取七百多页独家原创的面试手册!

    面渣逆袭手册

  • 相关阅读:
    tomcat的优化
    AI术士炼肛记:程序员开源「肛珠作弊」代码,在线寻找天选之子亲自体验
    linux-4.19 内存之页面回收
    一文搞定 Spring事务
    c++ 服务器编程tcp
    658. 找到 K 个最接近的元素
    第十五章《网络编程》第1节:网络基础知识
    Teamtalk登录流程详解,客户端和服务器交互流程分析
    【Koltin Flow(四)】Flow背压
    BTC和ETH在第三季度预期不佳 可能继续下跌?发展趋势模糊不清
  • 原文地址:https://blog.csdn.net/sinat_40770656/article/details/126077217