日本黄色一级经典视频|伊人久久精品视频|亚洲黄色色周成人视频九九九|av免费网址黄色小短片|黄色Av无码亚洲成年人|亚洲1区2区3区无码|真人黄片免费观看|无码一级小说欧美日免费三级|日韩中文字幕91在线看|精品久久久无码中文字幕边打电话

當前位置:首頁 > > 架構(gòu)師社區(qū)
[導讀]RPC 框架的討論一直是各個技術交流群中的熱點話題,阿里的 dubbo,新浪微博的 motan,谷歌的 grpc,以及不久前螞蟻金服開源的 sofa,都是比較出名的 RPC 框架。

RPC 框架的討論一直是各個技術交流群中的熱點話題,阿里的 dubbo,新浪微博的 motan,谷歌的 grpc,以及不久前螞蟻金服開源的 sofa,都是比較出名的 RPC 框架。RPC 框架,或者一部分人習慣稱之為服務治理框架,更多的討論是存在于其技術架構(gòu),比如 RPC 的實現(xiàn)原理,RPC 各個分層的意義,具體 RPC 框架的源碼分析…但卻并沒有太多話題和“如何設計 RPC 接口”這樣的業(yè)務架構(gòu)相關。

設計RPC接口時,你有考慮過這些嗎? 段子

可能很多小公司程序員還是比較關心這個問題的,這篇文章主要分享下一些個人眼中 RPC 接口設計的最佳實踐。

初識 RPC 接口設計

由于 RPC 中的術語每個程序員的理解可能不同,所以文章開始,先統(tǒng)一下 RPC 術語,方便后續(xù)闡述。

大家都知道共享接口是 RPC 最典型的一個特點,每個服務對外暴露自己的接口,該模塊一般稱之為 api;外部模塊想要實現(xiàn)對該模塊的遠程調(diào)用,則需要依賴其 api;每個服務都需要有一個應用來負責實現(xiàn)自己的 api,一般體現(xiàn)為一個獨立的進程,該模塊一般稱之為 app。

api 和 app 是構(gòu)建微服務項目的最簡單組成部分,如果使用 maven 的多 module 組織代碼,則體現(xiàn)為如下的形式。

serviceA 服務

serviceA/pom.xml 定義父 pom 文件

<modules> <module>serviceA-apimodule> <module>serviceA-appmodule> modules> <packaging>pompackaging> <groupId>moe.cnkiritogroupId> <artifactId>serviceAartifactId> <version>1.0.0-SNAPSHOTversion> 

serviceA/serviceA-api/pom.xml 定義對外暴露的接口,最終會被打成 jar 包供外部服務依賴

<parent> <artifactId>serviceAartifactId> <groupId>moe.cnkiritogroupId> <version>1.0.0-SNAPSHOTversion> parent> <packaging>jarpackaging> <artifactId>serviceA-apiartifactId> 

serviceA/serviceA-app/pom.xml 定義了服務的實現(xiàn),一般是 springboot 應用,所以下面的配置文件中,我配置了 springboot 應用打包的插件,最終會被打成 jar 包,作為獨立的進程運行。

<parent> <artifactId>serviceAartifactId> <groupId>moe.cnkiritogroupId> <version>1.0.0-SNAPSHOTversion> parent> <packaging>jarpackaging> <artifactId>serviceA-appartifactId> <build> <plugins> <plugin> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-maven-pluginartifactId> plugin> plugins> build> 

麻雀雖小,五臟俱全,這樣一個微服務模塊就實現(xiàn)了。

舊 RPC 接口的痛點

統(tǒng)一好術語,這一節(jié)來描述下我曾經(jīng)遭遇過的 RPC 接口設計的痛點,相信不少人有過相同的遭遇。

  • 查詢接口過多。各種 findBy 方法,加上各自的重載,幾乎占據(jù)了一個接口 80% 的代碼量。這也符合一般人的開發(fā)習慣,因為頁面需要各式各樣的數(shù)據(jù)格式,加上查詢條件差異很大,便造成了:一個查詢條件,一個方法的尷尬場景。這樣會導致另外一個問題,需要使用某個查詢方法時,直接新增了方法,但實際上可能這個方法已經(jīng)出現(xiàn)過了,隱藏在了令人眼花繚亂的方法中。

  • 難以擴展。接口的任何改動,比如新增一個入?yún)?,都會導致調(diào)用者被迫升級,這也通常是 RPC 設計被詬病的一點,不合理的 RPC 接口設計會放大這個缺點。

  • 升級困難。 在之前的 “初識 RPC 接口設計”一節(jié)中,版本管理的粒度是 project,而不是 module,這意味著:api 即使沒有發(fā)生變化,app 版本演進,也會造成 api 的被迫升級,因為 project 是一個整體。問題又和上一條一樣了,api 一旦發(fā)生變化,調(diào)用者也得被迫升級,牽一發(fā)而動全身。

  • 難以測試。接口一多,職責隨之變得繁雜,業(yè)務場景各異,測試用例難以維護。特別是對于那些有良好習慣編寫單元測試的程序員而言,簡直是噩夢,用例也得跟著改。

  • 異常設計不合理。在既往的工作經(jīng)歷中曾經(jīng)有一次會議,就 RPC 調(diào)用中的異常設計引發(fā)了爭議,一派人覺得需要有一個業(yè)務 CommonResponse,封裝異常,每次調(diào)用后,優(yōu)先判斷調(diào)用結(jié)果是否 success,在進行業(yè)務邏輯處理;另一派人覺得這比較麻煩,由于 RPC 框架是可以封裝異常調(diào)用的,所以應當直接 try catch 異常,不需要進行業(yè)務包裹。在沒有明確規(guī)范時,這兩種風格的代碼同時存在于項目中,十分難看!

在千米網(wǎng)的三個月中,看了不少最佳實踐。加上一次公司內(nèi)部易永健老師的分享,涉及到了相同的話題,耳濡目染,這些曾經(jīng)我發(fā)覺的痛點也逐漸有了解決之道。

單參數(shù)接口

如果你使用過 springcloud ,可能會不適應 http 通信的限制,因為 @RequestBody 只能使用單一的參數(shù),也就意味著,springcloud 構(gòu)建的微服務架構(gòu)下,接口天然是單參數(shù)的。而 RPC 方法入?yún)⒌膫€數(shù)在語法層面是不會受到限制的,但如果強制要求入?yún)閱螀?shù),會解決一部分的痛點。

使用 Specification 模式解決查詢接口過多的問題

public interface StudentApi{ Student findByName(String name); ListfindAllByName(String name); Student findByNameAndNo(String name,String no); Student findByIdcard(String Idcard);
}

如上的多個查詢方法目的都是同一個:根據(jù)條件查詢出 Student,只不過查詢條件有所差異。試想一下,Student 對象假設有 10 個屬性,最壞的情況下它們的排列組合都可能作為查詢條件,這便是查詢接口過多的根源。

public interface StudentApi{ Student findBySpec(StudentSpec spec); ListfindListBySpec(StudentListSpec spec); PagefindPageBySpec(StudentPageSpec spec);
}

上述接口便是最通用的單參接口,三個方法幾乎囊括了 99% 的查詢條件。所有的查詢條件都被封裝在了 StudentSpec,StudentListSpec,StudentPageSpec 之中,分別滿足了單對象查詢,批量查詢,分頁查詢的需求。如果你了解領域驅(qū)動設計,會發(fā)現(xiàn)這里借鑒了其中 Specification 模式的思想。

單參數(shù)易于做統(tǒng)一管理

public interface SomeProvider { void opA(ARequest request); void opB(BRequest request); CommonResponseopC(CRequest request);
}

入?yún)⒅械娜雲(yún)㈦m然形態(tài)各異,但由于是單個入?yún)ⅲ钥梢越y(tǒng)一繼承 AbstractBaseRequest,即上述的 ARequest,BRequest,CRequest 都是 AbstractBaseRequest 的子類。在千米內(nèi)部項目中,AbstractBaseRequest 定義了 traceId、clientIp、clientType、operationType 等公共入?yún)?,減少了重復命名,我們一致認為,這更加的 OO。

有了 AbstractBaseRequest,我們可以更加輕松地在其之上做 AOP,千米的實踐中,大概做了如下的操作:

  • 請求入?yún)⒔y(tǒng)一校驗(request.checkParam(); param.checkParam();)

  • 實體變更統(tǒng)一加鎖,降低鎖粒度

  • 請求分類統(tǒng)一處理(if (request instanceof XxxRequest))

  • 請求報文統(tǒng)一記日志(log.setRequest(JsonUtil.getJsonString(request)))

  • 操作成功統(tǒng)一發(fā)消息

如果不遵守單參數(shù)的約定,上述這些功能也并不是無法實現(xiàn),但所需花費的精力遠大于單參數(shù),一個簡單的約定帶來的優(yōu)勢,我們認為是值得的。

單參數(shù)入?yún)⒓嫒菪詮?/span>

還記得前面的小節(jié)中,我提到了 SpringCloud,在 SpringCloud Feign 中,接口的入?yún)⑼ǔ?@RequestBody 修飾,強制做單參數(shù)的限制。千米內(nèi)部使用了 Dubbo 作為 Rpc 框架,一般而言,為 Dubbo 服務設計的接口是不能直接用作 Feign 接口的(主要是因為 @RequestBody 的限制),但有了單參數(shù)的限制,便使之成為了可能。為什么我好端端的 Dubbo 接口需要兼容 Feign 接口?可能會有人發(fā)出這樣的疑問,莫急,這樣做的初衷當然不是為了單純做接口兼容,而是想充分利用 HTTP 豐富的技術棧以及一些自動化工具。

  • 自動生成 HTTP 接口實現(xiàn)(讓服務端同時支持 Dubbo 和 HTTP 兩種服務接口)

看過我之前文章的朋友應該了解過一個設計:千米內(nèi)部支持的是 Dubbo 協(xié)議和 HTTP 協(xié)議族(如 JSON RPC 協(xié)議,Restful 協(xié)議),這并不意味著程序員需要寫兩份代碼,我們可以通過 Dubbo 接口自動生成 HTTP 接口,體現(xiàn)了單參數(shù)設計的兼容性之強。

  • 通過 Swagger UI 實現(xiàn)對 Dubbo 接口的可視化便捷測試

又是一個兼容 HTTP 技術棧帶來的便利,在 Restful 接口的測試中,Swagger 一直是備受青睞的一個工具,但可惜的是其無法對 Dubbo 接口進行測試。兼容 HTTP 后,我們只需要做一些微小的工作,便可以實現(xiàn) Swagger 對 Dubbo 接口的可視化測試。

  • 有利于 TestNg 集成測試

自動生成 TestNG 集成測試代碼和缺省測試用例,這使得服務端接口集成測試變得異常簡單,程序員更能集中精力設計業(yè)務用例,結(jié)合缺省用例、JPA 自動建表和 PowerMock 模擬外部依賴接口實現(xiàn)本機環(huán)境。

設計RPC接口時,你有考慮過這些嗎? TestNg 自動化測試

這塊涉及到了公司內(nèi)部的代碼,只做下簡單介紹,我們一般通過內(nèi)部項目 com.qianmi.codegenerator:api-dubbo-2-restful ,com.qianmi.codegenerator:api-request-json 生成自動化的測試用例,方便測試。而這些自動化工具中大量使用了反射,而由于單參數(shù)的設計,反射用起來比較方便。

接口異常設計

首先肯定一點,RPC 框架是可以封裝異常的,Exception 也是返回值的一部分。在 go 語言中可能更習慣于返回 err,res 的組合,但 JAVA 中我個人更偏向于 try catch 的方法捕獲異常。RPC 接口設計中的異常設計也是一個注意點。

初始方案

public interface ModuleAProvider { void opA(ARequest request); void opB(BRequest request); CommonResponseopC(CRequest request);
}

我們假設模塊 A 存在上述的 ModuleAProvider 接口,ModuleAProvider 的實現(xiàn)中或多或少都會出現(xiàn)異常,例如可能存在的異常 ModuleAException,調(diào)用者實際上并不知道 ModuleAException 的存在,只有當出現(xiàn)異常時,才會知曉。對于 ModuleAException 這種業(yè)務異常,我們更希望調(diào)用方能夠顯示的處理,所以 ModuleAException 應該被設計成 Checked Excepition。

正確的異常設計姿勢

public interface ModuleAProvider { void opA(ARequest request) throws ModuleAException; void opB(BRequest request) throws ModuleAException; CommonResponseopC(CRequest request) throws ModuleAException;
}

上述接口中定義的異常實際上也是一種契約,契約的好處便是不需要敘述,調(diào)用方自然會想到要去處理 Checked Exception,否則連編譯都過不了。

調(diào)用方的處理方式

在 ModuleB 中,應當如下處理異常:

public class ModuleBService implements ModuleBProvider { @Reference ModuleAProvider moduleAProvider; @Override public void someOp() throws ModuleBexception{ try{
            moduleAProvider.opA(...);
        }catch(ModuleAException e){ throw new ModuleBException(e.getMessage());
        }
    } @Override public void anotherOp(){ try{
            moduleAProvider.opB(...);
        }catch(ModuleAException e){ // 業(yè)務邏輯處理 }
    }
}

someOp 演示了一個異常流的傳遞,ModuleB 暴露出去的異常應當是 ModuleB 的 api 模塊中異常類,雖然其依賴了 ModuleA ,但需要將異常進行轉(zhuǎn)換,或者對于那些意料之中的業(yè)務異常可以像 anotherOp() 一樣進行處理,不再傳遞。這時如果新增 ModuleC 依賴 ModuleB,那么 ModuleC 完全不需要關心 ModuleA 的異常。

異常與熔斷

作為系統(tǒng)設計者,我們應該認識到一點:RPC 調(diào)用,失敗是常態(tài)。通常我們需要對 RPC 接口做熔斷處理,比如千米內(nèi)部便集成了 Netflix 提供的熔斷組件 Hystrix。Hystrix 需要知道什么樣的異常需要進行熔斷,什么樣的異常不能夠進行熔斷。在沒有上述的異常設計之前,回答這個問題可能還有些難度,但有了 Checked Exception 的契約,一切都變得明了清晰了。

public class ModuleAProviderProxy { @Reference private ModuleAProvider moduleAProvider; @HystrixCommand(ignoreExceptions = {ModuleAException.class}) public void opA(ARequest request) throws ModuleAException {
        moduleAProvider.opA(request);
    } @HystrixCommand(ignoreExceptions = {ModuleAException.class}) public void opB(BRequest request) throws ModuleAException {
        moduleAProvider.oBB(request);
    } @HystrixCommand(ignoreExceptions = {ModuleAException.class}) public CommonResponseopC(CRequest request) throws ModuleAException { return moduleAProvider.opC(request);
    }

}

如服務不可用等原因引發(fā)的多次接口調(diào)用超時異常,會觸發(fā) Hystrix 的熔斷;而對于業(yè)務異常,我們則認為不需要進行熔斷,因為對于接口 throws 出的業(yè)務異常,我們也認為是正常響應的一部分,只不過借助于 JAVA 的異常機制來表達。實際上,和生成自動化測試類的工具一樣,我們使用了另一套自動化的工具,可以由 Dubbo 接口自動生成對應的 Hystrix Proxy。我們堅定的認為開發(fā)體驗和用戶體驗一樣重要,所以公司內(nèi)部會有非常多的自動化工具。

API 版本單獨演進

引用一段公司內(nèi)部的真實對話:

A:我下載了你們的代碼庫怎么編譯不通過啊,依賴中 xxx-api-1.1.3 版本的 jar 包找不到了,那可都是 RELEASE 版本啊。

B:你不知道我們 nexus 容量有限,只能保存最新的 20 個 RELEASE 版本嗎?那個 API 現(xiàn)在最新的版本是 1.1.31 啦。

A:啊,這才幾個月就幾十個 RELEASE 版本啦?這接口太不穩(wěn)定啦。

B:其實接口一行代碼沒改,我們業(yè)務分析是很牛逼的,一直很穩(wěn)定。但是這個 API 是和我們項目一起打包的,我們需求更新一次,就發(fā)布一次,API 就被迫一起升級版本。發(fā)生這種事,大家都不想的。

在單體式架構(gòu)中,版本演進的單位是整個項目。微服務解決的一個關鍵的痛點便是其做到了每個服務的單獨演進,這大大降低了服務間的耦合。正如我文章開始時舉得那個例子一樣:serviceA 是一個演進的單位,serviceA-api 和 serviceA-app 這兩個 Module 從屬于 serviceA,這意味著 app 的一次升級,將會引發(fā) api 的升級,因為他們是共生的!而從微服務的使用角度來看,調(diào)用者關心的是 api 的結(jié)構(gòu),而對其實現(xiàn)壓根不在乎。所以對于 api 定義未發(fā)生變化,其 app 發(fā)生變化的那些升級,其實可以做到對調(diào)用者無感知。在實踐中也是如此
api 版本的演進應該是緩慢的,而 app 版本的演進應該是頻繁的。
所以,對于這兩個演進速度不一致的模塊,我們應該單獨做版本管理,他們有自己的版本號。

問題回歸

  • 查詢接口過多。各種 findBy 方法,加上各自的重載,幾乎占據(jù)了一個接口 80% 的代碼量。這也符合一般人的開發(fā)習慣,因為頁面需要各式各樣的數(shù)據(jù)格式,加上查詢條件差異很大,便造成了:一個查詢條件,一個方法的尷尬場景。這樣會導致另外一個問題,需要使用某個查詢方法時,直接新增了方法,但實際上可能這個方法已經(jīng)出現(xiàn)過了,隱藏在了令人眼花繚亂的方法中。

解決方案:使用單參+Specification 模式,降低重復的查詢方法,大大降低接口中的方法數(shù)量。

  • 難以擴展。接口的任何改動,比如新增一個入?yún)?,都會導致調(diào)用者被迫升級,這也通常是 RPC 設計被詬病的一點,不合理的 RPC 接口設計會放大這個缺點。

解決方案:單參設計其實無形中包含了所有的查詢條件的排列組合,可以直接在 app 實現(xiàn)邏輯的新增,而不需要對 api 進行改動(如果是參數(shù)的新增則必須進行 api 的升級,參數(shù)的廢棄可以用 @Deprecated 標準)。

  • 升級困難。 在之前的 “初識 RPC 接口設計”一節(jié)中,版本管理的粒度是 project,而不是 module,這意味著:api 即使沒有發(fā)生變化,app 版本演進,也會造成 api 的被迫升級,因為 project 是一個整體。問題又和上一條一樣了,api 一旦發(fā)生變化,調(diào)用者也得被迫升級,牽一發(fā)而動全身。

解決方案:以 module 為版本演進的粒度。api 和 app 單獨演進,減少調(diào)用者的不必要升級次數(shù)。

  • 難以測試。接口一多,職責隨之變得繁雜,業(yè)務場景各異,測試用例難以維護。特別是對于那些有良好習慣編寫單元測試的程序員而言,簡直是噩夢,用例也得跟著改。

解決方案:單參數(shù)設計+自動化測試工具,打造良好的開發(fā)體驗。

  • 異常設計不合理。在既往的工作經(jīng)歷中曾經(jīng)有一次會議,就 RPC 調(diào)用中的異常設計引發(fā)了爭議,一派人覺得需要有一個業(yè)務 CommonResponse,封裝異常,每次調(diào)用后,優(yōu)先判斷調(diào)用結(jié)果是否 success,在進行業(yè)務邏輯處理;另一派人覺得這比較麻煩,由于 RPC 框架是可以封裝異常調(diào)用的,所以應當直接 try catch 異常,不需要進行業(yè)務包裹。在沒有明確規(guī)范時,這兩種風格的代碼同時存在于項目中,十分難看!

解決方案:Checked Exception+正確異常處理姿勢,使得代碼更加優(yōu)雅,降低了調(diào)用方不處理異常帶來的風險。


免責聲明:本文內(nèi)容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關機構(gòu)授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關鍵字: 驅(qū)動電源

在工業(yè)自動化蓬勃發(fā)展的當下,工業(yè)電機作為核心動力設備,其驅(qū)動電源的性能直接關系到整個系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動勢抑制與過流保護是驅(qū)動電源設計中至關重要的兩個環(huán)節(jié),集成化方案的設計成為提升電機驅(qū)動性能的關鍵。

關鍵字: 工業(yè)電機 驅(qū)動電源

LED 驅(qū)動電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個照明設備的使用壽命。然而,在實際應用中,LED 驅(qū)動電源易損壞的問題卻十分常見,不僅增加了維護成本,還影響了用戶體驗。要解決這一問題,需從設計、生...

關鍵字: 驅(qū)動電源 照明系統(tǒng) 散熱

根據(jù)LED驅(qū)動電源的公式,電感內(nèi)電流波動大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關鍵字: LED 設計 驅(qū)動電源

電動汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產(chǎn)業(yè)的重要發(fā)展方向。電動汽車的核心技術之一是電機驅(qū)動控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機驅(qū)動系統(tǒng)中的關鍵元件,其性能直接影響到電動汽車的動力性能和...

關鍵字: 電動汽車 新能源 驅(qū)動電源

在現(xiàn)代城市建設中,街道及停車場照明作為基礎設施的重要組成部分,其質(zhì)量和效率直接關系到城市的公共安全、居民生活質(zhì)量和能源利用效率。隨著科技的進步,高亮度白光發(fā)光二極管(LED)因其獨特的優(yōu)勢逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關鍵字: 發(fā)光二極管 驅(qū)動電源 LED

LED通用照明設計工程師會遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關鍵字: LED 驅(qū)動電源 功率因數(shù)校正

在LED照明技術日益普及的今天,LED驅(qū)動電源的電磁干擾(EMI)問題成為了一個不可忽視的挑戰(zhàn)。電磁干擾不僅會影響LED燈具的正常工作,還可能對周圍電子設備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來解決L...

關鍵字: LED照明技術 電磁干擾 驅(qū)動電源

開關電源具有效率高的特性,而且開關電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機重量也有所下降,所以,現(xiàn)在的LED驅(qū)動電源

關鍵字: LED 驅(qū)動電源 開關電源

LED驅(qū)動電源是把電源供應轉(zhuǎn)換為特定的電壓電流以驅(qū)動LED發(fā)光的電壓轉(zhuǎn)換器,通常情況下:LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關鍵字: LED 隧道燈 驅(qū)動電源
關閉