微服務(wù)架構(gòu)的兩大解耦利器與最佳實踐
原創(chuàng)【51CTO.com原創(chuàng)稿件】這幾年,微服務(wù)架構(gòu)這個術(shù)語漸成熱門詞匯,但它不是一個全新架構(gòu),更不是一個包治百病的架構(gòu)。那么,微服務(wù)架構(gòu)究竟能夠解決什么問題,又帶來哪些痛點?
本文將與大家談?wù)勥@個問題,以及微服務(wù)架構(gòu)的兩大解耦利器配置中心和消息總線的最佳實踐。
微服務(wù)架構(gòu)解決的問題與帶來的痛點
上圖是互聯(lián)網(wǎng)典型的高可用架構(gòu),大部分公司如果沒有使用微服務(wù),正在使用這樣的架構(gòu):
-
用戶端是瀏覽器 browser,APP 客戶端
-
后端入口是高可用的 nginx 集群,用于做反向代理
-
中間核心是高可用的 web-server 集群,研發(fā)工程師主要在這一層進行編碼工作
-
后端存儲是高可用的 db 集群,數(shù)據(jù)存儲在這一層。更典型的公司,web-server 層是通過 DAO/ORM 等技術(shù)來訪問數(shù)據(jù)庫。
最初的架構(gòu)都沒有服務(wù)層,這樣的架構(gòu)會遇到怎樣的痛點?對于沒有使用微服務(wù)架構(gòu)的公司來說,要不要升級到微服務(wù)架構(gòu)呢?
58 同城和 58 到家的架構(gòu)痛點
回答這個問題之前,先來看看您是否遇到和 58 同城及 58 到家類似的架構(gòu)痛點:
圖一,代碼拷貝。A、B、C 業(yè)務(wù)線,如果沒有微服務(wù)架構(gòu),可能要直接訪問數(shù)據(jù)庫里的數(shù)據(jù)來實現(xiàn)自己的業(yè)務(wù)需求。拿訪問用戶數(shù)據(jù)舉例,用戶中心包括所有公司必備的業(yè)務(wù),比如登陸、注冊、查找用戶信息等。如某業(yè)務(wù)線需要訪問用戶信息,需要通過封裝用戶訪問代碼模塊實現(xiàn)。如業(yè)務(wù)繁多,每個業(yè)務(wù)線都需要訪問用戶信息,潛在的會存在代碼拷貝問題。
圖二,底層復(fù)雜性擴散。隨著流量的增長,需要加入緩存,對數(shù)據(jù)的訪問模式和流程都會帶來影響。從直接訪問數(shù)據(jù)庫,變到先訪問緩存再訪問數(shù)據(jù)庫。這樣的復(fù)雜性,所有的業(yè)務(wù)都需關(guān)注,代碼都要重新做一遍。包括數(shù)據(jù)量增大后,要進行的水平線切分、分庫、分表,存儲引擎的變化等復(fù)雜性,要擴展到業(yè)務(wù)線。
圖三,代碼庫耦合。58 同城遇到圖一和圖二問題,最初想到的方案并不是微服務(wù),而是將相互拷貝的復(fù)雜性代碼封裝到一個代碼庫(DLL 或 jar 包),實現(xiàn)統(tǒng)一的相關(guān)功能,屏蔽復(fù)雜性。
拷貝代碼的好處是代碼獨立演化,做改動互不影響。弊端是一旦用上庫,業(yè)務(wù)就會耦合在一起,因共用jar包,一旦其中某個業(yè)務(wù)升級,其他的業(yè)務(wù)就可能受影響。
圖四,數(shù)據(jù)庫耦合。業(yè)務(wù)線不只訪問 user 數(shù)據(jù),還會結(jié)合自己的業(yè)務(wù)訪問自己的數(shù)據(jù):典型的情況是通過 join 數(shù)據(jù)表來實現(xiàn)各自業(yè)務(wù)線的一些業(yè)務(wù)邏輯。這樣的話:
-
業(yè)務(wù)線 A 的 table-user 與 table-A 耦合在了一起;
-
業(yè)務(wù)線 B 的 table-user 與 table-B 耦合在了一起;
-
業(yè)務(wù)線 C 的 table-user 與 table-C 耦合在了一起;
結(jié)果就是:table-user,table-A,table-B,table-C都耦合在了一起。隨著數(shù)據(jù)量的越來越大,業(yè)務(wù)線 ABC 數(shù)據(jù)庫無法進行垂直拆分,必須使用一個大庫(瘋了,一個大庫 300 多個業(yè)務(wù)表 =_=)。
圖五,SQL 質(zhì)量得不到保證,業(yè)務(wù)之間互相影響。由業(yè)務(wù)方拼裝的 SQL 語句調(diào)用方式,通過 ORM(對象關(guān)系映射)的方法生成 SQL 語句數(shù)據(jù)庫,這個庫是共用的,會影響所有的業(yè)務(wù)線。一旦某業(yè)務(wù)有慢 SQL 出現(xiàn),其他業(yè)務(wù)就會受影響。
回到要不要做微服務(wù)升級的問題,如果大家所負責(zé)的系統(tǒng)、模塊或公司也存在以上的這些問題,建議考慮做服務(wù)化,在中間加一個服務(wù)層,所有調(diào)用不允許直接連接底層庫。服務(wù)化還有一個很重要的特點就是數(shù)據(jù)庫私有化,任何人不能跨越服務(wù)程序,干預(yù)數(shù)據(jù)庫。想調(diào)用要通過接口來實現(xiàn),當(dāng)數(shù)據(jù)庫性能變差,直接加一臺機器,把數(shù)據(jù)庫遷移,對調(diào)用方不會產(chǎn)生影響。
在 58 同城,用戶中心由專門的部門負責(zé),是全公司、全業(yè)務(wù)依賴比較重的服務(wù),它對代碼要求和穩(wěn)定性要求比較高。整個 SQL 語句是服務(wù)層控制,向上提供有限的服務(wù)接口和無限的性能。
工程師要保障雖然提供用戶基礎(chǔ)數(shù)據(jù)的接口數(shù)是有限的,但調(diào)用方不需要關(guān)心底層細節(jié),可以認為性能是無限的。至于如何擴容,就是服務(wù)層的事情了。
下圖是互聯(lián)網(wǎng)典型的服務(wù)化架構(gòu)。以用戶中心為例,用戶中心服務(wù)向上屏蔽底層技術(shù)的復(fù)雜性,上層通過 RPC 接口來調(diào)用服務(wù),如同調(diào)用本地函數(shù)一樣,不需要關(guān)注分庫、分表、緩存。
業(yè)務(wù)方需要數(shù)據(jù),把數(shù)據(jù)拼裝出來返回 APP/PC 端即可,可以不關(guān)心數(shù)據(jù)存在哪里,底層的復(fù)雜性也由用戶層來承擔(dān)。這樣一來,用戶庫只有用戶服務(wù)依賴,任何人不得跨越用戶服務(wù)來直接調(diào)用數(shù)據(jù)庫,就不會存在代碼拷貝、代碼庫、數(shù)據(jù)庫耦合的情況。
微服務(wù)架構(gòu)的兩大解耦利器
微服務(wù)雖然看上去很好,但也給系統(tǒng)帶來很多問題,如部署方面,越來越復(fù)雜,分層越來越多,處理時間也隨之增加。如網(wǎng)絡(luò)交互方面,運維負載性、追查問題等等。那么:
-
面對架構(gòu)的耦合及復(fù)雜性如何來優(yōu)化
-
結(jié)構(gòu)如何配置
接下來,我們介紹配置中心最佳實踐與消息總線最佳實踐這兩大解耦利器。
放棄 IP 連接服務(wù),選擇內(nèi)網(wǎng)域名。58 到家是創(chuàng)業(yè)公司,痛點和很多公司都很相似。其中一個場景是 IP 的變化。最初,IP 寫在配置文件中,通過某個 IP 或端口訪問數(shù)據(jù)與服務(wù)。當(dāng)某臺機器出現(xiàn)問題,DB 同事會在新機器做部署,更換 IP。當(dāng)某個服務(wù)或 IP 發(fā)生變化,就在配置文件中修改,重啟。
這里的經(jīng)驗分享是千萬不要用 IP 連接服務(wù)或數(shù)據(jù)庫,要選擇內(nèi)網(wǎng)域名。這兩者的區(qū)別在于:
-
使用 IP 連接服務(wù)或數(shù)據(jù)庫的方式,所有的庫都和一個表有關(guān)聯(lián),一旦機器掛掉或升高配,幾乎所有的業(yè)務(wù)都需要修改 IP。即便只是升級一個業(yè)務(wù),都會嚴重影響其他業(yè)務(wù)。
-
選擇內(nèi)網(wǎng)域名的方式后,如果換 IP,在運維層面可以進行統(tǒng)一切斷,自動向上鏈接,上游的業(yè)務(wù)就不用動,也不受下層變動的影響。
配置私藏。如下圖是 58 到家早期改成內(nèi)網(wǎng)域名之后的配置文件。底層用戶服務(wù)或數(shù)據(jù)庫,是個高可用集群,從 IP1 到 IP3。上游有三個依賴,兩個服務(wù)器,一個 Web 調(diào)用這個高可用集群。Web 包含 WBE2.conf,調(diào)用 IP1,IP2,IP3。
在實踐過程中,這種配置私藏的方式遇到兩個痛點:
-
升級時不知道被那個服務(wù)調(diào)用。當(dāng)遇到流量越來越大,需要添加服務(wù)器時,如上圖,把 IP1 去掉,增加 IP4 和 IP5 的時候,需要通知上游。但問題在于流量不大時,因為對業(yè)務(wù)非常熟悉,工程師能夠準確的找到服務(wù)器對應(yīng)的負責(zé)人。隨著業(yè)務(wù)越來越復(fù)雜,工程師遇到出現(xiàn)了問題,不知道模塊被誰依賴的情況。
-
升級時需要上游配合重啟。當(dāng)增加 IP 時,需要找到對應(yīng)的上游服務(wù)器負責(zé)人,通知他進行服務(wù)器重啟。公司成百上千的服務(wù)每天都有人在升級,當(dāng)時的做法是采用建群,隨時做通知,但這樣很影響研發(fā)同事寫代碼的效率。
全局配置。最開始底層的通用基礎(chǔ)服務(wù),配置是寫在每個站點;而且每個應(yīng)用私藏在配置文件里,在升級過程中,不知道誰私藏了這個配置。
面對這兩個痛點,58到家采用了下圖的解決方案:全局配置
全局配置也就是升級,只需要做流程與規(guī)范上的優(yōu)化,對原有系統(tǒng)架構(gòu)不產(chǎn)生任何影響,成本低且可平滑的慢慢遷移。
下圖的實現(xiàn)原理是把最初放在每個服務(wù)器中的配置文件,抽取一個全局配置文件,做好目錄結(jié)構(gòu) global.conf。所有基礎(chǔ)服務(wù)配置如果由多個 global.conf 上游來讀取,必須通過 global.conf 來讀取。這樣所有的業(yè)務(wù)都在 global.conf,就可以保障下一次升級可連接到最新。
那么,在做擴容的時候,能不能實現(xiàn)調(diào)用方不需要升級呢?當(dāng)然可以,兩個小組件就可以實現(xiàn):
-
監(jiān)控全局文件的變化情況,發(fā)生變化就進行回調(diào),這樣用戶中心要配置修改的是全局配置。
-
動態(tài)鏈接池組件。這是一個自身及調(diào)整流程成本都很低的組件,負載均衡也會在其中實現(xiàn)。
配置中心。全局配置對于服務(wù)提供方而言,問題依然沒有全部解決,擴容不需要重啟,卻仍不知道被誰依賴,不知道被誰訪問,就沒辦法做服務(wù)治理、限流等操作。這時,工程師就要引入配置中心,來解決這個問題。
配置中心思路是部署用戶中心承載所有配置,取代所有全局配置文件。這樣一來,所有都依賴配置中心上游,服務(wù)1,服務(wù)2,服務(wù)3,都不再訪問global.conf,而是通過配置中心來拉取相關(guān)配置,配置變更,配置中心反向回調(diào),調(diào)用方也不要重啟。
配置中心最佳實踐總結(jié)。配置中心是微服務(wù)架構(gòu)中一個邏輯解耦但物理不解耦的利器。它原來在邏輯上依賴于自己的配置文件,依賴于下游,現(xiàn)在不再向配置文件索要配置,而是所有調(diào)用方邏輯上只依賴于配置中心。物理上不解耦,是從配置文件拿到配置以后該連誰還是連誰。
消息總線(Message Queue),后文稱 MQ,是一種跨進程的通信機制,用于上下游傳遞消息。它也是微服務(wù)架構(gòu)中很常見的解耦利器之一,在數(shù)據(jù)驅(qū)動的任務(wù)依賴、調(diào)用方不關(guān)注處理結(jié)果、關(guān)注結(jié)果的長時間調(diào)回等場景下使用。
數(shù)據(jù)驅(qū)動的任務(wù)依賴。大部分公司都有 BI、數(shù)據(jù)部門,每天都會跑一些日志、數(shù)據(jù)庫,多個任務(wù)之間往往存在依賴關(guān)系,任務(wù)1先執(zhí)行,依次是任務(wù) 2、任務(wù) 3 輸入,最終得到結(jié)果。在沒有消息總線之前,大多公司和58到家的做法雷同,就是人工排班表。
人工排班表的弊端如下:
-
原本執(zhí)行時間是40分鐘,但為保險,每個人都會多加時間,導(dǎo)致任務(wù)總執(zhí)行時間延長。
-
萬一某一任務(wù)的執(zhí)行時間超過預(yù)留時間,接下來的任務(wù)不知情,會導(dǎo)致整個業(yè)務(wù)失敗。
-
多個業(yè)務(wù)之間可能有多重依賴,特別是在數(shù)據(jù)統(tǒng)計、數(shù)據(jù)分析過程中,一些核心腳本執(zhí)行完,后面一系列腳本才能執(zhí)行。
如下圖,這種數(shù)據(jù)驅(qū)動的任務(wù)依賴非常適合使用MQ解耦。
-
task1準時開始,結(jié)束后發(fā)一個“task1 done”的消息
-
task2訂閱“task1 done”的消息,收到消息后第一時間啟動執(zhí)行,結(jié)束后發(fā)一個“task2 done”的消息
-
task3同理
采用 MQ 的優(yōu)點是:
-
不需要預(yù)留 buffer,上游任務(wù)執(zhí)行完,下游任務(wù)總會在第一時間被執(zhí)行
-
依賴多個任務(wù),被多個任務(wù)依賴都很好處理,只需要訂閱相關(guān)消息即可
-
有任務(wù)執(zhí)行時間變化,下游任務(wù)都不需要調(diào)整執(zhí)行時間
需要特別說明的是,MQ 只用來傳遞上游任務(wù)執(zhí)行完成的消息,并不用于傳遞真正的輸入輸出數(shù)據(jù)。
調(diào)用方不關(guān)注處理結(jié)果,這樣的情況也適合消息總線來做解耦。舉例,58 同城的很多下游需要關(guān)注“用戶發(fā)布帖子”這個事件,比如招聘用戶發(fā)布帖子后,招聘業(yè)務(wù)要獎勵 58 豆;房產(chǎn)用戶發(fā)布帖子后,房產(chǎn)業(yè)務(wù)要送 2 個置頂;二手用戶發(fā)布帖子后,二手業(yè)務(wù)要修改用戶統(tǒng)計數(shù)據(jù)。
對于這類需求,常見的實現(xiàn)方式是使用調(diào)用關(guān)系:帖子發(fā)布服務(wù)執(zhí)行完成之后,調(diào)用下游招聘業(yè)務(wù)、房產(chǎn)業(yè)務(wù)、二手業(yè)務(wù),來完成消息的通知,但事實上,這個通知是否正常、正確的執(zhí)行,帖子發(fā)布服務(wù)根本不關(guān)注。
這種方法的痛點是:
-
帖子發(fā)布流程的執(zhí)行時間增加了
-
下游服務(wù)宕機,可能導(dǎo)致帖子發(fā)布服務(wù)受影響,上下游邏輯+物理依賴嚴重
-
每當(dāng)增加一個需要知道“帖子發(fā)布成功”信息的下游,修改代碼的是帖子發(fā)布服務(wù),這一點是最惡心的,屬于架構(gòu)設(shè)計中典型的依賴倒轉(zhuǎn),誰用過誰痛誰知道(采用此法的請評論留言)
采用下圖的優(yōu)化方案:MQ解耦
-
帖子發(fā)布成功后,向MQ發(fā)一個消息
-
哪個下游關(guān)注“帖子發(fā)布成功”的消息,主動去MQ訂閱
采用 MQ 的優(yōu)點是:
-
上游執(zhí)行時間短
-
上下游邏輯+物理解耦,除了與 MQ 有物理連接,模塊之間都不相互依賴
-
新增一個下游消息關(guān)注方,上游不需要修改任何代碼
上游關(guān)注執(zhí)行結(jié)果,但執(zhí)行時間很長。有時候上游需要關(guān)注執(zhí)行結(jié)果,但執(zhí)行結(jié)果時間很長(典型的是調(diào)用離線處理,或者跨公網(wǎng)調(diào)用),也經(jīng)常使用回調(diào)網(wǎng)關(guān)+MQ來解耦。
舉例:微信支付,跨公網(wǎng)調(diào)用微信的接口,執(zhí)行時間會比較長,但調(diào)用方又非常關(guān)注執(zhí)行結(jié)果,此時一般怎么玩呢?
一般采用“回調(diào)網(wǎng)關(guān)+MQ”方案來解耦,新增任何對微信支付的調(diào)用,都不需要修改代碼。
-
調(diào)用方直接跨公網(wǎng)調(diào)用微信接口
-
微信返回調(diào)用成功,此時并不代表返回成功
-
微信執(zhí)行完成后,回調(diào)統(tǒng)一網(wǎng)關(guān)
-
網(wǎng)關(guān)將返回結(jié)果通知 MQ
-
請求方收到結(jié)果通知
這里需要注意的是,不應(yīng)該由回調(diào)網(wǎng)關(guān)來調(diào)用上游來通知結(jié)果,如果是這樣的話,每次新增調(diào)用方,回調(diào)網(wǎng)關(guān)都需要修改代碼,仍然會反向依賴,使用回調(diào)網(wǎng)關(guān)+ MQ 的方案。
綜上所述,兩個解耦利器的最佳實踐場景如下:
-
配置中心是邏輯解耦,物理不解耦的微服務(wù)的利器。它可以解決配置導(dǎo)致的系統(tǒng)耦合,架構(gòu)反向依賴的問題,配置中心的演進過程,配置私藏到全局配置文件,到配置中心。
-
消息總線是邏輯上解耦,物理上也解耦的微服務(wù)架構(gòu)利器。它非常適合數(shù)據(jù)驅(qū)動的任務(wù)依賴,調(diào)用方不關(guān)注處理結(jié)果,或者調(diào)用方關(guān)注處理結(jié)果,但是回調(diào)的時間很長的場景。不適合調(diào)用方強烈關(guān)注執(zhí)行結(jié)果的場景。
以上內(nèi)容由編輯王雪燕根據(jù)沈劍老師在 WOTA2017 “微服務(wù)架構(gòu)實踐”專場的演講內(nèi)容整理。
沈劍,現(xiàn)任58到家技術(shù)委員會主席,高級技術(shù)總監(jiān),負責(zé)企業(yè)、支付、營銷和客戶關(guān)系等多個后端業(yè)務(wù)部門。本質(zhì),技術(shù)人一枚?;ヂ?lián)網(wǎng)架構(gòu)技術(shù)專家,“架構(gòu)師之路”公眾號作者。曾任百度高級工程師,58同城高級架構(gòu)師,58同城技術(shù)委員會主席,58同城C2C技術(shù)部負責(zé)人。 “架構(gòu)師之路”公眾號
【51CTO原創(chuàng)稿件,合作站點轉(zhuǎn)載請注明原文作者和出處為51CTO.com】