Redian新闻
>
微服务之间调用的异常应该如何处理?

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

公众号新闻
点击关注公众号,Java干货及时送达

1前言

在分布式服务的场景下,业务服务都将进行拆分,不同服务之前都会相互调用,如何做好异常处理是比较关键的,可以让业务人员在页面使用系统报错后,很清楚的看到服务报错的原因,而不是返回代码级别的异常报错,比如NullException、IllegalArgumentException、FeignExecption等异常报错,这样就会让非技术人员看到了一头雾水,从而很降低用户的体验感。

2服务调用异常场景

这是一个很常规的服务链路调用异常,前端用户请求A服务,A服务再去请求B服务,B服务出现了异常,A服务返回的Fallback降级的报错异常,但是显然这个异常并不是很能让人理解。

这是feign服务之前调用异常的报错,通过FeignException内部的异常处理类进行处理。

3重写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,直接可以返回给前端调用接口,这样报错信息也比较清楚。

4Spirng全局异常处理

当然也可以通过全局异常处理的方式,来处理报错信息,直接在调用方服务的控制层进行切面处理即可,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<Timplements 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());
    }

}

5调用结果

@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进行返回,也是一种不错的选择。

6总结

以上两种服务之间调用异常处理的方法,分别在不同服务角度进行捕获处理,关于服务的异常处理,具体还要根据业务需求来进行处理,不同的异常可以分类进行捕获,例如基础异常、参数校验异常、工具类异常、业务检查异常等,都可以分开来进行定义,属于处理异常的一个规范定义。

来源:juejin.cn/post/7241395887943303226

--完--


ChatGPT 4.0 账号不用特殊网络了 !!!

目前GPT-4.0的功能:支持GPTs、联网功能、插件功能、上传文件、数据分析、AI画图、上传图片自动识别功能等 。这些功能都是3.5不能具备的 !


不过,这个功能目前只有升级Plus会员,才能使用 。

大家也都知道,官方开通的Plus会员,一个月20美元,相当于人民币180元每月,而且经常有被封号的风险 。

所以,这边一次性买了50多个Plus会员放在一个系统的池子里,共享给大家使用 。每月只需要90元,就可以直接使用 GPT 4.0 ,而且国内网络就可以直接登录 ,不需要额外的上网工具 。

复制购买链接到浏览器打开:https://qeosq.xetlk.com/s/408XZG

购买这个账号,一直有售后,不用担心中途封号或者用不了

或者直接微信付款后,加我微信:itcodexy,备注:90元购买plus账号
我会立马通过微信好友请求 。

扫码可以直接购买



微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
基于 RabbitMQ 和 Redis,美版“携程”网使用微服务提升搜索扩展性发现左室肥厚,该怎么处理?中国左室肥厚诊治路径发布2024年应该如何抄底中国股市?华男裤裆异常大!出境时被眼尖海关看下 “那里”鼓鼓囊囊有异常Yuval Harari and Ari 讨论好多问题 ,有趣又智慧如果你有1万美元应该如何投资?微服务过微怎么办?字节跳动提供了一种合并编译的方案|QCon《跌宕起伏心灵煎熬的14天》(3) 【保险公司评定和事故示意图】离婚伤人又伤财,富人应该如何“管理”婚姻赌城里抠出上万$,我最喜欢的日子美国移民申请在移民局阶段收到RFE?应如何处理?为持有人挽回损失是当务之急!基金经理道歉,四季报四大关键词曝光在最脆弱的时间下手!纽约华人男医生,以职务之便性侵至少14女。宝宝吐的奶中有黏液,严重吗?如何处理?编剧应该如何带着“镜头化”思维进行创作?后疫情时代,儿童呼吸疾病管理应该如何规范进行?| 秋冬呼吸大咖谈口臭的人为啥闻不到自己嘴里的异味74、长篇民国小说《永泰里》第十五章 黄雀在后(4)爸、你那边还好吧EB5隐性排期已来!想要实现本金和绿卡安全,申请人应该如何跑赢排期?新手编剧如何处理剧本结构?如何优雅地实现接口统一调用【热议】Dragon还是Loong?网民热议应该如何翻译“龙”微服务穷途末路?新招式能否开启“黄金演进期”?如何在SpringBoot中优雅地重试调用第三方API?美国二次通胀再起,四大板块应该如何配套如何处理抽离客观纪录和主动介入对谈的节奏?除了打怪升级,英雄救美里的感情线要怎样处理?趣图:遇到这种异常,你会如何处理如何处理复杂且棘手的问题?《灰度决策》​混沌学园一期同学企业古茗将上市,15个点看下沉茶饮新王的异同“我家在顶楼,把被子晒出去有阳光的味道。”居住需求逐渐差异化,我们应该如何“好好住”?|投资笔记内镜下遇到这样的病灶,你会如何处理?(常见又特别)硅谷精英杀妻案今日开庭!死者的人寿保险会如何处理?Netflix 如何处理其容器平台 Titus上 的孤儿 Pod 问题
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。