前语

在分布式服务的场景下,事务服务都将进行拆分,不同服务之前都会彼此调用,如何做好反常处理是比较要害的,能够让事务人员在页面使用体系报错后,很清楚的看到服务报错的原因,而不是回来代码级别的反常报错,比如NullException、IllegalArgumentException、FeignExecption等反常报错,这样就会让非技术人员看到了一头雾水,然后很下降用户的体验感。

服务调用反常场景

微服务之间调用的异常应该如何处理

这是一个很常规的服务链路调用反常,前端用户恳求A服务,A服务再去恳求B服务,B服务呈现了反常,A服务回来的Fallback降级的报错反常,可是显然这个反常并不是很能让人了解。

微服务之间调用的异常应该如何处理
这是feign服务之前调用反常的报错,经过FeignException内部的反常处理类进行处理。

重写Feign反常处理

首要我们能够经过完成feign的ErrorDecoder接口重写它的的decode办法,进行自界说反常处理,针对每个feign接口的反常报错,抛出自界说的exception将错误信息和错误码回来。

FeignExceptionConfiguration 自界说反常处理类

@Slf4j
@Configuration
public class FeignExceptionConfiguration {
    @Bean
    public ErrorDecoder errorDecoder() {
        return new UserErrorDecoder();
    }
    /**
     * 重新完成feign的反常处理,捕捉restful接口回来的json格局的反常信息
     *
     */
    public class UserErrorDecoder implements ErrorDecoder {
        @Override
        public Exception decode(String methodKey, Response response) {
            Exception exception = new MyException();
            ObjectMapper mapper = new ObjectMapper();
            //空属性处理
            mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_EMPTY);
            //设置输入时疏忽在JSON字符串中存在但Java目标实践没有的属性
            mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            //禁止使用int代表enum的order来反序列化enum
            mapper.configure(DeserializationConfig.Feature.FAIL_ON_NUMBERS_FOR_ENUMS, true);
            try {
                String json = Util.toString(response.body().asReader());
                log.info("反常回来成果:"+ JSON.toJSONString(json));
                exception = new RuntimeException(json);
                if (StringUtils.isEmpty(json)) {
                    return null;
                }
                FeignFaildResult result = mapper.readValue(json, FeignFaildResult.class);
                // 事务反常包装成自界说反常类MyException
                if (result.getCode() != 200) {
                    exception = new MyException(result.getMsg(),result.getCode());
                }
            } catch (IOException ex) {
                log.error(ex.getMessage(), ex);
            }
            return exception;
        }
    }
}

微服务之间调用的异常应该如何处理
这儿能够看到,经过处理后的反常回来成果,已经过滤掉feign的一长串反常,只留下code、msg、data等信息,直接映射到成果集目标上,经过自界说反常回来。

FeignFaildResult 反常成果集回来

/**
 *  依据 json 来界说需求的字段
 */
@Data
public class FeignFaildResult {
    private String msg;
    private int code;
}

MyException自界说反常

import lombok.Data;
@Data
public class MyException extends RuntimeException {
    // 自界说反常代码
    private int status = 503;
    public MyException() {
    }
    // 结构办法
    public MyException(String message, int status) {
        super(message);
        this.status = status;
    }
}

FeignClient接口界说

@FeignClient(contextId = "iTestServiceClient",
        value = "Lxlxxx-system2",
        fallback = TestServiceFallbackFactory.class,
        configuration = FeignExceptionConfiguration.class)
public interface ITestServiceClient {
    /**
     * 服务调用测验办法
     * @return
     */
    @GetMapping("/test/method")
    public R<String> testRequestMethod() throws Exception;
}

经过@FeignClient注解里边的configuration属性,开启自界说反常处理。

被调用方服务

被调用方服务事务处理直接抛出反常即可

微服务之间调用的异常应该如何处理

调用成果

代码中throw的反常message,直接能够回来给前端调用接口,这样报错信息也比较清楚。

微服务之间调用的异常应该如何处理

Spirng大局反常处理

当然也能够经过大局反常处理的方式,来处理报错信息,直接在调用方服务的控制层进行切面处理即可,Spring 3.2也提供了相应的的注解类@ControllerAdvice,配合@ExceptionHandler注解,即可完成大局反常处理。

ResultCode反常错误码界说

首要先界说反常错误码枚举

public enum ResultCode {
    /*
     * 通用错误码 Code约好
     * 0表明成功[SUCCESS],                        看到0,事务处理成功。
     * 10000 - 19999表明事务警告[WARN_],           这种code不是常规武器,能免则免。
     * 20000 - 29999表明通用错误代码[ERR_],        各个体系通用的错误代码。
     * 30000 - 39999表明事务自界说错误代码[DIY_]
     * 40000 - 49999表明体系错误[SYS_],            体系错误独自拉出来,作为独立区域。理论上这部分也是通用的,不能够自界说。
     */
    SUCCESS("0", "操作成功"),
    ERR_LACK_PARAM("20001", "恳求参数不正确"),
    ERR_NO_LOGIN("20002", "用户未登录"),
    ERR_NO_RIGHT("20003", "没有权限拜访该资源"),
    ERR_NO_SERVICE("20004", "资源不存在"),
    ERR_WRONG_STATUS("20005", "资源的当时状况不支持该操作"),
    ERR_LACK_CONFIG("20006", "短少必要的配置项"),
    ERR_PROCESS_FAIL("20007", "事务处理失利"),
    ERR_THIRD_API_FAIL("20008", "调用第三方接口失利"),
    ERR_IS_DELETED("20009", "资源已删除"),
    ERR_UPDATE_FAIL("20010", "更新操作失利"),
    SYS_MAINTENANCE("40001", "体系维护中"),
    SYS_BUSY("40002", "体系繁忙"),
    SYS_EXCEPTION("40003", "体系反常");
    private String code;
    private String msg;
    private ResultCode(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    public String getCode() {
        return this.code;
    }
    public void setCode(String code) {
        this.code = code;
    }
    public String getMsg() {
        return this.msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public static ResultCode get(String code) {
        ResultCode[] var1 = values();
        int var2 = var1.length;
        for (int var3 = 0; var3 < var2; ++var3) {
            ResultCode statusEnum = var1[var3];
            if (statusEnum.getCode().equals(code)) {
                return statusEnum;
            }
        }
        return null;
    }
    public String getErrorMsg(Object... params) {
        String errorMsg = null;
        if (params != null && params.length != 0) {
            MessageFormat msgFmt = new MessageFormat(this.msg);
            errorMsg = msgFmt.format(params);
        } else {
            errorMsg = this.msg;
        }
        return errorMsg;
    }
}

BaseResult一致回来成果目标

@Data
public class BaseResult<T> implements Serializable {
    private static final long serialVersionUID = 621986096326899992L;
    private String message;
    private String errorCode;
    private T data;
    public BaseResult() {
    }
    public BaseResult(String message, String errorCode) {
        this.message = message;
        this.errorCode = errorCode;
    }
    public static <T> BaseResult<T> success() {
        BaseResult<T> baseResult = new BaseResult<>();
        baseResult.setMessage(ResultCode.SUCCESS.getMsg());
        baseResult.setErrorCode(ResultCode.SUCCESS.getCode());
        return baseResult;
    }
    public static <T> BaseResult<T> success(T result) {
        BaseResult<T> baseResult = new BaseResult<>();
        baseResult.setData(result);
        baseResult.setMessage(ResultCode.SUCCESS.getMsg());
        baseResult.setErrorCode(ResultCode.SUCCESS.getCode());
        return baseResult;
    }
    public static <T> BaseResult<T> fail(ResultCode error) {
        BaseResult<T> baseResult = new BaseResult<>();
        baseResult.setErrorCode(error.getCode());
        baseResult.setMessage(error.getMsg());
        return baseResult;
    }
    public static <T> BaseResult<T> error(ResultCode error,String message) {
        BaseResult<T> baseResult = new BaseResult<>();
        baseResult.setErrorCode(error.getCode());
        baseResult.setMessage(message);
        return baseResult;
    }
    public static <T> BaseResult<T> fail(ResultCode error, Exception e) {
        BaseResult<T> baseResult = new BaseResult<>();
        baseResult.setErrorCode(error.getCode());
        baseResult.setMessage(e.getMessage());
        return baseResult;
    }
    public Boolean isSuccess() {
        return "0".equals(this.errorCode) ? true : false;
    }
}

CommonException自界说大局反常处理类

public class CommonException extends RuntimeException {
    private String code;
    /**
     * 自己暂时自界说状况码和状况信息
     *
     * @param code  状况码
     * @param message 状况信息
     */
    public CommonException(String code, String message) {
        super(message);
        this.code = code;
    }
    /**
     * @param resultCode 从枚举目标中获取状况码和状况信息
     */
    public CommonException(ResultCode resultCode) {
        super(resultCode.getMsg());
        this.code = resultCode.getCode();
    }
}

ExceptionController大局反常处理控制类

@ControllerAdvice
public class ExceptionController {
    /**
     * CommonException
     * @param e
     * @return
     */
    @ExceptionHandler(CommonException.class)
    @ResponseBody
    public BaseResult handlerException(CommonException e){
        //反常回来false,Result是上一篇接口回来目标。
        return new BaseResult(e.getMessage(),e.getCode());
    }
}

调用成果

@RestController
@Slf4j
public class TestController {
    @Autowired
    private ITestServiceClient iTestServiceClient;
    @GetMapping("/testMethod")
    public BaseResult testMethod() throws Exception {
        try {
            log.info("经过feign调用system2服务~~~~~~~~~");
            R<String> stringR = iTestServiceClient.testRequestMethod();
        } catch (Exception e) {
            throw new CommonException(ResultCode.SYS_EXCEPTION.getCode(), ResultCode.SYS_EXCEPTION.getMsg());
        }
        return BaseResult.success();
    }

微服务之间调用的异常应该如何处理

微服务之间调用的异常应该如何处理

我仍是模拟上面的场景,A服务去调B服务,B服务中抛出反常,经过界说的ExceptionController进行捕获反常,并且依据自界说的反常,拿到反常code和message进行回来,也是一种不错的选择。

总结

以上两种服务之间调用反常处理的办法,分别在不同服务角度进行捕获处理,关于服务的反常处理,详细还要依据事务需求来进行处理,不同的反常能够分类进行捕获,例如基础反常、参数校验反常、工具类反常、事务检查反常等,都能够分开来进行界说,属于处理反常的一个规范界说。