微服务架构下的异常处理
来源:互联网 发布:自己的淘宝账号查询 编辑:程序博客网 时间:2024/05/29 02:36
微服务架构下的统一异常处理
微服务架构实际上是一种将具有不同功能、不同部署位置的服务协调起来共同工作的分布式系统。由于分布式系统的复杂性,对这些服务之间的协调产生的异常处理就显得尤为重要了,好的异常处理不仅便于在异常发生的时候定位跟踪问题,还有利于保证系统的稳定性并可提高系统交互体验的友好性。所以,微服务开发过程中考虑异常情况的处理与考虑正常逻辑同等重要。
本文简单串联Java异常的基础知识和Spring提供的异常处理机制以及Spring Cloud服务间的异常处理机制,以实际项目实践为例说明在Spring Cloud微服务架构下自定义异常及统一处理异常的要点。
Java异常基础知识
Throwable 类是 Java 语言中所有错误或异常的超类,其子类分为Error、Exception。
Error是合理的应用程序不应该试图捕获的严重问题
Exception是合理的应用程序想要捕获的问题,Exception又分为CheckedException和UnCheckedException。
[ ] CheckedException是对于可恢复的条件使用被检查的异常,需要用try…catch…显式的捕获并且进行处理,例如IOException。
[ ] UncheckedException又叫做RuntimeException不是必须捕获的,对于导致程序无法执行的异常,必要时转化成有具体业务描述说明的异常。
异常处理的注意事项:
- 一般来说异常处理首先要防止异常丢失,避免使用覆盖式异常处理块,可以考虑使用异常链的特性处理,即使用从Throwable继承的initCause()。
- 在截获异常进行处理时要注意对异常进行一番处理,如:修正错误,给出明确位置,描述,原因以及提醒。
- 使用Try…finally结构保证发生异常时,资源也能正确释放、关闭。
- 截获异常后进行相应处理,根据业务逻辑需要,必要时可以重新抛出异常。
- 最好的处理方法是对异常信息分类,然后进行封装处理。
- 尤其需要注意的是避免把异常截获后不做任何处理,直接吞了,项目上不少初级程序员犯这类错误可能导致程序出错,不好查找原因。
Spring异常处理的三种方式
- 使用
@ExceptionHandlerannotation 实现在配置的Controller中拦截并处理异常
@Controllerpublic class UserController { private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class); @RequestMapping(value="/user/{id}", method=RequestMethod.GET) public String getUser(@PathVariable("id") int id, Model model) throws Exception{ if(id==1){ throw new UserNotFoundException(id); } } @ExceptionHandler(UserNotFoundException.class) public ModelAndView handleUserNotFoundException(HttpServletRequest request, Exception ex){ logger.error("Requested URL="+request.getRequestURL()); logger.error("Exception Raised="+ex); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("exception", ex); modelAndView.addObject("url", request.getRequestURL()); modelAndView.setViewName("error"); return modelAndView; } /** 输入参数仅有exception @ExceptionHandler public String handleException(UserNotFoundException exception) { return "errorView"; } */}
实现HandlerExceptionResolver接口,进行全局的异常拦截,使用时需要在MVCConfiguration中初始化异常处理器
public class ServiceHandlerExceptionResolver implements HandlerExceptionResolver { Logger logger = LoggerFactory.getLogger(ServiceHandlerExceptionResolver.class); public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler, Exception e) { e.printStackTrace(); Throwable rex = e; if (rex instanceof CIBaseException) { this.writeError(((CIBaseException) rex).getErrCode(), rex.getMessage(), httpServletResponse); } else { this.writeError("", "系统运行出错", httpServletResponse); } return new ModelAndView(); }}@Configurationpublic class MvcConfiguration extends WebMvcConfigurerAdapter { //需要在MVCConfiguration中初始化异常处理器 @Override public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) { exceptionResolvers.add(new ServiceHandlerExceptionResolver()); }}
使用@ControllerAdviceannotation类里定义@ExceptionHandler, 对项目中所有Controller中带有@ExceptionHandler注释的方法拦截并进行处理异常
@ControllerAdvicepublic class GlobalExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); @ExceptionHandler(SQLException.class) public String handleSQLException(HttpServletRequest request, Exception ex){ logger.info("SQLException Occured:: URL="+request.getRequestURL()); return "database_error"; } @ResponseStatus(value=HttpStatus.NOT_FOUND, reason="IOException occured") @ExceptionHandler(IOException.class) public void handleIOException(){ logger.error("IOException handler executed"); //returning 404 error code } /** 输入参数仅有exception @ExceptionHandler public String handleException(UserNotFoundException exception) { return "errorView"; } */}
Spring Cloud架构下统一处理异常的实例
本小节以实际项目为例,说明在Spring Cloud项目架构下自定义异常并统一处理异常的机制。本项目的服务结构如图所示:
- 所有的服务都采用Eureka Server注册
- 所有服务配置都采用Config Server实现服务注册
- 浅蓝色框中的服务为对外部系统提供服务的Open Service层,统一处理身份认证及安全等问题
- 橙色框中的服务为内部服务,都是Open Service层的调用向外暴露服务
- 系统中的服务大都采用声明式样REST客户端Feign作为服务客户端接口
首先,项目统一定义了基础异常类,并根据异常类型及业务分类定义了错误代码编号。所有的Internal Service层的服务对于可预见的异常都包装成系统基础异常,其他异常可以原样抛出。具体的异常类定义如下:
public class CIBaseException extends RuntimeException { private static final long serialVersionUID = *****; public static final int UNKNOWN_EXCEPTION = 0; public static final int BIZ_EXCEPTION = 1; public static final int TIMEOUT_EXCEPTION = 2; public static final int FORBIDDEN_EXCEPTION = 3; public static final int CISYS_EXCEPTION = 4; private int typecode = BIZ_EXCEPTION;// CIBaseException,异常类型用tpecode表示 private String errCode; public CIBaseException() { super(); } public CIBaseException(String message, Throwable cause) { super(message, cause); } public CIBaseException(String message) { super(message); } public CIBaseException(Throwable cause) { super(cause); } public CIBaseException(int type, String errCode) { super(); this.typecode = type; this.errCode = errCode; } public CIBaseException(int type, String errCode, String message, Throwable cause) { super(message, cause); this.typecode = type; this.errCode = errCode; } public CIBaseException(int type, String errCode, String message) { super(message); this.typecode = type; this.errCode = errCode; } public CIBaseException(int type, String errCode, Throwable cause) { super(cause); this.typecode = type; this.errCode = errCode; } ... }
系统在Open Service层的服务中定义了一个CustomerErrorDecoder实现了ErrorDecoder接口处理调用远程服务产生的异常(若在应用中配置了ErrorDecoder的Bean,则Spring Cloud Netflix会在创建服务客户端时在应用上下文里查找该bean以处理服务调用异常获取到的异常)。具体来说ErrorDecoder Bean主要是在Decode方法中从response中获取到异常信息,将所有远程服务产生的业务异常都还原成CIBaseException的业务异常,并把其他类型的异常也包装成提示信息友好异常。
public Exception decode(String s, Response response) { CIBaseException ex = null; try { if(response.body() != null) { String body = Util.toString(response.body().asReader()); logger.error(body); ExceptionInfo ei = this.objectMapper.readValue(body.getBytes("UTF-8"), ExceptionInfo.class); String message = ei.getMessage(); String code = ""; if (message.matches(this.errCodePattern)) { String [] cm = this.parseCodeMessage(message); code = cm[0]; message = cm[1]; ex = new CIBaseException(CIBaseException.BIZ_EXCEPTION, code, message); } } } catch (IOException var4) { var4.printStackTrace(); ex = new CIBaseException(CIBaseException.UNKNOWN_EXCEPTION, "", "系统运行异常"); } return null != ex ? ex : new CIBaseException(CIBaseException.UNKNOWN_EXCEPTION, "", "系统运行异常"); }
在Open Service层的服务中定义统一的Controller层错误处理类,解析异常对象,转化成友好的异常提示写入Response返回给前端页面进行展示,resoveException方法如下:
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) { e.printStackTrace(); Throwable rex = e; if (e instanceof HystrixRuntimeException) { rex = e.getCause(); } if (rex instanceof CIBaseException) { CIBaseException cbe = (CIBaseException) rex; if (StringUtils.isBlank(cbe.getErrCode()) && cbe.getMessage().matches(this.errCodePattern)) { String [] cm = this.parseCodeMessage(cbe.getMessage()); this.writeError(cm[0], cm[1], httpServletResponse); } else { this.writeError(cbe.getErrCode(), rex.getMessage(), httpServletResponse); } } else { this.writeError("", "系统运行出错", httpServletResponse); } return new ModelAndView(); }
开发过程中使用自定义异常的注意事项
系统统一异常处理框架会捕获异常,对于需要处理的异常。 程序开发过程中根据逻辑可以创建异常,必要时转换成可描述清楚异常发生时的业务场景的异常抛出。 需要注意的是不要打印异常,统一异常处理框架会打印异常,避免重复打印。 自定义异常类的使用需要注意以下三点:
- 包装转换时时注意传入原生异常,避免丢失异常信息,异常消息可以带业务描述,示例如下:
private int countHighScore(String score){ int counter = 0; try{ float fscore = Float.valueOf(score); .... }catch(NumberFormatException nex){ //注意:此处传入原生异常 throw new CIBaseException(ExceptionConstants.DEMO_SCORE_FORMAT_INVALID,nex);//输入参数格式错误! } return counter; }
未传入原生异常,会丢失具体异常信息,如下所示:
com.isoftstone.cityinsight.exception.CIBaseException: 0X001001=输入参数格式错误! at com.isoftstone.ci.user.web.controller.DemoUserController.countHighScore(DemoUserController.java:73) at com.isoftstone.ci.user.web.controller.DemoUserController.userTest(DemoUserController.java:58) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:483) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
包含原生异常的完整信息,如下所示:
java.lang.NumberFormatException: For input string: "ww" at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043) at sun.misc.FloatingDecimal.parseFloat(FloatingDecimal.java:122) at java.lang.Float.parseFloat(Float.java:451) at java.lang.Float.valueOf(Float.java:416) at com.isoftstone.ci.user.web.controller.DemoUserController.countHighScore(DemoUserController.java:71) at com.isoftstone.ci.user.web.controller.DemoUserController.userTest(DemoUserController.java:58) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:483) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
- 创建的业务异常,必要的时候增加详细的运行时获取的具体信息,便于异常获取端分析问题,示例如下:
/*找到某人的基本信息,并且根据基本信息计算分析其专业能力分值*/public Score countScoreByUserId(String userid){ CIUser user = userservice.getById(userid); if (user == null) { //注意:此处加入了运行时数据信息 throw new CIBaseException(ExceptionConstants.DEMO_USER_NOT_FOUND+", userid:"+userid);//该用户不存在 } Score score = scoreservice.countScore(user); return score;}
- 捕获通用异常并进行异常处理,并转换成有明确描述业务异常抛出去,例如IOException、SQLException
public String readfile(String filePath) { FileInputStream fileInputStream = null; StringBuffer sb=new StringBuffer(); String tempstr=null; try{ File file = new File(filePath); fileInputStream = new FileInputStream(file); BufferedReader br=new BufferedReader(new InputStreamReader(fis)); while((tempstr=br.readLine())!=null) sb.append(tempstr); }catch(FileNotFoundException fex){ throw new CIBaseException("ExceptionConstants.DEMO_LICENCEFILE_NOT_FOUND"),e);//文件不存在 }catch(IOException e){ throw new CIBaseException("ExceptionConstants.DEMO_LICENCEFILE_IOEXCEPTION),e);//IO异常 }finally{ try{ //关闭资源() if(fileInputStream!=null){ fileInputStream.close(); } }catch(IOException ioex){ throw new CIBaseException("0X01C001=".concat("读Licens文件失败(关闭资源失败..)!"),e); } } return sb.toString(); }
- 微服务架构下的异常处理
- 微服务,微架构[七]之springboot异常处理
- 微服务架构下dubbo的缺点
- 微服务架构下处理分布式事务,你必须知道的事儿
- 微服务架构下处理分布式事务,你必须知道的事儿
- 微服务架构下的移动架构实践
- 360搜索在微服务架构下的技术平台实践(二) -- 微服务架构
- 微服务架构下的数据一致性:概念及相关模式
- 微服务架构下的数据一致性:可靠事件模式
- 微服务架构下的数据一致性:概念及相关模式
- 微服务架构下的数据一致性:可靠事件模式
- 微服务架构 (九): 分布式微服务下的数据一致性
- 微服务架构下的数据一致性保证(一)
- 微服务架构下的数据一致性保证(二)
- API gateway---orange下的微服务架构设计
- 微服务架构下的数据一致性保证(一)
- 微服务架构下的数据一致性保证(二)
- 微服务架构下的分布式Session管理
- Linux学习基础篇(五)
- Debian 6.0.4安装图解教程
- css---前端如何用css代码写省略号,单行省略号,多行省略号?
- 汉诺塔
- git找回本地误删的文件
- 微服务架构下的异常处理
- c语言基础(七)联合体和枚举
- Springboot 热部署的两种方式
- java中的异常
- 元素从失去焦点到其他元素被点击期间的事件
- Laravel学习过程之Laravel安装及配置(一)
- linux系统下单网卡绑定多个IP地址
- C++解决数据精度问题,对浮点数保存指定位小数
- 设计模式-模版设计模式概述和使用-抽象类