微服务治理设计与实践

当项目系统到了一定的成熟度,业务庞大且稳定的程度后,往往会引入一些服务治理的技术。我们希望能够更好管理我们的系统。往小了说可以小到异常码的异常治理。系统在线上生产环境出现了各种各样异常的时候,我们如果对异常进行处理呢?别人如何来理解我的系统发生的异常呢?往大了说到链路追踪和日志治理 : 定位线上业务故障、性能低、丢数据。甚至我们需要一个监控系统对系统的各项指标进行监测:硬件层面的cpu、内存。JVM层面的fullGC频率、业务接口调用频率。

系统异常治理错误码规范分析

异常处理包括系统异常发生时的捕获和处理、接口异常的规范、对异常的统一化的理解。

通常来说:线上跑的系统出现了异常,最后都应该是在对外提供的接口处提供。比方说http接口、rpc接口、mq消费、后台定时调度线程。这几种情况往往是驱动了你的系统运行。

HTTP接口拿到了一个请求,开始通过service -> dao -> mapper ->rpc -> es -> redis等一系列的手段完成这个请求的处理,完成功能。功能完成期间可能会出一些异常,之后层层上抛。异常收敛到http接口入口处,发现本次请求异常了,在这里把异常做一个规范化的封装和定义返回给你的前端。前端根据规划化的一套异常定义:理解你的异常是什么并用他的用户的语言反馈出来

RPC接口对其他系统提供了一个接口。rpc接口入口处拿到了这个异常,针对这个异常封装和定义。对异常做一个治理返回(规范化)

当然对于MQ或者后台定时调度线程也是一样的。如果执行一个功能出了问题,MQ往往会返回一个异常给broker,让他下次来重试消费。定时调度一个功能如果出错了 ,我们也要对异常规范化处理。比如记录这个异常日志并上报异常给监控系统,发送短信或邮件告警。

HTTP参数检查示例:

    protected void processInternal(ProcessContext processContext) {
        CreateOrderRequest createOrderRequest = processContext.get("createOrderRequest");
        ParamCheckUtil.checkObjectNonNull(createOrderRequest);

        // 订单ID
        String orderId = createOrderRequest.getOrderId();
        ParamCheckUtil.checkStringNonEmpty(orderId, OrderErrorCodeEnum.ORDER_ID_IS_NULL);
        OrderInfoDO order = orderInfoDAO.getByOrderId(orderId);
        ParamCheckUtil.checkObjectNull(order, OrderErrorCodeEnum.ORDER_EXISTED);

        // 业务线标识
        Integer businessIdentifier = createOrderRequest.getBusinessIdentifier();
        ParamCheckUtil.checkObjectNonNull(businessIdentifier, OrderErrorCodeEnum.BUSINESS_IDENTIFIER_IS_NULL);
        if (BusinessIdentifierEnum.getByCode(businessIdentifier) == null) {
            throw new OrderBizException(OrderErrorCodeEnum.BUSINESS_IDENTIFIER_ERROR);
        }

        // 用户ID
        String userId = createOrderRequest.getUserId();
        ParamCheckUtil.checkStringNonEmpty(userId, OrderErrorCodeEnum.USER_ID_IS_NULL);

        // 订单类型
        Integer orderType = createOrderRequest.getOrderType();
        ParamCheckUtil.checkObjectNonNull(businessIdentifier, OrderErrorCodeEnum.ORDER_TYPE_IS_NULL);
        if (OrderTypeEnum.getByCode(orderType) == null) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_TYPE_ERROR);
        }

        // 卖家ID
        String sellerId = createOrderRequest.getSellerId();
        ParamCheckUtil.checkStringNonEmpty(sellerId, OrderErrorCodeEnum.SELLER_ID_IS_NULL);

        // 配送类型
        Integer deliveryType = createOrderRequest.getDeliveryType();
        ParamCheckUtil.checkObjectNonNull(deliveryType, OrderErrorCodeEnum.USER_ADDRESS_ERROR);
        if (DeliveryTypeEnum.getByCode(deliveryType) == null) {
            throw new OrderBizException(OrderErrorCodeEnum.DELIVERY_TYPE_ERROR);
        }

        // 地址信息
        String province = createOrderRequest.getProvince();
        String city = createOrderRequest.getCity();
        String area = createOrderRequest.getArea();
        String streetAddress = createOrderRequest.getStreet();
        ParamCheckUtil.checkStringNonEmpty(province, OrderErrorCodeEnum.USER_ADDRESS_ERROR);
        ParamCheckUtil.checkStringNonEmpty(city, OrderErrorCodeEnum.USER_ADDRESS_ERROR);
        ParamCheckUtil.checkStringNonEmpty(area, OrderErrorCodeEnum.USER_ADDRESS_ERROR);
        ParamCheckUtil.checkStringNonEmpty(streetAddress, OrderErrorCodeEnum.USER_ADDRESS_ERROR);

        // 区域ID
        String regionId = createOrderRequest.getRegionId();
        ParamCheckUtil.checkStringNonEmpty(regionId, OrderErrorCodeEnum.REGION_ID_IS_NULL);

        // 经纬度
        BigDecimal lon = createOrderRequest.getLon();
        BigDecimal lat = createOrderRequest.getLat();
        ParamCheckUtil.checkObjectNonNull(lon, OrderErrorCodeEnum.USER_LOCATION_IS_NULL);
        ParamCheckUtil.checkObjectNonNull(lat, OrderErrorCodeEnum.USER_LOCATION_IS_NULL);

        // 收货人信息
        String receiverName = createOrderRequest.getReceiverName();
        String receiverPhone = createOrderRequest.getReceiverPhone();
        ParamCheckUtil.checkStringNonEmpty(receiverName, OrderErrorCodeEnum.ORDER_RECEIVER_IS_NULL);
        ParamCheckUtil.checkStringNonEmpty(receiverPhone, OrderErrorCodeEnum.ORDER_RECEIVER_IS_NULL);

        // 客户端设备信息
        String clientIp = createOrderRequest.getClientIp();
        ParamCheckUtil.checkStringNonEmpty(clientIp, OrderErrorCodeEnum.CLIENT_IP_IS_NULL);

        // 商品条目信息
        List<CreateOrderRequest.OrderItemRequest> orderItemRequestList = createOrderRequest.getOrderItemRequestList();
        ParamCheckUtil.checkCollectionNonEmpty(orderItemRequestList, OrderErrorCodeEnum.ORDER_ITEM_IS_NULL);

        for (CreateOrderRequest.OrderItemRequest orderItemRequest : orderItemRequestList) {
            Integer productType = orderItemRequest.getProductType();
            Integer saleQuantity = orderItemRequest.getSaleQuantity();
            String skuCode = orderItemRequest.getSkuCode();
            ParamCheckUtil.checkObjectNonNull(productType, OrderErrorCodeEnum.ORDER_ITEM_PARAM_ERROR);
            ParamCheckUtil.checkObjectNonNull(saleQuantity, OrderErrorCodeEnum.ORDER_ITEM_PARAM_ERROR);
            ParamCheckUtil.checkStringNonEmpty(skuCode, OrderErrorCodeEnum.ORDER_ITEM_PARAM_ERROR);
        }

        // 订单费用信息
        List<CreateOrderRequest.OrderAmountRequest> orderAmountRequestList = createOrderRequest.getOrderAmountRequestList();
        ParamCheckUtil.checkCollectionNonEmpty(orderAmountRequestList, OrderErrorCodeEnum.ORDER_AMOUNT_IS_NULL);

        for (CreateOrderRequest.OrderAmountRequest orderAmountRequest : orderAmountRequestList) {
            Integer amountType = orderAmountRequest.getAmountType();
            ParamCheckUtil.checkObjectNonNull(amountType, OrderErrorCodeEnum.ORDER_AMOUNT_TYPE_IS_NULL);

            if (AmountTypeEnum.getByCode(amountType) == null) {
                throw new OrderBizException(OrderErrorCodeEnum.ORDER_AMOUNT_TYPE_PARAM_ERROR);
            }
        }
        Map<Integer, Integer> orderAmountMap = orderAmountRequestList.stream()
                .collect(Collectors.toMap(CreateOrderRequest.OrderAmountRequest::getAmountType,
                        CreateOrderRequest.OrderAmountRequest::getAmount));

        // 订单支付原价不能为空
        if (orderAmountMap.get(AmountTypeEnum.ORIGIN_PAY_AMOUNT.getCode()) == null) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_ORIGIN_PAY_AMOUNT_IS_NULL);
        }
        // 订单运费不能为空
        if (orderAmountMap.get(AmountTypeEnum.SHIPPING_AMOUNT.getCode()) == null) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_SHIPPING_AMOUNT_IS_NULL);
        }
        // 订单实付金额不能为空
        if (orderAmountMap.get(AmountTypeEnum.REAL_PAY_AMOUNT.getCode()) == null) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_REAL_PAY_AMOUNT_IS_NULL);
        }

        String couponId = createOrderRequest.getCouponId();
        if (StringUtils.isNotEmpty(couponId)) {
            // 订单优惠券抵扣金额不能为空
            if (orderAmountMap.get(AmountTypeEnum.COUPON_DISCOUNT_AMOUNT.getCode()) == null) {
                throw new OrderBizException(OrderErrorCodeEnum.ORDER_DISCOUNT_AMOUNT_IS_NULL);
            }
        }

        // 订单支付信息
        List<CreateOrderRequest.PaymentRequest> paymentRequestList = createOrderRequest.getPaymentRequestList();
        ParamCheckUtil.checkCollectionNonEmpty(paymentRequestList, OrderErrorCodeEnum.ORDER_PAYMENT_IS_NULL);

        Integer totalPayAmount = 0;
        for (CreateOrderRequest.PaymentRequest paymentRequest : paymentRequestList) {
            Integer payType = paymentRequest.getPayType();
            Integer accountType = paymentRequest.getAccountType();
            if (payType == null || PayTypeEnum.getByCode(payType) == null) {
                throw new OrderBizException(OrderErrorCodeEnum.PAY_TYPE_PARAM_ERROR);
            }
            if (accountType == null || AccountTypeEnum.getByCode(accountType) == null) {
                throw new OrderBizException(OrderErrorCodeEnum.ACCOUNT_TYPE_PARAM_ERROR);
            }
            Integer payAmount = paymentRequest.getPayAmount();
            if (payAmount == null) {
                // 支付金额不能为空
                throw new OrderBizException(OrderErrorCodeEnum.PAY_AMOUNT_IS_NULL);
            }
            totalPayAmount += payAmount;
        }
        if (!totalPayAmount.equals(orderAmountMap.get(AmountTypeEnum.REAL_PAY_AMOUNT.getCode()))) {
            throw new OrderBizException(OrderErrorCodeEnum.TOTAL_PAY_AMOUNT_ERROR);
        }
    }

RPC风控异常处理示例:

    /**
     * 订单风控检查
     */
    @SentinelResource(value = "RiskRemote:checkOrderRisk", fallbackClass = RiskRemoteFallback.class, fallback = "checkOrderRiskFallback")
    public CheckOrderRiskDTO checkOrderRisk(CheckOrderRiskRequest checkOrderRiskRequest) {
        JsonResult<CheckOrderRiskDTO> jsonResult = riskApi.checkOrderRisk(checkOrderRiskRequest);
        if (!jsonResult.getSuccess()) {
            throw new OrderBizException(jsonResult.getErrorCode(), jsonResult.getErrorMessage());
        }
        return jsonResult.getData();
    }

状态机统一异常处理

首先我们看一个示例:状态机统一异常处理

        @Override
        public void fire(Object event, Object context) {
            super.fire(event, context);
            Exception exception = exceptionThreadLocal.get();
            if (exception != null) {
                exceptionThreadLocal.remove();
                if (exception instanceof BaseBizException) {
                    throw (BaseBizException) exception;
                } else {
                    throw new RuntimeException(exception);
                }
            }
        }

正常情况下状态机:调用fire(event,context) 方法,会调用onStateChange方法。假如onStateChange方法抛出业务异常,这里会被状态机接管,然后使用一个Squirrel-Foundation内部的异常TransitionException。

可以看到它对我们的业务异常进行包装。然后抛出TransitionException异常。

我们一般的情景:在SpringBoot中调用状态机开始状态流转,调用了fire方法,接着得到一个TransitionException异常,但是这显然不是我们想要的结果。我们希望onStateChange方法抛出的异常如果是业务异常BaseBizException,则fire方法抛出的也是业务异常。

所以这里采用了一种方式,在onStateChange方法中使用ThreadLocal将状态保存起来,
那么fire方法就无法检测到我们实际业务代码是否抛出了异常,此时等fire方法返回的时候,我们再判断ThreadLocal中是否有异常,
如果有就直接抛出,这样就可以实现我们所需要的结果。


异常码与消息规范设计

为了应对系统里各个地方频繁出现的一些异常,我们需要设计一套多平台甚至公司级别的异常治理。各个系统都提供一套统一的异常码以及配套异常消息提示。其实通俗来说就是code+msg抛出

以下给出一些通用异常示例:

    // 正向订单业务异常枚举
    /**
     * 生成订单号
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 00      | 00    |
     * +--------+---------+-------+
     */
    ORDER_NO_TYPE_ERROR("160000", "订单号类型错误"),

    /**
     * 订单下单
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 01      | 00    |
     * +--------+---------+-------+
     */
    ORDER_EXISTED("160100", "订单已存在"),
    ORDER_ITEM_IS_NULL("160108", "订单商品信息不能为空"),
    TOTAL_PAY_AMOUNT_ERROR("160123", "总的支付金额错误"),

    /**
     * 订单预支付
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 02      | 00    |
     * +--------+---------+-------+
     */
    ORDER_PAY_AMOUNT_ERROR("160200", "订单支付金额错误"),
    ORDER_PRE_PAY_ERROR("160201", "订单支付发生错误"),
    ORDER_PRE_PAY_EXPIRE_ERROR("160202", "已经超过支付订单的截止时间"),

    /**
     * 订单支付回调
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 03      | 00    |
     * +--------+---------+-------+
     */
    ORDER_PAY_CALLBACK_ERROR("160300", "订单支付回调发生错误"),
    ORDER_CANCEL_PAY_CALLBACK_PAY_TYPE_SAME_ERROR("160304", "接收到支付回调的时候订单已经取消,且支付回调为同种支付方式"),
    ORDER_CANCEL_PAY_CALLBACK_PAY_TYPE_NO_SAME_ERROR("160305", "接收到支付回调的时候订单已经取消,且支付回调非同种支付方式"),


    /**
     * 移除订单
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 04      | 00    |
     * +--------+---------+-------+
     */
    ORDER_CANNOT_REMOVE("160400", "订单不允许删除"),

    /**
     * 调整订单配置地址
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 05      | 00    |
     * +--------+---------+-------+
     */
    ORDER_NOT_ALLOW_TO_ADJUST_ADDRESS("160500", "订单不允许调整配送地址"),
    ORDER_DELIVERY_NOT_FOUND("160501", "订单配送记录不存在"),
    ORDER_DELIVERY_ADDRESS_HAS_BEEN_ADJUSTED("160502", "订单配送地址已被调整过"),

    /**
     * 订单履约
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 09      | 00    |
     * +--------+---------+-------+
     */
    ORDER_FULFILL_ERROR("160900", "订单履约失败"),
    ORDER_NOT_ALLOW_INFORM_WMS_RESULT("160901", "订单不允许通知物流配送结果"),


    /**
     * 取消订单
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 08      | 00    |
     * +--------+---------+-------+
     */
    CANCEL_ORDER_CHILD_STATUS_CHANGED("160814", "子订单状态已变更,不能取消订单"),
    CANCEL_ORDER_LINKED_STATUS_CHANGED("160815", "当前订单的主单包含其他不允许取消的子订单,该笔订单取消失败"),
    CANCEL_ORDER_LINKED_STATUS_CANCELED("160816", "当前订单包含已取消过的订单,不能重复取消"),


    /**
     * 正向通用异常
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 40      | 00    |
     * +--------+---------+-------+
     */
    USER_ID_IS_NULL("164000", "用户ID不能为空"),
    ORDER_ID_IS_NULL("164001", "订单号不能为空"),
    BUSINESS_IDENTIFIER_IS_NULL("164002", "业务线标识不能为空"),


    //  逆向订单业务异常枚举
    /**
     * 用户发起售后申请
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 50      | 00    |
     * +--------+---------+-------+
     */
    AFTER_SALE_ORDER_ID_IS_NULL("165000", "发起售后的订单号不能为空"),
    AFTER_SALE_ORDER_STATUS_ERROR("165007", "售后单状态错误,非已签收状态的订单不能申请售后"),
    AFTER_SALE_PROCESS_RETURN_GOODS("165009", "处理售后退款重复"),
  

    /**
     * 缺品
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 51      | 00    |
     * +--------+---------+-------+
     */
    LACK_SKU_CODE_IS_NULL("165105", "缺品skuCode不能为空"),
    LACK_NUM_IS_LT_0("165106", "缺品数量不能小于0"),
    LACK_NUM_IS_GE_SKU_ORDER_ITEM_SIZE("165109", "缺品数量不能大于或等于下单商品数量"),

    /**
     * 撤销售后
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 52      | 00    |
     * +--------+---------+-------+
     */
    REVOKE_AFTER_SALE_REQUEST_IS_NULL("165200", "撤销售后入参不能为空"),
    REVOKE_AFTER_SALE_ID_IS_NULL("165201", "撤销售后ID不能为空"),
    REVOKE_AFTER_SALE_CANNOT_FREIGHT_OR_COUPONS("165205", "已产生运费or优惠券售后单,无法撤销"),

    /**
     * 查询售后列表
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 53      | 00    |
     * +--------+---------+-------+
     */
    AFTER_SALE_LIST_BUSINESS_IDENTIFIER_IS_NULL("165300", "查询售后列表业务线标识不能为空"),

    /**
     * 查询售后详情
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 54      | 00    |
     * +--------+---------+-------+
     */
    AFTER_SALE_DETAIL_ID_IS_NULL("165400", "查询售后详情售后ID不能为空"),

    /**
     * 支付退款回调
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 55      | 00    |
     * +--------+---------+-------+
     */
    PAY_REFUND_CALLBACK_STATUS_FAILED("165500", "支付退款回调状态错误"),
    PAY_REFUND_CALLBACK_BATCH_NO_IS_NULL("165503", "支付退款回调批次号不能为空"),
    PAY_REFUND_CALLBACK_AFTER_SALE_ID_IS_NULL("165509", "支付退款回调售后订单号不能为空"),

    /**
     * 接收售后审核结果
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 56      | 00    |
     * +--------+---------+-------+
     */
    AFTER_SALE_AUDIT_TYPE_IS_ERROR("165600", "优惠券售后单无需审核"),
    AFTER_SALE_AUDIT_CANNOT_REPEAT("165601", "不能重复处理客服审核信息"),
    AFTER_SALE_AUDIT_ITEM_CANNOT_NULL("165602", "售后商品信息不能为空"),

    /**
     * 查询售后支付信息
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 57      | 00    |
     * +--------+---------+-------+
     */
    AFTER_SALE_REFUND_INFO_IS_NULL("165700", "售后支付信息不能为空"),

    /**
     * 实际退款
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 58      | 00    |
     * +--------+---------+-------+
     */
    REFUND_MONEY_REPEAT("165800", "执行退款操作重复"),
    REFUND_ORDER_AMOUNT_FAILED("165801", "调用支付退款接口失败"),
    REFUND_ENUM_STATUS_IS_NULL("165802", "退款枚举状态值不能是空"),

    /**
     * 逆向通用异常
     * +--------+---------+-------+
     * | first  | middle  | last  |
     * +========+=========+=======+
     * | 16     | 90      | 00    |
     * +--------+---------+-------+
     */
    COLLECTION_PARAM_CANNOT_BEYOND_MAX_SIZE("169001", "[{0}]大小不能超过{1}"),
    SEND_MQ_FAILED("169003", "发送MQ消息失败"),
    ORDER_ID_PATTERN_ERROR("169005", "订单ID格式不正确"),
    ;

没有异常的规范肯定是不行的,在系统调用的入口处一定要把异常通用化标准化的抛出。各个系统之间接口对接的时候都是靠统一异常码交互。

多个系统会有多个功能流程,每个流程里也会有多个异常点,所以针对每个异常点都要设计异常码与提示。那么问题来了,那么多系统那么多流程代码,异常体系该如何去设计呢?


业务异常枚举错误码code定义⽬前做如下规范:设计如上述代码示例

  • code总⻓度为6
  • 前两位表示微服务或者通⽤异常(公司级别定义)
  • 中间两位表示功能模块
  • 最后两位⽤来递增表示具体的异常。

通用异常code:

  • 10:表示客户端通⽤异常
  • 20:表示服务端通⽤的异常
系统未知异常-1
客户端HTTP请求⽅法错误1001
客户端请求体JSON格式错误或字 段类型不匹配1003
业务⽅法参数检查不通过2001

	// =========== 系统级别未知异常 =========

    /**
     * 系统未知错误
     */
    SYSTEM_UNKNOWN_ERROR("-1", "系统未知错误"),

    // =========== 客户端异常 =========

    /**
     * 客户端HTTP请求方法错误
     * org.springframework.web.HttpRequestMethodNotSupportedException
     */
    CLIENT_HTTP_METHOD_ERROR("1001", "客户端HTTP请求方法错误"),

    /**
     * 客户端@RequestBody请求体JSON格式错误或字段类型错误
     * org.springframework.http.converter.HttpMessageNotReadableException
     * <p>
     * eg:
     * 1、参数类型不对:{"test":"abc"},本身类型是Long
     * 2、{"test":}  test属性没有给值
     */
    CLIENT_REQUEST_BODY_FORMAT_ERROR("1003", "客户端请求体JSON格式错误或字段类型不匹配"),


    // =========== 服务端异常 =========

    /**
     * 通用的业务方法入参检查错误
     * java.lang.IllegalArgumentException
     */
    SERVER_ILLEGAL_ARGUMENT_ERROR("2001", "业务方法参数检查不通过"),
    ;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值