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

2023-11-05

前言

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

服务调用异常场景

添加图片注释,不超过 140 字(可选)

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

添加图片注释,不超过 140 字(可选)

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

重写Feign异常处理

首先我们可以通过实现feign的ErrorDecoder接口重写它的的decode方法,进行自定义异常处理,针对每个feign接口的异常报错,抛出自定义的exception将错误信息和错误码返回。

FeignExceptionConfiguration 自定义异常处理类

 
 

typescript复制代码@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; } } }

添加图片注释,不超过 140 字(可选)

这里可以看到,经过处理后的异常返回结果,已经过滤掉feign的一长串异常,只留下code、msg、data等信息,直接映射到结果集对象上,通过自定义异常返回。

FeignFaildResult 异常结果集返回

 
 

arduino复制代码/** * 根据 json 来定义需要的字段 */ @Data public class FeignFaildResult { private String msg; private int code; }

MyException自定义异常

 
 

scala复制代码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接口定义

 
 

kotlin复制代码@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属性,开启自定义异常处理。

被调用方服务

被调用方服务业务处理直接抛出异常即可

添加图片注释,不超过 140 字(可选)

调用结果

代码中throw的异常message,直接可以返回给前端调用接口,这样报错信息也比较清楚。

添加图片注释,不超过 140 字(可选)

Spirng全局异常处理

当然也可以通过全局异常处理的方式,来处理报错信息,直接在调用方服务的控制层进行切面处理即可,Spring 3.2也提供了相应的的注解类@ControllerAdvice,配合@ExceptionHandler注解,即可实现全局异常处理。

ResultCode异常错误码定义

首先先定义异常错误码枚举

 
 

typescript复制代码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统一返回结果对象

 
 

typescript复制代码@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自定义全局异常处理类

 
 

scala复制代码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全局异常处理控制类

 
 

less复制代码@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()); } }

调用结果

 
 

less复制代码@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(); }

添加图片注释,不超过 140 字(可选)

添加图片注释,不超过 140 字(可选)

我还是模拟上面的场景,A服务去调B服务,B服务中抛出异常,通过定义的ExceptionController进行捕获异常,并且根据自定义的异常,拿到异常code和message进行返回,也是一种不错的选择。

总结

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

                                                                                                         资源获取:
大家 点赞、收藏、关注、评论啦 、 查看
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • Vue计算两个datetime共多少天

    假如starttime和endtime都是YYYY MM DD HH mm ss类型 将选择器的默认时间格式 object 转换成时间戳 开始时间减去结束时间 时间戳的形式进行运算 s然后转换成天数 通过toFixed函数保留两位小数 th
  • c语言中swap的意思,C语言中swap的作用和用法?

    慕村225694 swap函数一般是一个程序员自定义函数 通常是实现两个变量数值的交换 比如123int a 2 int b 3 swap a b 一般用到变量数值交换 交换后a 3 b 2 实现的方法多种多样 比如下面几种写法 1 通过使

随机推荐

  • Python——类的方法重写、property、运算符重载

    1 super 函数 主要是用来调用父类的方法 在子类中调用父类的方法时进行使用 2 私有方法 私有属性 1 定义方法 在类的内部 使用def关键字可以为类定义一个方法 与一般函数定义不同 类方法必须包含参数self 且为第一个参数 2 私
  • ubuntu搭建vpn步骤

    1 搭建环境 系统 Ubuntu 18 04 4 LTS Bionic Beaver 位置 轻量应用云服务器 2 安装软件 Sudo apt get update Sudo apt get install pptpd Sudo apt ge
  • 【ESP8266】关于调试fatal exception/自动重启的一些经验分享

    本人小白一枚 最近在捣鼓ESP8266的NONOS SDK开发 本来已经写好了一个工程测试基本功能也没什么问题了 但是发现了一个很严重的问题 就是每次一跑上40来分钟的时候 就会宕机重启 自动重启 真是奇了个怪了 本来这也没啥 但出于对稳定
  • 关闭 Ubuntu 中的关机/重启确认的小技巧

    导读 对于 Ubuntu 新手来说 有很多新东西要学 但是网上很多教程不是针对新手的 在这里 我们不走寻常路 不能说全部的教程都是为初学者准备 但至少大部分是 关闭 Ubuntu 中的关机 重启确认 这篇文章也是一篇新手教程 并且展示如何在
  • Android常用控件之悬浮窗

    悬浮窗可以显示在所有应用程序之上 不管在PC机还是Android设备上都有这个 最常见的是360的 加速球 来看下在Android设备上的效果 程序的目录结构如下图 创建Activity后启动Service就关闭 java view pla
  • 基于cordova打包RPGMAKERMV 安卓app

    基于cordova打包RPGMAKERMV 安卓app 1 RPGMakerMV部分 部署出网页项目 2 node部分 https nodejs org en 上下载node左边稳定版 右边是包含最新特性的版本 这是目前的版本可能不一样 设
  • 刷题之图像渲染

    有一幅以二维整数数组表示的图画 每一个整数表示该图画的像素值大小 数值在 0 到 65535 之间 给你一个坐标 sr sc 表示图像渲染开始的像素值 行 列 和一个新的颜色值 newColor 让你重新上色这幅图像 为了完成上色工作 从初
  • Verilog基本语法之循环语句(六)

    循环语句分为以下4种 for语句 通过三个步骤来决定语句的循环执行 1 给控制循环次数的变量赋初值 2 判定循环执行条件 若为假则跳出循环 若为真 则执行指定语句后 转到第三步 3 修改循环变量的值 返回第二步 repeat 连续执行一条语
  • qt 调节win声音版本大小

    QT4 情况下 运行的 会出错 目前暂时没有办法解决在 win下调节音量大小问题 在这里插入代码片 参考资料 QT 对window系统下音量的设置和获取 还有个很好贴子 没有找到
  • vim入门了

    自从上次搞定代码折叠之后 仿佛vim真的入门了 今天又看了一些内容 会复制 粘贴 查找了 更加的感觉入门了 值得庆贺 2012 5 3
  • JZOJ 幽幽子与森林

    题目大意 迷途竹林可以看成是一个n个点的森林 幽幽子定义dis u v 为u到v路径上的边的数量 若u和v不连通则为m 她定义整个森林的危险度为 为了去拜访永琳师匠 幽幽子需要提前知道迷途竹林的危险度 但迷途竹林的形态是时刻变化着的 所以幽
  • 栈系列之 最小栈的实现

    算法专题导航页面 算法专题 栈 栈系列之 栈排序 栈系列之 最小栈的实现 栈系列之 用栈实现队列 栈系列之 递归实现一个栈的逆序 题目 设计一个栈 其拥有常规的入栈 出栈操作外 需要额外具备获取最小元素的功能 其他限制 获取最小元素功能的时
  • 25. TCP协议之TCP中MSS与MTU

    MSS MSS英文全称为Maximum Segment Size 表示最大TCP报文段数据长度 并且MSS只会出现在对端发送SYN段时才会夹带的信息 在三次握手的过程中可以看到这个对端期望能够收到最大的数据段长度 如下 可以看到现在对端的M
  • 阿里云服务器部署node服务(一)

    万事开头难 尝试通过阿里云服务器部署node服务 中间踩了一些坑 借此给自己一个总结 1 远程服务器安装node 1 安装node wget https npmmirror com mirrors node v16 16 0 node v1
  • kafka介绍,安装以及简单的java调用kafka代码

    Producer 消息生产者 向broker发消息的客户端 Consumer 消息消费者 向broker取消息的客户端 Topic 一个队列 主题 Message 消息是kafka处理的对象 在kafka中 消息是被发布到broker的to
  • 【R】【纽约人口数量分析】

    文章目录 1 实验说明 2 实验环境 3 实验目的 4 实验内容 5 实验步骤 下载并导入数据 对生成的时间序列对象可视化 a 思考 b 由上述三种变量查看各个波动趋势数据 c 由上述结果可知 使用 plot 函数 修正数据 6 实验分析
  • Cadence学习六:ORCAD里怎么增加和删除Offpage connector

    ORCAD里怎么增加和删除Offpage connector 注 本文是个人再学习cadence17 4的时候遇到的问题小结 任何人不得商用 如有侵权 请联系本人删除 问题概述 对于ORCAD有多个页面的原理图 off page担当着在不同
  • error: C2248: “QObject::QObject”: 无法访问 private 成员(在“QObject”类中声明)

    QT中使用的C 对象经常会用到数据类 而存放数据可以选择使用QList lt gt QMap lt gt 等模板类存放指针或是对象 如果是选择存数据对象 考虑好之后的数据最好是静态访问的 很少去修改的 在存放的时候就会报上面的错误 原因是没
  • 各种通信接口的简单对比

    对比表 同步方式与异步方式的主要区别在于 是否传输时钟信号 只要是通訊前雙方需要設定相同波特率的 都是異步傳輸方式 异步传输 Asynchronous Transmission 每次异步传输的信息都以一个起始位开头 它通知接收方数据已经到达
  • 微服务之间调用的异常应该如何处理

    前言 在分布式服务的场景下 业务服务都将进行拆分 不同服务之间都会相互调用 如何做好异常处理是比较关键的 可以让业务人员在页面使用系统报错后 很清楚的看到服务报错的原因 而不是返回代码级别的异常报错 比如NullException Ille