踩坑實(shí)踐:如何消除微服務(wù)架構(gòu)中的系統(tǒng)耦合?
原創(chuàng)【51CTO.com原創(chuàng)稿件】微服務(wù)架構(gòu)實(shí)施后,不少通用數(shù)據(jù)訪問會(huì)拆分成服務(wù),通用業(yè)務(wù)也會(huì)拆分成服務(wù),站點(diǎn)與服務(wù)之間的依賴關(guān)系會(huì)變得復(fù)雜,服務(wù)與服務(wù)之間的調(diào)用關(guān)系也會(huì)變得復(fù)雜。
如果水平拆分/垂直拆分得不合理,系統(tǒng)之間會(huì)嚴(yán)重耦合,如何消除微服務(wù)架構(gòu)中的系統(tǒng)耦合?
2018 年 5 月 18 - 19 日,由 51CTO 主辦的全球軟件與運(yùn)維技術(shù)峰會(huì)在北京召開。
在“微服務(wù)架構(gòu)設(shè)計(jì)”分會(huì)場(chǎng),58 速運(yùn) CTO 沈劍帶來了《58 速運(yùn)微服務(wù)架構(gòu)解耦最佳實(shí)踐》的主題分享。
本文將按照如下幾個(gè)方面來展開分享:
- 微服務(wù)之前,系統(tǒng)中存在的耦合問題
- 微服務(wù)架構(gòu),存在什么問題?
- 58 速運(yùn)的微服務(wù)實(shí)踐
- 總結(jié)
相對(duì)于 58 同城,58 速運(yùn)屬于一家初創(chuàng)型公司。在早期,我們使用的是簡(jiǎn)單的三層架構(gòu):
- 最上游是端,包括 PC、H5 和 App。
- 中間是 Web 應(yīng)用。
- 下面是數(shù)據(jù)存儲(chǔ)。
這樣的架構(gòu)能夠適應(yīng) 58 速運(yùn)早期“搶時(shí)間”這一特點(diǎn)的快速發(fā)展模式,同時(shí)也能夠支撐產(chǎn)品的快速迭代。
比如 58 速運(yùn)能夠在接到請(qǐng)求之后的 5 分鐘內(nèi)開車過來,將您的一個(gè)家具搬到某處。
在業(yè)務(wù)上,我們與滴滴的相同之處是:“同城、短途、及時(shí)性”;而區(qū)別則是:滴滴“帶人”、我們“拉貨”。
我們當(dāng)前的業(yè)務(wù)主要分為三大塊:
- 2 C,如:幫助大家搬家,不過客頻次比較低,不屬于我們主要的訂單來源。
- 2 小 B,如:幫助賣五金、建材、衛(wèi)浴等小商戶每天把貨物送到客戶家里,所以頻次比較高。
- 2 大 B,如:幫助 OFO 之類的企業(yè)客戶每天將共享單車從倉(cāng)庫(kù)里運(yùn)到各個(gè)地點(diǎn)。
所以總體來說,我們采用的是一般創(chuàng)業(yè)型公司最常見的架構(gòu),并將業(yè)務(wù)垂直地切分為三塊。
包括:搬家的站點(diǎn)(Web);為小 B 叫“貨的”的站點(diǎn);為大 B“優(yōu)配”的站點(diǎn)。在最底下則是統(tǒng)一的數(shù)據(jù)庫(kù)存儲(chǔ)。
隨著業(yè)務(wù)的持續(xù)發(fā)展,數(shù)據(jù)量的慢慢上升,我們?cè)谥蟮膬伞⑷昱龅搅笋詈系膯栴}。
俗話說:歷史總是驚人的相似,大家可以結(jié)合我下面的介紹,看看是否也遇到過此類問題?
微服務(wù)之前,系統(tǒng)中存在的耦合問題
為啥代碼會(huì) Copy 來 Copy 去?
最早期我們并沒有小 B 類和大 B 類,而只有一個(gè)“貨的”的系統(tǒng)和站點(diǎn)。所有用戶都是統(tǒng)一的,并未做任何類型上的垂直切分,全部的請(qǐng)求也都通過“貨的”的數(shù)據(jù)訪問層,去訪問底層數(shù)據(jù)。
接著,我們發(fā)現(xiàn) C 類的客頻次比較低,因此逐漸增加了“貨的”業(yè)務(wù)、“優(yōu)配”業(yè)務(wù)、“貨的”的站點(diǎn)、“貨的”的數(shù)據(jù)訪問、“優(yōu)配”的站點(diǎn)、“優(yōu)配”的數(shù)據(jù)訪問等。
可見,業(yè)務(wù)就這么一塊、一塊長(zhǎng)出來了。但是代碼可不是真正一行、一行寫出來的。
在早期組織架構(gòu)中,我們只有 5 個(gè)人負(fù)責(zé)“貨的”的前端、后端,直至運(yùn)維的全部。
后來我們?cè)黾恿?3 個(gè)人負(fù)責(zé)“優(yōu)配”業(yè)務(wù),又增加了 10 個(gè)人從事“貨的”業(yè)務(wù)。
可見,早期為了提高效率,幾個(gè)人就這么粗獷地把研發(fā)到測(cè)試全干了。而后期就算有業(yè)務(wù)的新增,我們同樣需要用到之前業(yè)務(wù)中對(duì)于用戶數(shù)據(jù)的“增、刪、查、改”。
而此時(shí),我們的團(tuán)隊(duì)并不會(huì)從頭將代碼重寫一遍,而是從同事那里將以前現(xiàn)成的代碼復(fù)制、粘貼過來,再結(jié)合自己的業(yè)務(wù)特性稍作修改,并保持大部分代碼的一致。
眾所周知,代碼復(fù)制會(huì)存在許多潛在的問題。因此在同一個(gè)模塊、以及同一個(gè)工程里,我們不允許通過復(fù)制、粘貼而產(chǎn)生重復(fù)代碼的函數(shù);而在跨工程、跨業(yè)務(wù)、跨系統(tǒng)時(shí),代碼復(fù)制同樣是被禁止的。
因?yàn)椋绻瓉淼哪翘状a出現(xiàn)了問題,或是在用戶數(shù)據(jù)表需要升級(jí)的時(shí)候,我們會(huì)面臨許多地方需要修改的痛點(diǎn)。這正是跨系統(tǒng)、跨業(yè)務(wù)所帶來的耦合問題。
從架構(gòu)層面來說,通過對(duì)服務(wù)層進(jìn)行抽象,能夠緩解由于業(yè)務(wù)日趨復(fù)雜和重復(fù)代碼的日益增多所帶來的各種隱患。
因此,我們將用于訪問“搬家”、“貨的”、“優(yōu)配”的用戶數(shù)據(jù)的那部分代碼抽象出來,變成一個(gè)通用的 user-service。
就像調(diào)用本地函數(shù)那樣,業(yè)務(wù)方通過一行代碼,傳遞一個(gè) UID 過去,以獲得 UID 的實(shí)例。
而具體如何拼裝 SQL 語句,則被 DAO 層放到了 user-service 的微服務(wù)中,從而向上游屏蔽了底層的 SQL 拼裝過程。
在抽象 Service 的過程中,我們所遵循的原則是:公共的部分下沉,而個(gè)性化的部分則由每個(gè)業(yè)務(wù)線來承擔(dān)。
我們籍此減少了由于代碼的反復(fù)拷貝所導(dǎo)致的耦合問題。可見,微服務(wù)是一種對(duì)于創(chuàng)業(yè)性公司業(yè)務(wù)增長(zhǎng)的潛在解決方案。
為啥總是被迫聯(lián)動(dòng)升級(jí)?
隨著我們數(shù)據(jù)量和訪問量的上漲,系統(tǒng)的不同部分難免會(huì)出現(xiàn)不同的問題,最明顯的就是:讀取吞吐量的增大。
對(duì)于創(chuàng)業(yè)性公司的絕大部分業(yè)務(wù)場(chǎng)景來說,最先出現(xiàn)的都是由于讀多寫少所帶來的數(shù)據(jù)庫(kù)瓶頸問題。
所以一般來說我們不用去修改代碼,而直接將數(shù)據(jù)庫(kù)做出集群,以主從同步、和從多個(gè)服務(wù)器上讀取數(shù)據(jù)的方式來提升讀的性能。
同時(shí)我們也可以增加緩存,以降低數(shù)據(jù)庫(kù)和磁盤 I/O 的壓力。這都是常見的優(yōu)化手段。
在增加了緩存之后,你會(huì)發(fā)現(xiàn)讀取數(shù)據(jù)的流程和訪問數(shù)據(jù)的代碼也會(huì)相繼發(fā)生了變化。
即:從直接訪問數(shù)據(jù)庫(kù)變成了先訪問緩存,如果在緩存里命中、則直接返回;如果未命中、再訪問讀庫(kù)、將數(shù)據(jù)取出后放入緩存中。
與此同時(shí),數(shù)據(jù)的寫入也會(huì)發(fā)生類似的變化。即:從直接操作寫入數(shù)據(jù)庫(kù)變成了需要考慮緩存的一致性,你必須得把緩存淘汰掉,才能修改數(shù)據(jù)庫(kù)內(nèi)容。
由于速運(yùn)有著多塊垂直的業(yè)務(wù)和不同的用戶分類,因此引入緩存的復(fù)雜性會(huì)擴(kuò)散到整個(gè)業(yè)務(wù)線上。
例如:由于用戶的訪問量巨大,我們?cè)黾恿司彺?,那么各個(gè)產(chǎn)品系統(tǒng),包括“搬家”、“貨的”、“優(yōu)配”等流程就需要做相應(yīng)的升級(jí)。
而其中“優(yōu)配”的負(fù)責(zé)人會(huì)覺得:“是因?yàn)榈讓拥膹?fù)雜性擴(kuò)散到我這里,我是被迫進(jìn)行技術(shù)改進(jìn)和升級(jí)的。”
那么隨著數(shù)據(jù)量的增大,我們通過綜合運(yùn)用上述方法,采取了水平切分的方式來優(yōu)化整體架構(gòu)的性能。
例如:我們會(huì)將單個(gè)用戶庫(kù)或用戶表轉(zhuǎn)化為多個(gè)實(shí)例、多個(gè)庫(kù)、多個(gè)表,以降低單實(shí)例、單庫(kù)、單表的數(shù)據(jù)量,從而提升整體的容量。這也是互聯(lián)網(wǎng)架構(gòu)中十分常見的技術(shù)優(yōu)化手段。
在過去單庫(kù)的模式下,你只需要將 SQL 語句發(fā)往該數(shù)據(jù)庫(kù)便可;而變成多個(gè)庫(kù)之后,則會(huì)涉及到集函數(shù)、求最大/最小、Join 等方面。
由于數(shù)據(jù)庫(kù)被水平切分,業(yè)務(wù)側(cè)的代碼需要做相應(yīng)的改動(dòng)。而當(dāng)你有多個(gè)上游的時(shí)候,你會(huì)發(fā)現(xiàn)底層的復(fù)雜性會(huì)迅速擴(kuò)散到所有的上游業(yè)務(wù)方那里。
上述提到的上游業(yè)務(wù)方所必須關(guān)注的緩存復(fù)雜性和切分復(fù)雜性,只是兩個(gè)最典型的例子。
我們 58 同城還曾出現(xiàn)過:底層的存儲(chǔ)引擎由 MySQL 變更為 MongoDB 的情況。
這些底層資源的耦合和復(fù)雜性的變化,都值得上游的所有業(yè)務(wù)方予以關(guān)注。
由此可見,服務(wù)化可以讓上述問題得到緩解。因?yàn)?,它只需要一個(gè)團(tuán)隊(duì)去關(guān)注底層的復(fù)雜性。
如上圖所示在升級(jí)之后,所有的業(yè)務(wù)側(cè)通過 RPC 就像調(diào)用本地函數(shù)一樣去獲取遠(yuǎn)端的數(shù)據(jù),只要傳一個(gè) UID 過去便能獲取一個(gè)用戶的實(shí)體。
具體這些數(shù)據(jù)是放在哪個(gè)分庫(kù)中(是放在緩存中、MySQL 中、還是 MongoDB 中),只需被服務(wù)層所關(guān)注。
而當(dāng)?shù)讓有枰?jí)的時(shí)候,所有的調(diào)用方,乃至所有的業(yè)務(wù)線都不會(huì)被牽動(dòng),我們只需對(duì)服務(wù)進(jìn)行升級(jí)。可見,通過服務(wù)化,我們很好地解決了底層復(fù)雜性的耦合問題。
兄弟部分上線,為啥我們掛了?
在服務(wù)化之前,多個(gè)業(yè)務(wù)線會(huì)同時(shí)訪問同一份數(shù)據(jù),以前面的用戶數(shù)據(jù)為例。
雖然我們的每個(gè)業(yè)務(wù)線都能夠通過由 DAO 拼裝的 SQL 語句去訪問同一個(gè)數(shù)據(jù)層(當(dāng)然也有些公司甚至都沒有 DAO 層,而直接拼裝 SQL 語句去訪問數(shù)據(jù)庫(kù)),但是每個(gè)業(yè)務(wù)線上工程師的能力是不一樣的。
較資深的工程師在拼裝 SQL 的過程中,會(huì)考慮到索引以及優(yōu)化等問題;但是一些經(jīng)驗(yàn)欠佳的工程師在寫下一行 DAO 代碼的時(shí)候,可能不曾想到它所被轉(zhuǎn)化的 SQL 語句。
還可能因?yàn)闆]有命中索引,而導(dǎo)致數(shù)據(jù)庫(kù)的全盤掃描,進(jìn)而出現(xiàn) CPU 的利用率達(dá)到百分之百的問題。
過去,我們“搬家”的業(yè)務(wù)線曾寫了一個(gè)非常低效的 SQL 語句并發(fā)布到了線上。
它直接導(dǎo)致了整個(gè)數(shù)據(jù)庫(kù)實(shí)例的 CPU 利用率高達(dá)百分之百,進(jìn)而影響到了“貨的”和“優(yōu)配”。
而由于“搬家”的訂單量遠(yuǎn)小于“貨的”和“優(yōu)配”的訂單量,那么“貨的”一旦訪問訂單的時(shí)候,就會(huì)發(fā)現(xiàn)系統(tǒng)是訪問不了的。
這就造成了:“搬家”的上線卻導(dǎo)致“貨的”“掛掉”了的局面。究其原因,正是因?yàn)樵摷軜?gòu)中 SQL 語句的質(zhì)量沒有得到很好的控制。
另外,我們也需要遵從 SQL、Java 等方面的編程規(guī)范。我在負(fù)責(zé) DBA 部門的時(shí)候,就曾要求:無論什么規(guī)范,都必須限定在十條以內(nèi),以合適一張 A4 紙單面打印出來。
在做了服務(wù)化之后,服務(wù)層應(yīng)能夠向上游業(yè)務(wù)提供一些相對(duì)比較通用的 RPC 訪問,我們籍此可以通過服務(wù)層來控制 SQL 的質(zhì)量。
這里同樣以用戶數(shù)據(jù)的“增、刪、查、改”為例,在用戶側(cè)訪問時(shí),如果你傳來用戶名/密碼,我就回傳 UID;如果你傳來一個(gè) UID,我就給你一個(gè)用戶的實(shí)例。
可見,這些接口都是非常有限且通用的。它們對(duì)于數(shù)據(jù)庫(kù)的訪問,都被控制在 Service 上,而非用戶層面。所以我總結(jié)出來服務(wù)化具有如下原則:
數(shù)據(jù)庫(kù)私有
任何上游不得繞過 Service 去訪問底層數(shù)據(jù)庫(kù)。業(yè)務(wù)層只能調(diào)用接口,即 SQL 由服務(wù)所決定,這一點(diǎn)很重要。
對(duì)上游提供有限且通用的接口
許多公司雖然做了服務(wù)化,但是服務(wù)層仍然有許多個(gè)性化的、與業(yè)務(wù)緊密相關(guān)的接口,這就沒有達(dá)到服務(wù)化的目的。
例如:我們?cè)?jīng)在 user-service 里,有著大量與“搬家”、“貨的”、“優(yōu)配”相關(guān)的業(yè)務(wù)代碼,一旦上游出現(xiàn)新的需求,他就提交給服務(wù)層去修改。
這樣的話,user-service 實(shí)際上實(shí)現(xiàn)的是各種個(gè)性化的需求,由于這些接口的復(fù)用性低,因此不但會(huì)導(dǎo)致其代碼的混亂,還會(huì)造成研發(fā)的瓶頸。可見,服務(wù)化只應(yīng)該提供有限的通用接口。
服務(wù)側(cè)要保證無限的性能
我們通過水平擴(kuò)展、加緩存、分表等方式去解決各種并發(fā)量、吞吐量、和數(shù)據(jù)量的問題,從而保證了上游側(cè)不必關(guān)心各種操作的實(shí)現(xiàn)細(xì)節(jié)。這就是服務(wù)維護(hù)者對(duì)外的一種服務(wù)承諾。
業(yè)務(wù)一旦出了問題只會(huì)影響到自己;如果服務(wù)出現(xiàn)了故障,那么就會(huì)有深遠(yuǎn)的影響,甚至?xí)?dǎo)致用戶無法登錄。
可見,諸如用戶 Service、訂單 Service、支付 Service、商家 Service,都必須具有良好的穩(wěn)定性。
我們?cè)?jīng)在“同城”做過的一個(gè)實(shí)踐是:將公司最基礎(chǔ)的 Service 放置在架構(gòu)部,由資深的工程師去做維護(hù)。
數(shù)據(jù)庫(kù)拆分真的容易?
在最早期,由于 58 速運(yùn)的數(shù)據(jù)量較小,我們只用一個(gè)庫(kù)將所有表格包含其中。
這些表中既有如用戶表這樣的公共表格,也有一些業(yè)務(wù)個(gè)性化的表格,例如與“搬家”相關(guān)的一些用戶信息。
公共表以 UID 為 Key 放置用戶公布的屬性;個(gè)性化表同樣以 UID 為 Key,包括“搬家”用戶個(gè)性化屬性。那么,“搬家”的某些業(yè)務(wù)場(chǎng)景可能會(huì)同時(shí)提取公共的和個(gè)性化的數(shù)據(jù)。
由于只有一個(gè)庫(kù)、一個(gè)實(shí)例,我們通過簡(jiǎn)單代碼直接根據(jù)相同的 UID、運(yùn)用 Join 去操作兩張表,便可取出所有需要的數(shù)據(jù)。即使用到對(duì)于 UID 的索引,也不會(huì)有多次的交互,或出現(xiàn)性能的問題。
當(dāng)然,這些都是基于兩張表必須在同一個(gè)實(shí)例中的前提條件。同理,我們的“貨的”、“優(yōu)配”也是這么各自構(gòu)建的。
另外,除了 Join,還有各種子查詢、自制定函數(shù)、視圖、觸發(fā)器,都可能出現(xiàn)耦合在一個(gè)實(shí)例的情況。因此,我們很難將這種結(jié)構(gòu)拆成多個(gè)實(shí)例。
那么當(dāng)業(yè)務(wù)越來越復(fù)雜、數(shù)據(jù)量越來越大、數(shù)據(jù)庫(kù)里的數(shù)據(jù)表越來越多時(shí),我們勢(shì)必要消除數(shù)據(jù)庫(kù)的耦合,通過微服務(wù)架構(gòu)的改造來拆分出多個(gè)實(shí)例。
如圖所示,最上方是原始的耦合,我們?cè)谙旅娉橄蟪鰜砉残缘臄?shù)據(jù),包括 user-service 和 db-user(一個(gè)單獨(dú)的實(shí)例)。
對(duì)于個(gè)性化的數(shù)據(jù),我們也要拆到個(gè)性化的庫(kù)里。如果你要進(jìn)一步拆分的話,我們還能對(duì)共性的數(shù)據(jù)以及個(gè)性的數(shù)據(jù)分別抽象成 Service。
如圖所示,“搬家”、“貨的”、“優(yōu)配”都分別有自己的 Service,和各自的數(shù)據(jù)庫(kù),從而實(shí)現(xiàn)了將業(yè)務(wù)整體數(shù)據(jù)拆到了多個(gè)單實(shí)例中。
我們的拆分目標(biāo)是:實(shí)現(xiàn)數(shù)據(jù)請(qǐng)求需要根據(jù) UID 訪問 RPC 接口,并基于 user-service 先拿到共性數(shù)據(jù)。
如果你只是抽象了數(shù)據(jù)庫(kù),那么需要用 UID 去拼裝 SQL 以拿個(gè)性的數(shù)據(jù);如果你也抽象了業(yè)務(wù) Service,那么就通過 UID 自己做邏輯拼裝,產(chǎn)生完整的 SQL 語句,去訪問業(yè)務(wù) Service 的接口,從而得到業(yè)務(wù)個(gè)性化的數(shù)據(jù)。
這是一個(gè)循序漸進(jìn)的過程,我們耗時(shí)三個(gè)季度,對(duì)站點(diǎn)應(yīng)用層的代碼做了大量的修改工作。
完成之后,我們實(shí)現(xiàn)了:根據(jù) DBA 新增的設(shè)備臺(tái)數(shù)和新的實(shí)例,將數(shù)據(jù)拆出來并遷移過去。
由上可見,兩層變?nèi)龑拥募軜?gòu)給我們帶來了四點(diǎn)好處:
- 加強(qiáng)了復(fù)用性
- 屏蔽了復(fù)雜性
- 保證了 SQL 質(zhì)量
- 確保了擴(kuò)展性
而且調(diào)用方不再需要關(guān)注 JDBC、DAO 和緩存,只需傳送 UID 便可。
微服務(wù)架構(gòu),存在什么問題?
眾所周知,各種技術(shù)大會(huì)一般都只講服務(wù)化和微服務(wù)的好處,幾乎不會(huì)提及坑點(diǎn)。
而大家也不要盲目地評(píng)判諸如 Dubbo 等微服務(wù)框架的優(yōu)劣,更不要以為引入了 RPC 框架,就實(shí)現(xiàn)了服務(wù)化。
我們通過親自實(shí)踐,在經(jīng)歷了改造、消除了耦合、演進(jìn)了架構(gòu)的過程中,也遇到過如下的問題:
微服務(wù)會(huì)帶來系統(tǒng)復(fù)雜性的上升
即:原來由數(shù)據(jù)庫(kù)單點(diǎn)做緩存,改造后會(huì)增加多個(gè)服務(wù)層。
層次依賴關(guān)系會(huì)變得非常復(fù)雜
即:原來是 Nginx/站點(diǎn)/數(shù)據(jù)庫(kù)的模式,改造后引入了多個(gè)相互依賴的服務(wù),包括數(shù)據(jù)庫(kù)與緩存。
而且服務(wù)還可能會(huì)再次調(diào)用其他的服務(wù),例如:我們的“同城”,它在業(yè)務(wù)上就像一個(gè)包含了各種帖子的論壇,一般由商業(yè)置頂推薦部分、付費(fèi)部分、中間自然搜索部分、下面人工部分、以及右側(cè)的個(gè)人中心所組成。
這些數(shù)據(jù)的展示,需要先訪問商業(yè)服務(wù)進(jìn)行搜索、獲得搜索數(shù)據(jù)后,再推薦服務(wù),以及調(diào)用個(gè)性化的數(shù)據(jù),最后拼裝成一個(gè)列表頁(yè)面。
這些代碼在各個(gè)業(yè)務(wù)線上都有重復(fù)。而如果商業(yè)的結(jié)構(gòu)需要升級(jí),則所有的業(yè)務(wù)線接口都予以跟進(jìn);如果推薦部分出現(xiàn)了 Bug,那么所有都要跟著修改。
因此我們把相同的公共部分抽象為通用列表的服務(wù),由它來統(tǒng)一調(diào)用底層的商業(yè)服務(wù)、自然搜索服務(wù)、推進(jìn)服務(wù)和個(gè)人服務(wù)。
隨著業(yè)務(wù)邏輯的日趨復(fù)雜,我們的服務(wù)層次也會(huì)增多,而服務(wù)的抽象和相互之間的依賴關(guān)系也勢(shì)必日漸復(fù)雜。
監(jiān)控和運(yùn)維部署也會(huì)變得復(fù)雜
例如:在一個(gè)站點(diǎn)上集群了三個(gè)節(jié)點(diǎn)的時(shí)候,我們?cè)谠缙诓]有專門地去做運(yùn)維,而是首先 SSH 到第一臺(tái)→wget 一個(gè) war 包→解壓→restart。然后同法炮制第二臺(tái)、第三臺(tái)。
那么當(dāng)站點(diǎn)有十個(gè)以上時(shí),運(yùn)維就不能這么做了。因此從長(zhǎng)遠(yuǎn)來看,我們需要開發(fā)自動(dòng)化的運(yùn)維腳本和運(yùn)維平臺(tái)。
那么在引入服務(wù)化之后,隨著服務(wù)與集群數(shù)量的增加,運(yùn)維部署與監(jiān)控的工作量也勢(shì)必會(huì)有所增加。
定位問題更麻煩
例如:當(dāng)用戶反饋登錄緩慢時(shí),負(fù)責(zé) Web 登錄的人員通過排查發(fā)現(xiàn)是列表服務(wù)的問題,就轉(zhuǎn)給其列表服務(wù)人員。
列表服務(wù)的人員經(jīng)查發(fā)現(xiàn)是調(diào)用不出用戶中心了→則由負(fù)責(zé)用戶中心的工程師進(jìn)一步調(diào)查→他們上升到 DBA 那里→DBA 通過運(yùn)維人員才發(fā)現(xiàn)是阿里云上的某個(gè)節(jié)點(diǎn)出了問題。
最終認(rèn)定問題不大,只需重啟或摘除掉該節(jié)點(diǎn),以及修改網(wǎng)絡(luò)配置便可恢復(fù)。可見這樣的定位過程是極其復(fù)雜的。
綜上所述,微服務(wù)也會(huì)給我們帶來一些潛在問題,因此大家要事先考慮周全。
58 速運(yùn)的微服務(wù)實(shí)踐
我們通過實(shí)踐形成了一套技術(shù)體系,從而更快、更好地支持了自己的微服務(wù)架構(gòu):
統(tǒng)一的服務(wù)框架
我的建議是:要在一開始就定下整體統(tǒng)一的基礎(chǔ)體系,通過統(tǒng)一語言、統(tǒng)一框架,來減少重復(fù)開發(fā)。
例如 58 同城很早就統(tǒng)一了自研的框架,盡管初期并不太好用,但是隨著時(shí)間的推移,它被慢慢地改善且好用起來。
統(tǒng)一數(shù)據(jù)訪問層
如果有的團(tuán)隊(duì)用 JDBC,有的用 DAO,這樣重復(fù)的成本會(huì)很高,因此一定要事先達(dá)成共識(shí)。
配置中心
早期各個(gè) user-service 的 IP 地址都被寫在配置文件里,那么一旦服務(wù)需要擴(kuò)容出一個(gè)節(jié)點(diǎn),就需要找到所有調(diào)用它的上游調(diào)用方,告知 IP 地址的變更,調(diào)用方再各自經(jīng)歷復(fù)雜的修改,并配以必要的重啟。
而如果我們使用的是配置中心的話,則可以通過簡(jiǎn)單配置,以平臺(tái)發(fā)通知的方式,告知 IP 的變更,進(jìn)而所有調(diào)用方的流量都會(huì)被遷移到新的節(jié)點(diǎn)之上。
服務(wù)治理
包括:服務(wù)發(fā)現(xiàn)與限流等一系列的問題。例如:某個(gè)上游的調(diào)用方寫了一個(gè)帶有 Bug 的死循環(huán),導(dǎo)致將下游所有的調(diào)用次數(shù)都占滿了。
那么我們可以運(yùn)用服務(wù)質(zhì)量的治理,根據(jù)調(diào)用方的峰值來進(jìn)行配額和限流。
如此,就算出現(xiàn)了死循環(huán),它只會(huì)把自己的配額用光,而不影響到其他的業(yè)務(wù)線。
可見服務(wù)質(zhì)量的管理對(duì)于服務(wù)本身的快速擴(kuò)/縮容,以及遇到問題時(shí)的降級(jí),都是非常有用的。
統(tǒng)一監(jiān)控
為了實(shí)現(xiàn)統(tǒng)一的服務(wù)框架和數(shù)據(jù)訪問層,我們可以在框架層的請(qǐng)求出入口、在 DAO 的層面上、訪問數(shù)據(jù)庫(kù)的前/后、訪問緩存、以及訪問 Redis 的 MemoryCacheClient 時(shí)簡(jiǎn)單包裝一層。
從而 hook 這些節(jié)點(diǎn),快速地監(jiān)控到所有的接口、數(shù)據(jù)庫(kù)的訪問、緩存訪問的時(shí)間??梢娫诳蚣軐用嫔?,所有的接口都能夠被統(tǒng)一監(jiān)控到。
統(tǒng)一調(diào)用鏈分析
由于微服務(wù)化之后,層次關(guān)系變得復(fù)雜,因此我們需要具有一個(gè)調(diào)用關(guān)系的視圖。
如果出現(xiàn)某個(gè)請(qǐng)求的超時(shí),我們就能迅速定位到是網(wǎng)絡(luò)、是數(shù)據(jù)庫(kù)、還是節(jié)點(diǎn)的問題。
自動(dòng)化運(yùn)維平臺(tái)
通過調(diào)節(jié)服務(wù)的上限與擴(kuò)容等操作,讓服務(wù)化給技術(shù)體系帶來更大的便利。
總結(jié)
微服務(wù)解決了:代碼拷貝的耦合,底層復(fù)雜性擴(kuò)散的耦合,SQL 質(zhì)量不可控,以及 DB 實(shí)例無法擴(kuò)容的耦合問題。
同時(shí),微服務(wù)帶來的問題有:系統(tǒng)復(fù)雜性的上升,層次間依賴關(guān)系變得復(fù)雜,運(yùn)維、部署更麻煩,監(jiān)控變得更復(fù)雜,定位問題也更麻煩等。
因此服務(wù)化并不是簡(jiǎn)單引入一個(gè)RPC框架,而是需要一系列的技術(shù)體系來做支撐。
我們需要通過建立該技術(shù)體系,以解決如下可能面對(duì)的問題:
- 統(tǒng)一服務(wù)框架和數(shù)據(jù)訪問層(包括:數(shù)據(jù)庫(kù)的統(tǒng)一訪問、緩存、Redis 的 MemoryCache 等)
- 配置中心和服務(wù)治理
- 統(tǒng)一的監(jiān)控
- 調(diào)用鏈
- 自動(dòng)化運(yùn)維平臺(tái)
互聯(lián)網(wǎng)架構(gòu)技術(shù)專家,“架構(gòu)師之路”公眾號(hào)作者。曾任百度高級(jí)工程師,58同城高級(jí)架構(gòu)師,58 同城技術(shù)委員會(huì)主席。2015 年調(diào)至 58 到家任高級(jí)總監(jiān),技術(shù)委員會(huì)主席,負(fù)責(zé)基礎(chǔ)架構(gòu),技術(shù)平臺(tái),運(yùn)維安全,信息系統(tǒng)等后端技術(shù)體系搭建。2017 年調(diào)至 58 速運(yùn)任 CTO,負(fù)責(zé) 58 速運(yùn)技術(shù)體系的搭建。
【51CTO原創(chuàng)稿件,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文作者和出處為51CTO.com】