在微服务架构中异常如何正确使用

本篇文章给大家介绍一下在微服务架构中异常正确使用的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。

在微服务架构中异常如何正确使用

异常的正确使用在微服务架构中的重要性排前三,没什么意见吧

异常的正确使用在微服务架构中的重要性排前三,没什么意见吧

Curdboy 们好久不见,先祝大家端午节快乐。最近想说说异常,我的思考俨然形成了闭环,希望这套组合拳能对你的业务代码有所帮助。

下面只讨论世界上最好的语言和生态最完整的语言,没什么意见吧。

异常的异同

PHP 在 PHP7 异常的设计和 Java 保持一致了 Exception extends Throwable  ,不过在历史原因和设计理念上还是有一些细微的差别。比如 PHP 中的异常是有 code 属性的,这样就存在多种异常聚类为同一个异常,然后在catch 区块里根据 code 写不同的业务逻辑代码。

而 Java 异常则没有code ,不能这样设计,只能针对不同的情况使用不同的异常。所以我们习惯服务对外暴露的通过包装类来封装,而不是直接依赖异常的透传。

统一异常的处理

在 Java 代码里,最让人诟病的就是漫山遍野的try catch  ,没什么意见吧。随便抓一段代码

@Overridepublic DataResult> getAds(Integer liveId) {        try {        List adsDTO = new ArrayList();        //...业务逻辑省略        DataResult.success(adsDTO);    } catch (Exception e) {        log.error("getAds has Exception:{}", e.getMessage(), e);        DataResult.failure(ResultCode.CODE_INTERNAL_ERROR, e.getMessage()); // 将异常信息返回给服务端调用方    }        return dataResult;}

登录后复制

很多时候都是无脑上来就先写个 try catch 再说,不管里面是否会有非运行时异常。比较好的方式是使用 aop 的方式来拦截所有的服务方法的调用,统一接管异常然后做处理。

@Around("recordLog()")public Object record(ProceedingJoinPoint joinPoint) throws Throwable {  //... 请求调用来源记录    Object result;  try {    result = joinPoint.proceed(joinPoint.getArgs());  } catch (Exception e) {    //... 记录异常日志        DataResult res = DataResult.failure(ResultCode.CODE_INTERNAL_ERROR, e.getMessage());    result = res;  }    //... 返回值日志记录    return result;}

登录后复制

有一点小问题,如果直接将 A 服务的异常信息直接返回给调用者 B,可能存在一些潜在的风险,永远不能相信调用者,即使他根正苗红三代贫农也不行。因为不能确定调用者会将该错误信息作何处理,可能就直接作为 json 返回给了前端。

RuntimeException

在 Java 中异常可以分为运行时异常和非运行时异常,运行时异常是不需要捕获的,在方法上也不需要标注 throw Exception,比如我们在方法里使用 guava 包里的Preconditions工具类,抛出的IllegalArgumentException也是运行时异常。

@Overridepublic DataResult> getAds(Integer liveId) {  Preconditions.checkArgument(null != liveId, "liveIds not be null");    List adsDTOS = new ArrayList();  //...业务逻辑省略  return DataResult.success(adsDTOS);}

登录后复制

我们也可以使用该特性,自定义自己的业务异常类继承RuntimeException

XXServiceRuntimeException extends RuntimeException

登录后复制

对于不符合业务逻辑情况则直接抛出 XXServiceRuntimeException

@Overridepublic DataResult> getAds(Integer liveId) {  if (null == liveId) {    throw new XXServiceRuntimeException("liveId can't be null");  }    List adsDTOS = new ArrayList();  //...业务逻辑省略  return DataResult.success(adsDTOS);}

登录后复制

然后在 aop 做统一处理做相应的优化,对于前面比较粗暴的做法,应该将除了XXServiceRuntimeException和IllegalArgumentException之外的异常内部记录,不再对外暴露,但是一定要记得通过requestId将分布式链路串起来,在DataResult中返回,方便问题的排查。

@Around("recordLog()")public Object record(ProceedingJoinPoint joinPoint) throws Throwable {  //... 请求调用来源记录    Object result;  try {    result = joinPoint.proceed(joinPoint.getArgs());  } catch (Exception e) {    //... 记录异常日志①    log.error("{}#{}, exception:{}:", clazzSimpleName, methodName, e.getClass().getSimpleName(), e);        DataResult res = DataResult.failure(ResultCode.CODE_INTERNAL_ERROR);    if (e instanceof XXServiceRuntimeException || e instanceof IllegalArgumentException) {       res.setMessage(e.getMessage());    }     result = res;  }  if (result instanceof DataResult) {      ((DataResult) result).setRequestId(EagleEye.getTraceId()); // DMC   }    //... 返回值日志记录    return result;}

登录后复制

异常监控

说好的闭环呢,使用了自定义异常类之后,对异常日志的监控报警的阈值就可以降低不少,报警更加精准,以阿里云 SLS 的监控为例

* and ERROR not XXServiceRuntimeException not IllegalArgumentException|SELECT COUNT(*) AS count

登录后复制

这里监控的是记录异常日志① 的日志

PHP 里的异常

上面 Java 里说到的问题在 PHP 里也同样存在,不用 3 种方法来模拟 aop 都不能体现 PHP 是世界上最好的语言

//1. call_user_func_array//2. 反射//3. 直接 newtry {  $class = new $className();  $result = $class->$methodName();} catch (Throwable $e) {    //...略}

登录后复制

类似上面的架构逻辑不再重复编写伪代码,基本保持一致。也是自定义自己的业务异常类继承RuntimeException,然后做对外输出处理。

但是PHP 里有一些历史包袱,起初设计的时候很多运行时异常都是作为 Notice,Warning 错误输出的,但是错误的输出缺少调用栈,不利于问题的排查

function foo(){  return boo("xxx");}function boo($a){  return explode($a);}foo();

登录后复制

Warning: explode() expects at least 2 parameters, 1 given in /Users/mengkang/Downloads/ab.php on line 8

登录后复制

看不到具体的参数,也看不到调用栈。如果使用set_error_handler + ErrorException之后,就非常清晰了。

set_error_handler(function ($severity, $message, $file, $line) {    throw new ErrorException($message, 10001, $severity, $file, $line);});function foo(){  return boo("xxx");}function boo($a){  return explode($a);}try{  foo();}catch(Exception $e){  echo $e->getTraceAsString();}

登录后复制

最后打印出来的信息就是

Fatal error: Uncaught ErrorException: explode() expects at least 2 parameters, 1 given in /Users/mengkang/Downloads/ab.php:12Stack trace:#0 [internal function]: {closure}(2, 'explode() expec...', '/Users/mengkang...', 12, Array)#1 /Users/mengkang/Downloads/ab.php(12): explode('xxx')#2 /Users/mengkang/Downloads/ab.php(8): boo('xxx')#3 /Users/mengkang/Downloads/ab.php(15): foo()#4 {main}  thrown in /Users/mengkang/Downloads/ab.php on line 12

登录后复制

修改上面的函数

function boo(array $a){  return implode(",", $a);}

登录后复制

则没法捕获了,因为抛出的是PHP Fatal error:  Uncaught TypeError,PHP7 新增了
class Error implements Throwable,则在 PHP 系统错误日志里会有 Stack,但是不能和整个业务系统串联起来,这里就又不得不说日志的设计,我们期望像 Java 那样通过一个 traceId 将所有的日志串联起来,从 Nginx 日志到 PHP 里的正常 info level 日志以及这些Uncaught TypeError,所以接管默认输出到系统错误日志,在 catch 代码块中记录到统一的地方。那么这里就简单修改为

set_error_handler(function ($severity, $message, $file, $line) {    throw new ErrorException($message, 10001, $severity, $file, $line);});function foo(){  return boo("xxx");}function boo(array $a){  return implode(",", $a);}try{  foo();}catch(Throwable $e){  echo $e->getTraceAsString();}

登录后复制

catch Throwable就能接受Error和Exception了。

但是 set_error_handler 没办法处理一些错误,比如E_PARSE的错误,可以用register_shutdown_function来兜底。

值得注意的是register_shutdown_function的用意是在脚本正常退出或显示调用exit时,执行注册的函数。是脚本运行(run-time not parse-time)出错退出时,才能使用。如果在调用register_shutdown_function的同一文件的里面有语法错误,是无法注册的,但是我们项目一般都是分多个文件的,这样就其他文件里有语法错误,也能捕获了

register_shutdown_function(function(){    $e = error_get_last();    if ($e){        throw new ErrorException($e["message"], 10002, E_ERROR, $e["file"], $e["line"]);    }});

登录后复制

如果你想直接使用这些代码(PHP的)直接到项目可能会有很多坑,因为我们习惯了系统中有很多  notice 了,可以将 notice 的错误转成异常之后主动记录,但是不对外抛出异常即可。

推荐学习:php视频教程

以上就是在微服务架构中异常如何正确使用的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2511438.html

(0)
上一篇 2025年3月5日 14:33:12
下一篇 2025年3月2日 08:23:48

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • PHP如何使用Sodium加密扩展函数

    本篇文章给大家介绍一下php使用sodium加密扩展函数的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。 PHP的Sodium加密扩展函数了解 这是本次加密扩展系列的最后一篇文章,也是我们要学习了解的最后一个 PHP…

    2025年3月5日
    200
  • php怎么实现整数除以整数

    在php中可以通过算术运算符“/”来实现整数除以整数,其除法运算符总是返回浮点数,只有当两个操作数都是整数并且正好能整除,这时它返回一个整数。 本文操作环境:Windows7系统、PHP7.1版,DELL G3电脑 php怎么实现整数除以整…

    2025年3月5日
    200
  • php怎么将html转换为xml

    在php中可以通过“DOMDocument()”将html转换为xml,代码语句如“$doc=new DOMDocument();$doc->loadHTML($html);”。 本文操作环境:Windows7系统、PHP7.1版,D…

    2025年3月5日
    200
  • php怎么实现汉语转拼音

    php实现汉语转拼音的方法:1、安装“overtrue/pinyin”拓展;2、通过“$pinyin = new Pinyin();$pinyin->permalink(‘测试汉字转拼音’);”等方式进行转换即…

    2025年3月5日
    200
  • php如何清除浏览器缓存

    php清除浏览器缓存的方法:首先打开相应的代码文件;然后添加代码“header(“Cache-Control: no-cache, must-revalidate” );”即可清除浏览器缓存。 本文操作环境:Wind…

    2025年3月5日
    200
  • php curl怎么使用post方法

    php curl使用post的方法:首先启动一个CURL会话;然后对认证证书来源进行检查;接着从证书中检查SSL加密算法是否存在;最后以POST方式请求https协议接口即可。 本文操作环境:Windows7系统、PHP7.1版,DELL …

    2025年3月5日
    200
  • 如何解决php curl ssl证书错误问题

    php curl ssl证书错误的解决办法:1、在代码中加入“curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);”;2、添加“cacert.pem”文件路径并重启web服务器即可。 本文操作环境…

    2025年3月5日
    200
  • php怎么实现点击加载更多

    php实现点击加载更多的方法:首先新建index.php并引入jQuery库;然后新建“connect_sql.php”;最后修改index.php里的js脚本即可。 本文操作环境:Windows7系统、PHP7.1版,DELL G3电脑 …

    2025年3月5日
    200
  • php删除数组中的空值

    php删除数组中的空值的方法:1、通过foreach或者while去除数组中的空值元素;2、通过array_filter函数去除数组中的空值元素。 本文操作环境:Windows7系统、PHP7.1版,DELL G3电脑 php怎么删除数组中…

    2025年3月5日
    200
  • php链接mongodb失败怎么办

    php链接mongodb失败的解决办法:首先建立一个root role的账号;然后建立一个userAdmin;接着建立数据库连接账号;最后使用MongoClient连接即可。 本文操作环境:Windows7系统、PHP7.1版,DELL G…

    2025年3月5日
    200

发表回复

登录后才能评论