摘要:分析性能的影響但是需要注意時(shí)間單位,只是微秒而已,毫秒的千分之一秒的百萬(wàn)分之一。在這種情況下,優(yōu)化毫秒的性能隱患無(wú)異于撿了芝麻丟了西瓜。
同步自:https://sulin.me/2019/T2ZXZB....
在分布式系統(tǒng)開發(fā)中,我們經(jīng)常需要將各種各樣的狀態(tài)碼、錯(cuò)誤信息傳遞給最外層的調(diào)用方,這個(gè)調(diào)用方通常是http/api接口,錯(cuò)誤信息比如登錄失效、參數(shù)錯(cuò)誤等等。
最外層接口暴露的數(shù)據(jù)通常是類似于{code, msg, data}這樣的json格式,這一點(diǎn)沒有任何爭(zhēng)議。
但是分布式系統(tǒng)的節(jié)點(diǎn)之間RPC調(diào)用、節(jié)點(diǎn)內(nèi)部方法調(diào)用中,通常會(huì)用ServiceException或Result
這是一種比較常見的錯(cuò)誤信息傳遞方式,某些大廠甚至直接將它們?cè)O(shè)為技術(shù)規(guī)范,強(qiáng)制各個(gè)團(tuán)隊(duì)采用這種方式。常見的Result模板如下:
@Data public class Result{ private int code; // 也可以是String等 private String msg; private T data; }
在系統(tǒng)開發(fā)中的應(yīng)用通常是這樣的:
ResultuserModelResult = userService.query(userId); if (!userModelResult.isSuccess() || userModelResult.getData != null) { return Result.fail(userModelResult); // 透?jìng)麇e(cuò)誤 } UserModel userModel = userModelResult.getData(); if (userModel.getStatus() != UserStatusEnum.NORMAL) { return Result.fail("user unavaliable"); // 用戶不可用 } // ... 正常使用UserModel
在比較復(fù)雜的分布式微服務(wù)環(huán)境中,類似的代碼非常之多,每個(gè)依賴服務(wù)的調(diào)用都伴隨著一段類似的容錯(cuò)邏輯。
這種模式比較類似Golang語(yǔ)言中的錯(cuò)誤碼處理,這也是Golang比較被人詬病的地方,即每一步都得進(jìn)行錯(cuò)誤判斷。
更殘酷的現(xiàn)實(shí)是,盡管有了Result封裝,但是仍然會(huì)有后端系統(tǒng)的Exception透?jìng)鬟^來(lái)。在我接觸過的實(shí)際應(yīng)用中,這種突破Result封裝的異常透?jìng)鹘^非個(gè)例,我自己負(fù)責(zé)的系統(tǒng)在調(diào)用更后端的國(guó)內(nèi)最強(qiáng)交易系統(tǒng)時(shí),就曾接到過最內(nèi)部交易中心TC的業(yè)務(wù)異常,排查問題時(shí)追蹤的團(tuán)隊(duì)就有不止5個(gè)。
ServiceException介紹顧名思義,這個(gè)方式就是使用異常中斷將正常邏輯與異常邏輯進(jìn)行拆分。
在系統(tǒng)開發(fā)中,大部分錯(cuò)誤都需要直接中斷服務(wù),直接將錯(cuò)誤反饋給用戶,正因?yàn)槿绱?,我們?cè)谑褂?b>Result
通常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; } }
系統(tǒng)內(nèi)部組件在遇到數(shù)據(jù)缺失、越權(quán)訪問、登錄失效、賬戶鎖定等異常情況時(shí),直接拋出ServiceException中斷邏輯,然后由最外層的Filter或Aspect捕捉異常,提取其中的code和msg返回給用戶。
實(shí)際使用的代碼邏輯類似這樣:
UserModel userModel = userService.query(userId); // userID不存在、不可用等隱藏在異常中 // ... 使用userModel
這種方式明顯優(yōu)雅、精簡(jiǎn)了許多,對(duì)于開發(fā)效率的提高以及后期維護(hù)都有幫助。
但是在坊間有許多流言聲稱,使用異常中斷會(huì)影響性能,甚至有人通過簡(jiǎn)單的性能測(cè)試推出異常中斷的性能耗時(shí)比返回Result快幾百倍云云。
性能測(cè)試針對(duì)性能問題,我也進(jìn)行了一個(gè)簡(jiǎn)單的測(cè)試,具體測(cè)試代碼參見:
https://github.com/sisyphsu/b...
這里使用JMH進(jìn)行性能測(cè)試,說(shuō)到benchmark,真的是羨慕golang語(yǔ)言自帶的test庫(kù),實(shí)在是太方便了。
測(cè)試內(nèi)部的業(yè)務(wù)邏輯非常簡(jiǎn)單,只是調(diào)用一次System.currentTimeMillis()并返回long時(shí)間戳。
性能測(cè)試中分別使用Result
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的性能損耗確實(shí)是普通方法調(diào)用的360倍,有的人也確實(shí)是基于這種理由得出Java異常中斷性能損耗嚴(yán)重的結(jié)論。
分析性能的影響但是需要注意時(shí)間單位,只是微秒而已,毫秒的千分之一、秒的百萬(wàn)分之一。
假設(shè)某個(gè)微服務(wù)單CPU吞吐量為1000QPS,而其中有10%是非法請(qǐng)求,那么異常中斷的性能損耗也只是萬(wàn)分之一而已,對(duì)于服務(wù)耗時(shí)的影響也只是0.001毫秒而已。
在性能測(cè)試中,業(yè)務(wù)耗時(shí)只是獲取系統(tǒng)時(shí)間,大概耗時(shí)為25ns。正因?yàn)槿绱瞬棚@得異常中斷的性能損耗達(dá)到恐怖的“幾百倍”,但是如果業(yè)務(wù)耗時(shí)從25ns變?yōu)?b>25us、25ms呢?
再談性能瓶頸我們?cè)诜治鱿到y(tǒng)性能時(shí),一定要搞清楚它的數(shù)量級(jí)以及性能瓶頸,切記陷入性能優(yōu)化的困境。
舉個(gè)粗糙例子,在常規(guī)服務(wù)中,利用了索引的DB操作在1~10毫秒之間,訪問分布式Cache的耗時(shí)在3~30毫秒之間,微服務(wù)RPC的網(wǎng)絡(luò)性能損耗在3~10毫秒之間,客戶端與服務(wù)器之間的網(wǎng)絡(luò)耗時(shí)在5~300毫秒之間,如此之類等等。在這種情況下,優(yōu)化0.001毫秒的性能隱患無(wú)異于撿了芝麻丟了西瓜。
我曾經(jīng)寫過類似TCP的底層網(wǎng)絡(luò)協(xié)議,在那種高頻場(chǎng)景中,算法優(yōu)化帶來(lái)0.1微秒的性能優(yōu)化就意味著每秒鐘吞吐量幾成甚至幾倍的提升,但是在分布式調(diào)用的低頻場(chǎng)景中,這種性能用處沒有任何用處。
另外一個(gè)例子,幾年前我和同事在討論DB數(shù)據(jù)表設(shè)計(jì)時(shí),因?yàn)橛唵螤顟B(tài)使用什么長(zhǎng)度的int而爭(zhēng)執(zhí)的面紅脖子粗,現(xiàn)在想想,訂單狀態(tài)上優(yōu)化的1個(gè)字節(jié),長(zhǎng)年累月下來(lái)也只是節(jié)省不到1MB的磁盤空間而已,有什么用呢?
RPC中的異常中斷對(duì)于使用Dubbo、HSF這種遠(yuǎn)程調(diào)用框架而言,使用異常中斷進(jìn)行錯(cuò)誤信息傳遞,需要注意一點(diǎn)就是,異常類型需要設(shè)計(jì)為通用的,即各個(gè)微服務(wù)都引用的基礎(chǔ)類型。
在某廠的技術(shù)規(guī)范中有說(shuō)到:
1) 使用拋異常返回方式,調(diào)用方如果沒有捕獲到就會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤。2) 如果不加棧信息,只是new自定義異常,加入自己的理解的error message,對(duì)于調(diào)用端解決問題的幫助不會(huì)太多。如果加了棧信息,在頻繁調(diào)用出錯(cuò)的情況下,數(shù)據(jù)序列化和傳輸?shù)男阅軗p耗也是問題。
我對(duì)這種技術(shù)規(guī)范相當(dāng)?shù)牟灰詾槿弧?/p>
首先業(yè)務(wù)異常本來(lái)就需要調(diào)用方透?jìng)鹘o最外層,諸如數(shù)據(jù)不存在、登錄失效、用戶鎖定這種異常,中間的調(diào)用方捕獲了也往往沒有什么用。
其次又是鬼扯性能損耗,這種低頻的數(shù)據(jù)序列化和內(nèi)網(wǎng)傳輸會(huì)有什么樣的性能損耗呢?棧信息透?jìng)鹘o調(diào)用方也有益于故障排查,我曾經(jīng)接到過TC的異常棧信息,根據(jù)棧中的package直接就繞過三四層找到了底層出錯(cuò)的地方,可以說(shuō)是節(jié)省了大量的時(shí)間。
結(jié)論在分布式微服務(wù)中,采用異常中斷可以大幅精簡(jiǎn)業(yè)務(wù)代碼,對(duì)于性能的影響也微乎其微。
輔助以@NotNull、@Nullable等注解,可以讓分布式開發(fā)如風(fēng)一般的快速便捷。在復(fù)雜的服務(wù)網(wǎng)絡(luò)中,業(yè)務(wù)異常也可以方便開發(fā)人員精確地定位錯(cuò)誤,避免大家順著調(diào)用鏈一層一層地追蹤故障點(diǎn)的尷尬情景。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/73640.html
摘要:原型鏈只看構(gòu)造函數(shù)的原型對(duì)象和實(shí)例化出來(lái)的對(duì)象。既然構(gòu)造函數(shù)本身是函數(shù),那么和直接調(diào)用有什么區(qū)別答案揭曉只有通過調(diào)用構(gòu)造函數(shù)才會(huì)走第二步,也就是原型的委托操作。 原型 相信js開發(fā)者都知道原型,原型鏈,但是很多人暈暈乎乎對(duì)此不知甚解。下面分享一下我的個(gè)人心得。 學(xué)習(xí)中的困惑 構(gòu)造函數(shù),原型,實(shí)例對(duì)象之間的關(guān)系是什么? 原型鏈?zhǔn)窃趺蠢^承的? 既然構(gòu)造函數(shù)本身是函數(shù),那么new和直接調(diào)用...
摘要:能幫我們解決什么痛點(diǎn)實(shí)現(xiàn)異步執(zhí)行,在未出現(xiàn)前,我們通常是使用嵌套的回調(diào)函數(shù)來(lái)解決的。那么,接下來(lái)我們看一下使用的實(shí)例可以傳入兩個(gè)參數(shù)表示兩個(gè)狀態(tài)的回調(diào)函數(shù),第一個(gè)是,必選參數(shù)第二個(gè)是,可選參數(shù)的方便之處。 深入理解promise 對(duì)于現(xiàn)在的前端同學(xué)來(lái)說(shuō)你不同promise你都不好意思出門了。對(duì)于前端同學(xué)來(lái)說(shuō)promise已經(jīng)成為了我們的必備技能。 那么,下面我們就來(lái)說(shuō)一說(shuō)promise...
摘要:你能學(xué)到什么如何使用實(shí)現(xiàn)異步編程異步編程的原理解析前言結(jié)合上一篇文章,我們來(lái)聊聊基礎(chǔ)原理說(shuō)到異步編程,你想到的是和,但那也只是的語(yǔ)法糖而已。表示一個(gè)異步操作的最終狀態(tài)完成或失敗,以及其返回的值。至此實(shí)現(xiàn)異步操作的控制。 你能學(xué)到什么 如何使用 Generator + Promise 實(shí)現(xiàn)異步編程 異步編程的原理解析 前言 結(jié)合 上一篇文章 ,我們來(lái)聊聊 Generator 基礎(chǔ)原理...
摘要:數(shù)據(jù)的層級(jí)意味著迭代數(shù)據(jù)結(jié)構(gòu)并提取它的數(shù)據(jù)。對(duì)于技術(shù)人而言技是單兵作戰(zhàn)能力,術(shù)則是運(yùn)用能力的方法。在前端娛樂圈,我想成為一名出色的人民藝術(shù)家。 聊聊 for of 說(shuō)起 for of 相信每個(gè)寫過 JavaScript 的人都用過 for of ,平時(shí)我們用它做什么呢?大多數(shù)情況應(yīng)該就是遍歷數(shù)組了,當(dāng)然,更多時(shí)候,我們也會(huì)用 map() 或者 filer() 來(lái)遍歷一個(gè)數(shù)組。 但是就...
閱讀 7417·2021-09-22 15:36
閱讀 6231·2021-09-02 10:20
閱讀 1958·2019-08-30 15:44
閱讀 2799·2019-08-29 14:06
閱讀 1260·2019-08-29 11:17
閱讀 1692·2019-08-26 14:05
閱讀 3291·2019-08-26 13:50
閱讀 1637·2019-08-26 10:26