美文网首页
2019-02-14 问题记录

2019-02-14 问题记录

作者: 万物归于简 | 来源:发表于2019-02-14 11:11 被阅读0次

    一、提交订单时的各种问题

    1. 服务刚启动第一次下单后,orderClient 可能会出现熔断的情况,但orderService等其它服务继续在执行。

    1) 问题描述

    orderClient 设置了hystrix的超时时间,但是并没有起作用:

    hystrix:
      command:
        default:  #default全局有效,service id指定应用有效
          execution:
            timeout:
              #如果enabled设置为false,则请求超时交给ribbon控制,为true,则超时作为熔断根据
              enabled: true
            isolation:
              thread:
                timeoutInMilliseconds: 5000 #断路器超时时间,默认1000ms
    
    2)解决方案

    后来发现,不仅需要设置hystrix的超时时间,还需要设置feign的超时时间,经过测试,两个需要一起设置

    feign:
      hystrix:
        enabled: true
      client:
        config:
          order-server:    #feign的value值, 若为default则为所有feign
            connectTimeout: 1000
            readTimeout: 5000
            loggerLevel: full
    hystrix:
      command:
        default:  #default全局有效,service id指定应用有效
          execution:
            timeout:
              #如果enabled设置为false,则请求超时交给ribbon控制,为true,则超时作为熔断根据
              enabled: true
            isolation:
              thread:
                timeoutInMilliseconds: 3000 #断路器超时时间,默认1000ms
    

    会使用其中较小的超时时间。一般情况下,hystrix的超时时间不宜设置过大,最大3秒,下一个问题会说具体原因,且feign的超时时间 要大于 hystrix 的最大超时时间。为什么要说是hystrix 的最大超时时间?因为hystrix可以为单个服务的单个方法设置单独的超时时间(但经过测试,发现并不起作用)。

    hystrix:
      threadpool:
        # 指定服务的配置
        #    user-service:
        #      coreSize: 20
        #      maxQueueSize: 200
        #      queueSizeRejectionThreshold: 3
        #    # userThreadPool是UserTimeOutCommand中配置的threadPoolKey
        #    userThreadPool:
        #      coreSize: 20
        #      maxQueueSize: 20
        #      queueSizeRejectionThreshold: 3
        # 这是默认的配置
        default:
          coreSize: 10
          maxQueueSize: 200
          queueSizeRejectionThreshold: 200
      command:
        # 指定feign客户端中具体的方法,格式: 服务名#方法名
            UserService#timeout():
              execution:
                isolation:
                  thread:
                    timeoutInMilliseconds: 5000
            userCommandKey:
              execution:
                isolation:
                  thread:
                    timeoutInMilliseconds: 5000
        # 这是默认的配置
        default:
          execution:
            timeout:
              enabled: true
            isolation:
              strategy: THREAD
              thread:
                timeoutInMilliseconds: 15000
                interruptOnTimeout: true
                interruptOnFutureCancel: false
              semaphore:
                maxConcurrentRequests: 2
    

    2. 提交订单时的问题

    1) 问题描述

    下单操作使用了事物,隔离级别是 READ_CONMMIT ,即 读已提交。代码如下:

        /**
         * 购买商品
         * @param orderDO 订单信息
         * @param buyGoodsList 购买的商品信息
         * @return 订单信息
         */
        @Transactional
        public OrderDTO addOrder(OrderDO orderDO, List<GoodsDTO> buyGoodsList){
    
            // 用统一的时间,避免性能消耗
            long time = DateTime.now().getMillis();
    
            insertOrder(orderDO, time);
    
            // 在商品服务检查库存并买商品
            List<GoodsDO> goodsList = getGoodsDOList(buyGoodsList);
    
            Map<Integer, Integer> goodsMap = getGoodsMap(buyGoodsList);
    
            // 更新订单状态 和金额商品件数等信息
            updateOrderState(orderDO, time, goodsList, goodsMap);
    
            // 插入订单详情
            List<OrderDetailDO> detailDOList = addDetailDOList(orderDO, time, goodsList, goodsMap);
    
            // 支付订单
            payOrder(orderDO, time);
    
            OrderDTO returnDTO = new OrderDTO();
            BeanUtils.copyProperties(orderDO, returnDTO);
            returnDTO.setDetailDOList(detailDOList);
            return returnDTO;
        }
    

    其中 getGoodsDOList 和 payOrder 分别通过feign调用 商品服务 和 支付服务,且这些服务都会使用事物

        private List<GoodsDO> getGoodsDOList(List<GoodsDTO> buyGoodsList) {
            Response<List<GoodsDO>> buyResponse = orderGoodsFeign.buyGoodsList(buyGoodsList);
            if (buyResponse.getCode() != ErrorCodeEnum.SUCCESS.getCode()){
                throw new GlobalException(buyResponse);
            }
            return buyResponse.getData();
        }
    
        private void payOrder(OrderDO orderDO, long time) {
            Response payResp = payFeign.pay(new PayDTO().setOrderId(orderDO.getId()).setUserId(orderDO.getUserId()));
            if (payResp.getCode() != ErrorCodeEnum.SUCCESS.getCode()){
                throw new GlobalException(payResp);
            }
            orderDO.setState(OrderStateEnum.SENDING.code).setUpdateTime(time);
    
            int i1 = orderMapper.updateMoney(orderDO);
            if (i1 == 0){
    //            log.error("更新订单价格和数量时失败,orderDO={}",orderDO);
    //            throw new GlobalException(ErrorCodeEnum.OPERATE_ERROR);
                //TODO 放入消息队列进行重试处理
                log.error("更新订单价格和数量时失败,orderDO={}",orderDO);
            }
        }
    

    这样就会出现分布式事物问题:

    1. 若这个过程失败了,商品服务修改的数据并不会回滚,目前出现的问题是库存减少后不会回滚
    2. 由于使用的是读已提交,所以这个订单并没有真正入库,支付服务那边通过传过去的订单id(出于安全考虑只传用户id和订单id)去查询订单信息时(如金额),并没有查到该订单
    2) 思考
    1. 出于对用户体验和系统性能考虑,用户直接操作的相关的接口应尽量实时返回,延迟最多不要超过1秒,少数特殊接口可以不超过3秒。否则做成异步处理,若有必要前端可进行轮询查询处理结果。
    2. 非用户直接操作的相关接口延迟最多不要超过1-5秒,否则做成异步。依并发量而定,并发量越高延迟需要越小,原因是并发量越高,需要处理的线程越多,线程池中线程数量固定,若每个线程的处理时间越长,则有可能线程被占用完的情况,而并发量比较大,极有可能会导致线程池缓存队列溢出,导致熔断等情况。
    3) 解决方案——待解决
    想法:
    1. 首先入库订单,状态是初始状态(即未进行库存检测、支付等),防止漏单,此时订单只有商品件数等信息,并没有支付金额信息,因为还需要去商品服务查询
    2. 检测商品库存并购买商品 (getGoodsDOList),由于用户下单和支付是连续的,所以支付之前的操作(订单初始入库,库存检测等等)必须是同步的(待考量),故检测商品库存和购买商品需要同步调用。新建一张商品中间状态表,用于存储购买商品的中间状态,如购买数量等,当下单失败时可以进行数据回滚,若回滚失败可通过消息队列进行重试。该表也可用于用户规定时间内未支付,订单自动作废时进行商品数据回滚的依据。
    3. 购买商品成功后修改订单状态等一系列操作完成后,该下单接口可返回了,然后订单进入待支付状态。剩下的就是支付功能了,与下单功能剥离,所以订单服务并不需要调用支付服务的接口。
    4. 下单完成后,前端即可无缝进入支付功能(弹出支付密码输入框等),若支付完成,则删除商品中间状态表的记录。
    5. 订单进入待支付状态后,15分钟内未支付则订单自动作废,商品状态表回滚。前端显示15分钟倒计时,但是后端作废时间应该是 15分钟5秒,即比前端显示时间多出几秒钟,用于解决如下问题:
      若刚好在15分钟那一刻,用户支付成功了,程序去查询订单时发现已经作废了,就会出现问题。若多出几秒钟,在那一刻用户支付成功了,该订单也能支付成功。若超过了15分钟,用户就不能再支付了则订单在15分5秒后作废。

    相关文章

      网友评论

          本文标题:2019-02-14 问题记录

          本文链接:https://www.haomeiwen.com/subject/hkiseqtx.html