在散布式体系开辟中,我们常常须要将林林总总的状况码、毛病信息通报给最外层的挪用方,这个挪用方平常是http/api接口,毛病信息比方登录失效、参数毛病等等。
最外层接口暴露的数据平常是相似于{code, msg, data}如许的json花样,这一点没有任何争议。
然则散布式体系的节点之间RPC挪用、节点内部要领挪用中,平常会用ServiceException或Result<T>的体式格局举行毛病信息的通报,这两种体式格局有什么区别以及孰优孰劣呢?本文侧重于开辟效力与体系机能议论这个题目。
Result<T>引见
这是一种比较罕见的毛病信息通报体式格局,某些大厂以至直接将它们设为技术规范,强迫各个团队采纳这类体式格局。罕见的Result模板以下:
@Data public class Result<T> { private int code; // 也能够是String等 private String msg; private T data; }
在体系开辟中的运用平常是如许的:
Result<UserModel> userModelResult = userService.query(userId); if (!userModelResult.isSuccess() || userModelResult.getData != null) { return Result.fail(userModelResult); // 透传毛病 } UserModel userModel = userModelResult.getData(); if (userModel.getStatus() != UserStatusEnum.NORMAL) { return Result.fail("user unavaliable"); // 用户不可用 } // ... 平常运用UserModel
在比较庞杂的散布式微效劳环境中,相似的代码非常之多,每一个依靠效劳的挪用都伴随着一段相似的容错逻辑。
这类形式比较相似Golang言语中的毛病码处置惩罚,这也是Golang比较被人诟病的处所,即每一步都得举行毛病判断。
更严酷的现实是,只管有了Result封装,然则依然会有后端体系的Exception透传过来。在我打仗过的现实运用中,这类打破Result封装的非常透传绝非个例,我本身担任的体系在挪用更后端的国内最强交易体系时,就曾接到过最内部交易中心TC的营业非常,排查题目时追踪的团队就有不止5个。
ServiceException引见
望文生义,这个体式格局就是运用非常中断将平常逻辑与非常逻辑举行拆分。
在体系开辟中,大部分毛病都须要直接中断效劳,直接将毛病反馈给用户,正由于云云,我们在运用Result<T>时,常常须要写相似if(result.isFail()){return…}如许的代码。而运用ServiceException,我们就能够省略掉绝大部分相似的代码。
平常ServiceException能够如许定义:
@Getter public class ServiceException extends RuntimeException { private final int code; private final String msg; public ApiException() { this(-1, null); } public ApiException(Code code) { this(code, null); } public ApiException(Code code, String msg) { super(msg); this.code = code; this.msg = msg; } }
体系内部组件在碰到数据缺失、越权接见、登录失效、账户锁定等非常情况时,直接抛出ServiceException中断逻辑,然后由最外层的Filter或Aspect捕捉非常,提取个中的code和msg返回给用户。
现实运用的代码逻辑相似如许:
UserModel userModel = userService.query(userId); // userID不存在、不可用等隐藏在非常中 // ... 运用userModel
这类体式格局显著文雅、精简了很多,关于开辟效力的进步以及后期保护都有协助。
然则在坊间有很多蜚语宣称,运用非常中断会影响机能,以至有人经由过程简朴的机能测试推出非常中断的机能耗时比返回Result快几百倍云云。
机能测试
针对机能题目,我也举行了一个简朴的测试,细致测试代码拜见:
https://github.com/sisyphsu/b...
这里运用JMH举行机能测试,说到benchmark,真的是艳羡golang言语自带的test库,实在是太轻易了。
测试内部的营业逻辑非常简朴,只是挪用一次System.currentTimeMillis()并返回long时候戳。
机能测试中分别运用Result<T>返回值以及抛出Exception,针对抛出非常的机能测试,又增添的差别深度的挪用栈测试,这是由于Java在抛出非常时,须要剖析当前Thread的栈,而挪用栈越深,所形成的机能消耗就越大。细致栈深度取值为1、10、100:
Test.test avgt 5 0.027 ± 0.001 us/op Test.testException avgt 5 1.060 ± 0.045 us/op Test.testDeep10Exception avgt 5 1.826 ± 0.122 us/op Test.testDeep100Exception avgt 5 9.802 ± 0.411 us/op
乍一看,非常栈深度为100的机能消耗确实是平常要领挪用的360倍,有的人也确实是基于这类来由得出Java非常中断机能消耗严峻的结论。
剖析机能的影响
然则须要注重时候单元,只是微秒罢了,毫秒的千分之一、秒的百万分之一。
假定某个微效劳单CPU吞吐量为1000QPS,而个中有10%是不法要求,那末非常中断的机能消耗也只是万分之一罢了,关于效劳耗时的影响也只是0.001毫秒罢了。
在机能测试中,营业耗时只是猎取体系时候,也许耗时为25ns。正由于云云才显得非常中断的机能消耗到达恐惧的“几百倍”,然则假如营业耗时从25ns变成25us、25ms呢?
再谈机能瓶颈
我们在剖析体系机能时,肯定要搞清楚它的数量级以及机能瓶颈,牢记堕入机能优化的逆境。
举个粗拙例子,在通例效劳中,利用了索引的DB操纵在1~10毫秒之间,接见散布式Cache的耗时在3~30毫秒之间,微效劳RPC的收集机能消耗在3~10毫秒之间,客户端与效劳器之间的收集耗时在5~300毫秒之间,云云之类等等。在这类情况下,优化0.001毫秒的机能隐患无异于捡了芝麻丢了西瓜。
我曾写过相似TCP的底层收集协定,在那种高频场景中,算法优化带来0.1微秒的机能优化就意味着每秒钟吞吐量几成以至几倍的提拔,然则在散布式挪用的低频场景中,这类机能用途没有任何用途。
别的一个例子,几年前我和同事在议论DB数据表设想时,由于定单状况运用什么长度的int而争论的面红脖子粗,如今想一想,定单状况上优化的1个字节,天长日久下来也只是节约不到1MB的磁盘空间罢了,有什么用呢?
RPC中的非常中断
关于运用Dubbo、HSF这类长途挪用框架而言,运用非常中断举行毛病信息通报,须要注重一点就是,非常范例须要设想为通用的,即各个微效劳都援用的基本范例。
在某厂的技术规范中有说到:
1) 运用抛非常返回体式格局,挪用方假如没有捕捉到就会发生运行时毛病。
2) 假如不加栈信息,只是new自定义非常,到场本身的明白的error message,关于挪用端解决题目的协助不会太多。假如加了栈信息,在频仍挪用失足的情况下,数据序列化和传输的机能消耗也是题目。
我对这类技术规范相称的不以为然。
起首营业非常原本就须要挪用方透传给最外层,诸如数据不存在、登录失效、用户锁定这类非常,中心的挪用方捕捉了也每每没有什么用。
其次又是鬼扯机能消耗,这类低频的数据序列化和内网传输会有什么样的机能消耗呢?栈信息透传给挪用方也有益于毛病排查,我曾接到过TC的非常栈信息,依据栈中的package直接就绕过三四层找到了底层失足的处所,能够说是节约了大批的时候。
结论
在散布式微效劳中,采纳非常中断能够大幅精简营业代码,关于机能的影响也微不足道。
辅佐以@NotNull、@Nullable等注解,能够让散布式开辟如风平常的疾速便利。在庞杂的效劳收集中,营业非常也能够轻易开辟人员精确地定位毛病,防止人人顺着挪用链一层一层地追踪毛病点的为难情形。
以上就是Exception与Result的引见(代码示例)的细致内容,更多请关注ki4网别的相干文章!