作者:京東物流 馮志文
前兩篇從服務(wù)粒度和服務(wù)內(nèi)的分層架構(gòu)角度探討,本文繼續(xù)從服務(wù)間代碼復(fù)用角度探討。
背景
在分布式架構(gòu)中,代碼復(fù)用是個難題。那么如何處理代碼功能共享的問題呢?本文結(jié)合日常實踐中的案例,介紹幾種分布式架構(gòu)中管理代碼復(fù)用性的技術(shù)。包括代碼復(fù)制、共享代碼庫(jar包)、共享服務(wù)、邊車服務(wù)。對于每一種技術(shù),列出優(yōu)缺點、適合場景權(quán)衡。
本文的觀點源自我在學(xué)習(xí)與實踐過程中的深思熟慮,尚處于不斷探索和驗證的階段。希望能“拋磚引玉”,激發(fā)更多的討論與交流。讓我們共同進(jìn)步,在探討與實證中尋求真知。
一、代碼復(fù)制
共享的代碼被復(fù)制到每一個服務(wù)中。這種技術(shù)在服務(wù)早期比較流行。雖然現(xiàn)在代碼復(fù)制比較少見,但它還是解決跨多個分布式服務(wù)的代碼復(fù)用的有效技術(shù)。但這種缺點很明顯,因為如果在代碼中發(fā)現(xiàn)錯誤或需要對代碼進(jìn)行重構(gòu)改造,需要在包含該代碼庫的所有服務(wù)變更。
這種技術(shù)在一些場景比較有用,比如服務(wù)需要的高度靜態(tài)的一次性代碼。這種類型的代碼非常適合復(fù)制,因為它是靜態(tài)的并且不包含任何錯誤。比如很多通用的業(yè)務(wù)識別邏輯就用這種方式,在不同應(yīng)用代碼庫個應(yīng)用中編寫。
案例1:根據(jù)sendpay標(biāo)位判斷XXX代碼
public static boolean isXXX(String sendpay) { boolean flag = false; String sendpay_x = Character.toString(sendpay.charAt(x)); String sendpay_y = Character.toString(sendpay.charAt(y)); if (("1".equals(sendpay_y) || "2".equals(sendpay_y) || "0".equals(sendpay_y)) && "1".equals(sendpay_x)) { flag = true; } return flag; }
案例2:新功能上線DUCC開關(guān)
/** * XXX功能 控制開關(guān) */ private boolean enableLargeApplianceSendMsg = false; public boolean isEnableLargeApplianceSendMsg() { return enableLargeApplianceSendMsg; } public void setEnableLargeApplianceSendMsg(boolean enableLargeApplianceSendMsg) { this.enableLargeApplianceSendMsg = enableLargeApplianceSendMsg; log.info("enableLargeApplianceSendMsg: {}", enableLargeApplianceSendMsg); }
代碼復(fù)制技術(shù) | |
---|---|
優(yōu)點 | 1、無代碼共享 |
缺點 | 1、代碼分散各應(yīng)用中,變更起來比較復(fù)雜。 2、無法保證跨服務(wù)代碼一致性,有時候容易遺漏某個地方未修改 3、缺乏跨服務(wù)版本控制能力 |
適合場景 | 1、關(guān)聯(lián)應(yīng)用較少,比如2-3個左右可以接受,如果牽扯應(yīng)用較多(比如5+,具體如何確定“較多”的基準(zhǔn)或者指標(biāo)待定)則不適合,同時核心要思考是不是服務(wù)粒度拆分的太細(xì)了? 2、簡單的靜態(tài)代碼(比如公共工具類,業(yè)務(wù)邏輯通用類) 3、變更頻率較低 |
二、共享代碼庫
共享代碼庫是共享技術(shù)的常用技術(shù)之一。共享代碼庫是一個外部組件(比如JAVA的jar包),共享代碼庫比代碼復(fù)用更高一層級?;诠δ軇澐?,提供較為獨(dú)立的功能。比如目前采用了根據(jù)基礎(chǔ)能力和業(yè)務(wù)模塊抽象時效內(nèi)核JAR包方式,詳見下圖中時效計算內(nèi)核jar包。為什么要采用這種方式呢?后文會說明權(quán)衡點。
jar包引入看起來簡單,在編譯時被整合和共享。但其實也有利弊取舍和復(fù)雜性。其中最重要的是代碼庫的共享庫粒度和版本控制。
1、依賴管理和變更控制
如果粗粒度共享庫中任何類文件發(fā)生變更,都需要每個服務(wù)變更、測試、部署。這極大的增加了共享庫更改的整體測試范圍。粗粒度共享庫的變更會影響多個服務(wù),但會減少依賴關(guān)系。
將共享庫分解為更小的基于功能的共享庫(比如拆分為 ABCDE等),這樣有利于變更控制和整體可維護(hù)性。但這會造成依賴管理的混亂。如下圖,業(yè)務(wù)根據(jù)不同業(yè)務(wù)域拆分不同 jar包,導(dǎo)致jar包依賴復(fù)雜。
共享庫對少量的應(yīng)用可能并不重要,但隨著服務(wù)數(shù)量增加,變更管理和依賴管理相關(guān)的問題也會增加。
建議:避免大的粗粒度的共享庫,盡可能爭取更小的、功能分區(qū)的庫,有利于變更控制而不是依賴管理。
2、版本控制策略
對于共享庫來說,版本控制需要向后兼容(后退、考慮舊版本兼容),同時也需要高度敏捷性。比如a.jar包變更升級版本為1.1,只需要1個服務(wù)上線,其他N個服務(wù)不會有任何影響。這雖然看起來版本控制很簡單,但同樣存在權(quán)衡和隱藏的復(fù)雜性。比如下次N個應(yīng)用也需要上線。并且依賴管理混亂(服務(wù)ABC依賴a.jar版本1.1,服務(wù)DEF依賴a.jar版本1.0),復(fù)雜性不僅體現(xiàn)在版本變更的通知,也存在舊版本棄用的情況。
共享代碼庫技術(shù) | |
---|---|
優(yōu)點 | 1、減少重復(fù)代碼 2、不受網(wǎng)絡(luò)影響,性能更加穩(wěn)定。對性能要求較高的場景使用該方式會有一定優(yōu)勢; 3、節(jié)省服務(wù)器硬件成本,尤其服務(wù)器QPS高,需要部署大量服務(wù)器資源的場景下。 4、支持版本變更 |
缺點 | 1、可維護(hù)性較差,依賴了該組件的服務(wù)都需要跟著一起升級,隨著時間的推移,梳理維護(hù)起來會很麻煩; 2、組件升級成本高且風(fēng)險較大,成本包括了開發(fā)維護(hù)升級各個服務(wù)的成本、測試驗證的成本及運(yùn)維發(fā)布的成本,需升級維護(hù)的服務(wù)越多,成本越高,對應(yīng)的風(fēng)險也越大。 3、容易jar包沖突 4、版本溝通可能很困難 5、依賴可能難以管理、版本棄用可能很復(fù)雜 6、如jar包過大,維護(hù)困難,并且調(diào)用方引入過多項目無用代碼 |
適合場景 | 1、無隱形依賴,更新頻率低和更新影響小的代碼,比如通用的判斷訂單、運(yùn)單校驗 2、服務(wù)器資源硬件成本控制要求較為嚴(yán)格,盡量降低成本。 3、內(nèi)部一些公共功能處理場景,不涉及到數(shù)據(jù)庫資源層面的連接和調(diào)用,適合組件化的方式; 4、對性能要求較高的應(yīng)用 |
為什么時效內(nèi)核需要采用jar包給下單前下單后各應(yīng)用這種方式呢?結(jié)合上面的優(yōu)缺點,主要權(quán)衡核心點如下
1.應(yīng)用場景相關(guān):XX是下單前商詳結(jié)算等高并發(fā)場景,下單后訂單生產(chǎn)節(jié)奏控制。
2.降本:預(yù)估降低了服務(wù)器硬件成本XXX核左右
3.性能:通過JAR包的依賴的方式來較少RPC調(diào)用,提升了接口性能TP99,尤其是用戶在商詳、結(jié)算提單頁面
4.更新頻率低:由于時效內(nèi)核更新頻率較低,一年1-2次左右的改動點
三、共享服務(wù)
共享服務(wù)技術(shù)通過將共享功能服務(wù)化來避免重復(fù)使用。對應(yīng)上面改造,把時效內(nèi)核jar包進(jìn)行服務(wù)化時效內(nèi)核應(yīng)用,具體架構(gòu)圖如下:
共享服務(wù)是分布式中常見的共享服務(wù)的方法,但也需要權(quán)衡,比如變更風(fēng)險、性能、可伸縮性、容錯性。
1、變更風(fēng)險
使用共享服務(wù)變更共享代碼是一把雙刃劍。 只需要共享服務(wù)部署上線,但共享服務(wù)的變更可能在運(yùn)行時破壞其他服務(wù)。那必須牽扯版本控制、共享代碼庫是在編譯的時候綁定版本控制,降低更改風(fēng)險。但如何在共享服務(wù)中版本化變更呢?使用API版本控制。但使用API版本控制有個問題,很多服務(wù)協(xié)議不是restful api,而是rpc或者消息mq,這樣會使得版本控制復(fù)雜。
共享服務(wù)雖然版本控制可以幫助降低這風(fēng)險,但它的應(yīng)用和管理更復(fù)雜。
2、性能
共享功能服務(wù)必須進(jìn)行服務(wù)間調(diào)用,存在網(wǎng)絡(luò)延遲開銷而影響性能。
3、可伸縮性
共享服務(wù)一定要隨著調(diào)用服務(wù)的規(guī)模進(jìn)行伸縮
共享服務(wù)技術(shù) | |
---|---|
優(yōu)點 | 1、減少重復(fù)代碼 2、高度解耦: 每個服務(wù)都是獨(dú)立的,可以獨(dú)立開發(fā)、部署和擴(kuò)展,提高了系統(tǒng)的可維護(hù)性和可擴(kuò)展性。 3、快速迭代: 服務(wù)可以根據(jù)需求快速更新和迭代,更容易適應(yīng)業(yè)務(wù)變化。 4、資源隔離,互不影響,對調(diào)用方隱藏內(nèi)部細(xì)節(jié)。 |
缺點 | 1、增加硬件成本 2、性能受到網(wǎng)絡(luò)延遲影響 3、服務(wù)依賴導(dǎo)致 容錯性、可用性、可伸縮性、吞吐量問題 4、版本控制可能困難 5、分布式固有問題:比如一致性、分布式事務(wù)處理等 |
適合場景 | 1、適合多語言的環(huán)境 2、共享功能頻繁變化 3、不需要太多的服務(wù)器資源 4、對性能要求不高 |
四、邊車和ServiceMesh服務(wù)網(wǎng)格
"邊車服務(wù)"(Sidecar Pattern)這個術(shù)語來源于摩托車的邊車(sidecar),這是一種附加在摩托車旁邊的一輪車廂,可以搭載乘客或貨物,但它不是摩托車本身的核心部分。
邊車服務(wù)(Sidecar Pattern)在微服務(wù)架構(gòu)中用于將一些與業(yè)務(wù)邏輯不直接相關(guān)的控制面(如注冊發(fā)現(xiàn)、熔斷限流、pfinder鏈路追蹤監(jiān)控、DUCC配置管理等)從應(yīng)用程序中分離出來。這樣,應(yīng)用程序可以專注于業(yè)務(wù)邏輯,而邊車服務(wù)則負(fù)責(zé)處理其他方面的問題。
邊車服務(wù)的關(guān)鍵特點包括:
?復(fù)用性:由于邊車服務(wù)可以被多個主應(yīng)用共享,因此一些通用的功能(如服務(wù)發(fā)現(xiàn)、斷路器、限流器等)可以在不同的服務(wù)之間重用,減少了代碼的冗余。
?隔離性:邊車為主應(yīng)用提供了一個清晰的隔離層,使得主應(yīng)用可以專注于業(yè)務(wù)邏輯,而不必關(guān)心其他非功能性的問題。 邊車服務(wù)是主應(yīng)用程序的附屬,為主應(yīng)用提供支持和增強(qiáng)功能。
?易于維護(hù):邊車的引入使得對于共享功能的更新和維護(hù)變得更加簡單,因為這些功能被集中到單獨(dú)的服務(wù)中,不需要在每個應(yīng)用中單獨(dú)進(jìn)行修改。
?透明性:對于主應(yīng)用程序來說,邊車的存在應(yīng)該是透明的,主應(yīng)用不需要知道邊車的具體實現(xiàn)細(xì)節(jié)。
?獨(dú)立性:邊車服務(wù)可以獨(dú)立于主應(yīng)用程序更新和維護(hù),無需修改主應(yīng)用程序的代碼。
通過使用邊車模式,開發(fā)人員可以將關(guān)注點分離,使主應(yīng)用程序更加簡潔,只關(guān)注業(yè)務(wù)邏輯的實現(xiàn),而將服務(wù)治理等通用性問題交給邊車服務(wù)處理。
如果每個服務(wù)都包含邊車組件,那么它就形成了服務(wù)網(wǎng)格。每個服務(wù)右邊的盒子都互相連接,形成一個“網(wǎng)格”
服務(wù)架構(gòu)是圍繞各自領(lǐng)域組織的,但服務(wù)治理運(yùn)維耦合需要橫切這些領(lǐng)域
五、總結(jié)
技術(shù)最終是要服務(wù)于業(yè)務(wù),每種技術(shù)選擇沒有絕對的好壞,各有優(yōu)缺點,適合場景。具體應(yīng)該用哪一種,需要根據(jù)成本、團(tuán)隊技能、系統(tǒng)的未來發(fā)展綜合考慮,目前團(tuán)隊系統(tǒng)中上面幾種情況都存在。正如軟件架構(gòu)定律:軟件架構(gòu)中的一切都是在權(quán)衡,架構(gòu)背后的原因比方法更重要。
審核編輯 黃宇
-
分布式
+關(guān)注
關(guān)注
1文章
911瀏覽量
74568 -
代碼
+關(guān)注
關(guān)注
30文章
4807瀏覽量
68801 -
架構(gòu)
+關(guān)注
關(guān)注
1文章
516瀏覽量
25502 -
復(fù)用
+關(guān)注
關(guān)注
0文章
7瀏覽量
12011
發(fā)布評論請先 登錄
相關(guān)推薦
評論