当前位置: 首页 > 后端技术 > PHP

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

时间:2023-03-30 06:17:16 PHP

正确使用异常是微服务架构重要性中的前三。我没意见。节日快乐。最近想说说异常。我的思维似乎形成了一个闭环。希望这个组合拳能对你的业务代码有所帮助。下面只讨论世界上最好的语言,生态最完整的语言,不发表意见。异常的异同PHP7的异常设计与Java的ExceptionextendsThrowable是一致的,但是在历史原因和设计理念上还是有一些细微的差别。比如PHP中的异常有一个code属性,这样就有多个异常聚集成同一个异常,然后根据code在catch块中写入不同的业务逻辑代码。但是Java异常是没有代码的,所以不能这样设计,不同的异常只能针对不同的情况。因此,我们习惯于通过封装类来封装对外暴露的服务,而不是直接依赖异常的透传。统一的异常处理在Java代码中,最受诟病的莫过于漫山遍野的trycatch。我没意见。只需获取一段代码@OverridepublicDataResult>getAds(IntegerliveId){try{ListadsDTO=newArrayList<>();//...省略业务逻辑DataResult.success(adsDTO);}catch(Exceptione){log.error("getAdshasException:{}",e.getMessage(),e);DataResult.failure(ResultCode.CODE_INTERNAL_ERROR,e.getMessage());//返回异常信息给Server-sidecaller}returndataResult;}很多时候无脑先写一个trycatch,不管里面有没有非运行时异常。更好的方法是使用aop拦截所有服务方法调用,统一接管异常再处理。@Around("recordLog()")publicObjectrecord(ProceedingJoinPointjoinPoint)throwsThrowable{//...请求调用源记录对象结果;尝试{结果=joinPoint.proceed(joinPoint.getArgs());}catch(Exceptione){//...记录异常日志DataResultres=DataResult.failure(ResultCode.CODE_INTERNAL_ERROR,e.getMessage());结果=资源;}//...returnvaluelogrecordreturnresult;}有个小问题。如果将A服务的异常信息直接返回给调用者B,可能会存在一些潜在的风险。打电话的人永远不能相信,即使他是贫农三代。因为不确定调用者会如何处理错误信息,所以可能会直接以json的形式返回给前端。Java中的RuntimeException可以分为运行时异常和非运行时异常。不需要捕获运行时异常,也不需要在方法上标记throwException。比如我们在方法中使用了guava包中的Preconditions工具类。抛出的IllegalArgumentException也是一个运行时异常。@OverridepublicDataResult>getAds(IntegerliveId){Preconditions.checkArgument(null!=liveId,"liveIdsnotbenull");列表adsDTOS=newArrayList<>();//...业务逻辑省略returnDataResult.success(adsDTOS);}我们也可以利用这个特性自定义自己的业务异常类继承RuntimeExceptionXXServiceRuntimeExceptionextendsRuntimeException如果不符合业务逻辑,直接抛出XXServiceRuntimeException@OverridepublicDataResult>getAds(IntegerliveId){if(null==liveId){thrownewXXServiceRuntimeException("liveId不能为null");}ListadsDTOS=newArrayList<>();//...业务逻辑省略returnDataResult.success(adsDTOS);}然后在aop中统一处理,进行相应的优化。对于之前粗略的做法,除了XXServiceRuntimeException和IllegalArgumentException之外的异常都应该在内部记录下来,不再暴露给外界,但是一定要记得通过requestId连接分布式链接,在DataResult中返回,方便排错。@Around("recordLog()")publicObjectrecord(ProceedingJoinPointjoinPoint)throwsThrowable{//...请求调用源记录对象结果;尝试{结果=joinPoint.proceed(joinPoint.getArgs());}catch(Exceptione){//...记录异常日志①log.error("{}#{},exception:{}:",clazzSimpleName,methodName,e.getClass().getSimpleName(),e);DataResultres=DataResult.failure(ResultCode.CODE_INTERNAL_ERROR);if(einstanceofXXServiceRuntimeException||einstanceofIllegalArgumentException){res.setMessage(e.getMessage());}结果=res;}if(resultinstanceofDataResult){((DataResult)result).setRequestId(EagleEye.getTraceId());//DMC}//...返回值日志记录返回结果;}异常监控表示闭环,使用自定义异常类后,异常日志监控告警的门槛可以降低很多,告警更多准确的。以阿里云SLS的监控为例*ERRORnotXXServiceRuntimeExceptionnotIllegalArgumentException|SELECTCOUNT(*)AScount这里的监控是在日志中记录异常日志①PHP上面提到的问题在Java中同样存在于PHP中。不能体现PHP是世界上//1上的最佳语言。call_user_func_array//2。反思//3。直接newtry{$class=new$className();$result=$class->$methodName();}catch(\Throwable$e){//...缩写}类似上面的架构逻辑,不用重复写伪代码,基本保持一致,自定义自己的业务异常类继承RuntimeException,然后对外输出处理。然而,PHP有一些历史包袱。最初设计时,很多运行时异常都是作为Notice和Warning错误输出的,但是错误输出缺少调用栈,不利于排错。functionfoo(){returnboo("xxx");}functionboo($a){returnexplode($a);}foo();警告:explode()需要至少2个参数,其中1个在/Users/中给出mengkang/Downloads/ab.php第8行看不到具体的参数,也看不到调用栈。如果用set_error_handler+ErrorException,就很清楚了。set_error_handler(function($severity,$message,$file,$line){thrownewErrorException($message,10001,$severity,$file,$line);});functionfoo(){returnboo("xxx");}functionboo($a){returnexplode($a);}try{foo();}catch(Exception$e){echo$e->getTraceAsString();}最后打印的信息是Fatalerror:UncaughtErrorException:explode()expectsatleast2parameters,1givenin/Users/mengkang/Downloads/ab.php:12Stacktrace:#0[internalfunction]:{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}throwin/Users/mengkang/Downloads/ab.phponline12修改上面的函数functionboo(array$a){returnimplode(",",$a);},不能捕获,因为抛出的是PHPFatalerror:UncaughtTypeError,PHP7已经添加了类Error实现Throwable,在PHP系统错误日志中会有一个Stack,但是不能串联到整个业务系统。这里就不得不说说日志的设计了。我们期望像Java一样传递一个traceId把所有的日志串联起来,从Nginx的日志到PHP中普通的info级别的日志还有这些UncaughtTypeError,所以接管默认输出到系统错误日志,统一记录在catch代码块的地方,然后只需将其修改为set_error_handler(function($severity,$message,$file,$line){thrownewErrorException($message,10001,$severity,$file,$line);});functionfoo(){返回boo("xxx");}functionboo(array$a){returnimplode(",",$a);}try{foo();}catch(Throwable$e){echo$e->getTraceAsString();}catchThrowable可以接受错误和异常。但是set_error_handler不能处理一些错误,比如E_PARSE错误,可以使用register_shutdown_function来覆盖。值得注意的是,register_shutdown_function的作用是在脚本正常退出或调用exit时执行注册的函数。它只能在脚本运行(运行时而不是解析时)并因错误退出时使用。如果调用register_shutdown_function的同一个文件有语法错误,是注册不上去的,但是我们的项目一般是分多个文件,这样其他文件也有语法错误,register_shutdown_function(function(){$e=error_get_last();如果($e){thrownew\ErrorException($e["message"],10002,E_ERROR,$e["file"],$e["line"]);}});如果想把这些代码(PHP)直接用到项目中,可能会有很多坑,因为我们习惯了系统中有很多通知,我们可以把通知错误变成异常,主动记录,但不要向外界抛出异常即可。今天先到这里,下次再说说如何记录日志。虽然PHP环境的开发看似不明朗,但是因为热爱,所以还是继续吧。在某些场景下,它仍然是最好的选择。