微服务架构下的异常处理

来源:互联网 发布:自己的淘宝账号查询 编辑:程序博客网 时间: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异常处理的三种方式

  1. 使用@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();    }
阅读全文
1 0
原创粉丝点击