顛覆微服務認知:深入思考微服務的七個主流觀點

發表於2019-08-14
原文地址:樑桂釗的部落格

部落格地址:http://blog.720ui.com

歡迎關注公眾號:「服務端思維」。一群同頻者,一起成長,一起精進,打破認知的侷限性。

一、逃離單體系統,擁抱微服務?

單體系統和微服務的區別在於,一個單體系統是一個大而全的功能集合,每個伺服器執行的是這個應用的完整服務。而微服務是獨立自治的功能模組,它是生態系統中的一部分,和其他微服務是共生關係。現在,業界對單體系統和微服務的普遍觀點是:單體系統非常容易開發、測試、部署,但是單體系統面對的問題也很多,例如開發效率變低、維護成本增加、部署影響變大、可擴充套件性較差、技術選型成本高,而引入了微服務可以實現每個微服務易於開發與維護,便於溝通與協作,很適合小團隊敏捷開發與持續交付;每個微服務職責單一,高內聚、低耦合。同時,每個微服務能夠獨立開發、獨立執行、獨立部署;每個微服務之間是獨立的,如果某個服務部署或者當機,只會影響到當前服務,而不會對整個業務系統產生影響;每個微服務可以隨著系統規模的不斷擴大,面對海量使用者和高併發,獨立做水平擴充套件與垂直擴充套件;每個微服務可以使用不同的程式語言以及不同的儲存技術,使得我們更容易嘗試新的技術。此外,對單個服務進行業務重構,也不會面臨很大的業務負擔與技術債券。

image.png

筆者對微服務系統的觀點是,我們從單體系統向微服務系統改造的過程中,需要認真思考什麼階段使用微服務。微服務不是銀彈,它對於設計和運維難度提出了更高的要求,同時也帶來了一些技術的複雜度。因此,我們需要思考與解決分散式的複雜性、資料的一致性、服務的管理與運維、服務的自動化部署等解決方案。事實上,微服務通過拆分單體系統使其成為多個體積更小的服務來降低單個服務的複雜性,但是,我們從整體來看,這種方式有造成了存在大量的服務,而服務之間的相互呼叫也會增多,從而導致整個系統架構變得更加複雜。

我們經常忽視業務價值和成本考量,而太過追求技術,那麼可能會導致我們精心設計的分散式架構嚴重影響我們開發的速度和業務的快速迭代,並且隨著業務的不確定性往往導致我們的架構在半年到一年之內就已經不完全適用,推倒重來。此外,如果業務沒有發展起來也會導致前期大量的伺服器資源盲目的浪費了,這對於初創業務得不償失。因此,我們在專案前期為了保證快速增長業務,保證系統儘量減少依賴且獨立完整,減低引入微服務架構後的技術複雜度,例如它對於運維難度提出了更高的要求,因為好的微服務架構需要穩定的基礎設施。隨著業務發展良好,系統規模會不斷擴大,它的擴充套件性、伸縮性、可用性和效能都限制了我們的業務發展,此時,我們懷著明確的業務思考和投入更多的資源再來考慮微服務改造。

微服務架構使用服務作為模組化的單元,那麼,我們可以在前期設計的時候通過 Maven 的 module 模組化來初步隔離依賴,為我們之後的改造預留空間。注意的是,微服務在生態系統中是共生關係。這裡,不僅僅侷限在它們可能存在鏈路依賴,同時它們的業務價值一定是共生的。因此,後期識別出單體系統的核心價值、關鍵功能,再把這些功能拆分成獨立且自完整的模組。這裡,改造方案可以閱讀筆者的《高可用可伸縮微服務架構:基於Dubbo、Spring Cloud和Service Mesh》一書的第十二章 “遺留系統的微服務架構改造”。

總結一下,微服務通過拆分單體系統使其成為多個體積更小的服務來降低單個服務的複雜性,但是,我們從整體來看,這種方式有造成了存在大量的服務,而服務之間的相互呼叫也會增多,從而導致整個系統架構變得更加複雜。因此,我們不單單隻關注技術,而需要考量投入產出比,保障當前階段的利益最大化。

二、擺脫單體系統就遠離大泥球?

單體系統讓很多人詬病的是其服務內聚混亂,看起來就像一個大泥球。那麼,服務化之後,就解決了這個問題了嗎?事實上,微服務通過拆分單體系統使其成為多個體積更小的服務來降低單個服務的複雜性,讓單個系統看起來更加的職能清晰,但是,整個系統架構變得更加複雜。事實上,生產環境的多服務之間的呼叫可能如圖所示的場景。
image.png
通常情況下,生產環境的微服務生態比上面的案例複雜的多,可能存在幾十個到幾百個的服務。那麼,對於我們而言,如何系統地梳理服務之間的依賴關係和鏈路關係就顯得非常重要。尤其在大促的時候,需要對於核心鏈路進行強保障,這個工作就顯得更加重要了。對此,我推薦通過 APM 的流量採集實現自動化鏈路梳理。

此外,我們在設計架構,每當有服務粒度的劃分問題,例如新專案的建立,或者對於服務邊界模凌兩可的時候,我們需要對服務邊界討論清楚,儘可能讓我們的服務保持內聚性。

三、遷移微服務能提升系統健壯性嗎?

這裡,還有一個主流的觀點:一個單體系統是一個大而全的功能集合,如果某個服務出現故障,會對整個業務系統產生影響,然而使用微服務可以實現如果某個服務部署或者當機,只會影響到當前服務,而不會影響到整個業務系統。

事實上,這個觀點看起來非常正確,但是在真實的業務場景下,並不是推動我們改造的關鍵原因。首先,一個單體為了避免單點故障,肯定需要叢集和負載均衡,注意的是,叢集和負載均衡和微服務(服務垂直拆分)不是互斥關係,而是在高併發和分散式中的共存關係。此外,為了解決服務部署,我們可以考慮通過滾動釋出來實現服務的無中斷。所以,單體系統不一定就是不健壯的。同時,引入了微服務之後,從整體來看,這種方式有造成了存在大量的服務,而服務之間的相互呼叫也會增多,從而導致整個系統架構變得更加複雜,某個鏈路上的某個節點出現故障的機率就大大的增加了,更多的依賴也意味著發生更多問題的可能。此時,假設其中某條呼叫鏈路上某個微服務當機而無法提供服務,那麼對其強依賴的上游服務,如何保障其自身可用性?(我在這裡特指強依賴呼叫,服務降級或者熔斷機制可能會對業務有損,並不是解決的有效方案。)因此,很多場景下,某個服務當機也可能會影響到整個業務系統。

因此,如果我們沒有面向失敗設計,並且構建一套服務治理的體系,反而會導致整體服務的不健壯性。

四、遷移微服務能提升系統的效能嗎?

那麼,什麼才是遷移微服務的主要動因了?我覺得最主要的利益動因是服務的可擴充套件性和提升效能方面的考量。一個服務因為宿主機器的限制可能達到了瓶頸,為了更加進一步的壓榨機器的資源,拆分服務是一個好主意。同時,我們還可以進一步實現自動縮擴容來調整機器的使用。但是,遷移微服務一定能提升系統的效能嗎?我的觀點是真不一定。服務化後,呼叫鏈路變長,原本的一次 RPC 通訊可能變成幾次,效能損耗有所增加。例如,異地多活主要挑戰之一就是網路時延,跨城市一定會有延時的問題,假設跨地域網路延時可能在一百毫秒以內,一次 HTTP 請求涉及到一兩百次跨城市 RPC 呼叫,那麼整個響應時間會增加很多,所以延時帶來的挑戰非常大。那麼,阿里為了解決資料延遲的問題,最早提出了單元化的解決思路,即將讓請求收斂到同一區域內完成,單元高內聚,不做跨區域訪問,即“單元封閉”。此外,還存在跨服務呼叫的網路超時問題,通過重試也增加了同步阻塞的隱患性。

因此,服務化是犧牲了服務之間鏈路上的呼叫效能,來整體提高整個業務系統對於機器資源壓榨來提升系統的效能。

五、微服務的可用性 = 服務提供的每次呼叫都是可靠且可用的?

對於微服務的可用性,很多人的理解是,服務提供的每次呼叫都是可靠且可用的。這個觀點不太對。事實上,微服務保證其服務的整體可用性。通常情況下,如果服務 A 呼叫服務 B,如果呼叫了 10 秒,那麼後面的情況可能就會阻塞,間接地,導致了執行緒池撐爆,導致服務不可用。因此,我們就會採取超時機制來保障極短的時間內完成結果響應,儘可能不出現同步阻塞問題。
image.png
此外,如果服務 B 出現故障,所有呼叫依賴的服務都將出現阻塞,如果有大量的請求會導致執行緒資源會被消耗完畢,導致服務癱瘓。事實上,服務與服務之間的依賴性會導致級聯傳播,從而間接導致服務故障的“雪崩”效應,造成整個微服務系統不可用。為了解決這個問題,熔斷機制就有了用武之地。

六、微服務中資料庫是相互獨立且透明?

微服務的一個主流觀點是,在每個服務都有自己的快取和資料庫,並且快取和資料庫是相互獨立且透明的。因此,共享快取與共享資料庫是不對的。那如果服務 A 需要獲取服務 B 的資料怎麼辦?一般的做法是,服務 B 提供一個獲取該資料的 API 介面,而服務 A 通過呼叫該介面進行業務組裝。因此,微服務化之後,服務之間的資料交換都是通過介面來開展的,如果服務 A 越過服務 B 的業務邏輯之間訪問服務 B 的資料,其會破壞了微服務之間的資料獨立性。

此時,筆者需要潑一下冷水。凡事無絕對,有幾種特殊的場景可能需要共享資料。其一,那就是舊的服務過渡到新的服務的場景,新的服務複用舊的服務的資料庫從而到達功能與資料過渡的需求。其二,多個服務之間可能依賴於同一個資料來源,例如報表的資料聚合。這種情況下,如果我們單純的依賴於 RPC 的介面呼叫很可能會導致偶發性的呼叫超時,從而導致故障發生的機率更大。那麼,解決這個問題的常用套路就是共享資料,要麼通過資料冗餘的方式進行資料同步,然後基於本地的服務進行邊界內的資料聚合;要麼通過抽離數倉方案進行資料的集中化 ETL,然後再對外通過加工好的資料。其三,更加現實的成本問題。事實上,更多的資料庫會帶來更多的經費成本。很多時候,我們也會從經費成本來考慮問題。我們選擇複用原來的資料庫表,等待業務價值明確之後,再考慮單獨獨立資料庫。

同時,共享資料技術方案可避免資料之間的上下文不明確的情況下代價高昂且重複的資料遷移,並可在需要時更輕鬆地調整服務粒度,然後在服務粒度穩定之後再進行資料遷移。因此,我們要在兩者之間尋求適當的平衡,儘可能遵守微服務的主流觀點,充分利用微服務帶來的好處。

七、組織保障微服務的實施?

微服務對與組織結構提出了新的要求,它建議將大團隊拆分成為多個小團隊,而每個團隊各自運維開發和運維一個或多個服務,並且需要流程上持續交付、持續部署、DevOps。

image.png
不同的服務可能由不同的團隊開發與維護,實際場景下,微服務的便利性更多的在於團隊內部能夠產生閉環,換句話說,團隊內部可以易於開發與維護,便於溝通與協作,但是對於外部團隊就存在很大的溝通成本與協作成本。如圖所示,團隊 A 對於服務 C 的瞭解是一個黑盒,我們不知道它是單體服務還是微服務,它部署了幾臺伺服器,需要依賴哪些下游服務,是否存在限流、熔斷和降級策略,以及如何接入。如果我們需要確認這些問題,通常情況下,都需要人工協作和確認。
image.png

當然,這個是組織分工帶來的不可避免的問題,那麼我們儘可能保證我們自己團隊內部的服務的內聚性,圍繞業務模組進行劃分,保證微服務具有業務的獨立性與完整性,儘可能少的存在服務依賴,鏈式呼叫。這裡,又丟擲了一個新的問題。微服務有多“微”?事實上,對於服務的拆分並非越小越好,甚至極端的案例是把一塊功能拆分成一個服務,這種做法是不對的。因此,拆分粒度應該保證微服務具有業務的獨立性與完整性,服務的拆分圍繞業務模組進行拆分。如果單獨拆分成服務的業務價值/技術價值不明確,那麼就讓它耦合在這個單體系統中,在整個專案的生命週期裡已經足夠了。如果隨著業務的發展與需求,我們可以隨著調整系統原始碼層次上模組結構,並將其拆分成獨立的微服務。

有的時候,團隊對專案具有絕對的所有權,從而因為團隊利益上的考慮而出現生產上的微服務是一個“半成品”。筆者相信這種情況並非個例,而是絕大多數的常態。現在,我們來看一個案例。團隊 A 考慮到功能的複用性而開發了一個“互動元件”,其中包括 “評論模組”功能。此時,團隊 B 並不知情也開發了一個類似的“互動元件”。而團隊 C 也有這個需求,它知道團隊 A 有這個“互動元件”,希望可以複用,但是由於這個“互動元件”在設計的時候更多地考慮了團隊 A 的當前業務,沒有很好的複用性,例如不支援“評論蓋樓”功能,而由於團隊 A 出於當前其他專案的進度原因無法馬上提供支援,團隊 C 評估後決定花一週時間自己開發一個符合自己業務需求的“互動元件”。此時,各個專案團隊各自維護了一個“互動元件”。這個案例中,由於團隊之間的職責與邊界導致了服務的複用存在侷限性,甚至造成各自為戰的局面,這種情況一般需要公司層面進行規劃和統籌。無獨有偶,團隊 A 和團隊 B 都在做工單系統,但是兩者需要融合,為了保證兩個團隊的既有利益,他們並不是將原來的架構打破進行融合,而是在原有的基礎上確定領域邊界。

此外,假設外部一個 RPC 介面不太穩定,一般的做法就是去分析不穩定的原因,但是跨團隊合作的情況下,外部服務可能就是一個黑盒,並且團隊之間可能就是一堵隱形的牆,那麼溝通協作成本是非常大的,此時需要有人來全鏈路牽頭讓大家的利益達成一致。那麼,當下最高效的方式可能就是團隊內部通過其他手段來規避這個問題,例如冗餘快取或者介面適配。因此,它也許就是當前組織結構和環境下業務價值的最大化的較優方案,我們需要適應當下,展望未來。說到介面適配,其實還有一個非常常見的微服務架構設計:介面卡服務模式。通常情況下,外部服務給我們的訊息體格式和我們所需要的不一致,此時,我們去推動他們改造顯得不太現實,那麼,為了保障我們的業務邏輯不引入大量的業務適配邏輯,我們就會引入適配層 (介面卡服務),它將外部服務的訊息體適配成統一的標準格式,然後再向上暴露服務,例如退款適配、物流適配等。

因此,公司組織在很大程度上影響了服務邊界的確定。通常情況下,我們在自身團隊的邊界內做領域劃分,儘可能的滿足業務需求,雖然從技術層面來看這個是一個“半成品”,但是它也許就是當前環境下業務價值的最大化的較優方案。所謂分久必合,當大家看到它有足夠大的價值後,在考慮進一步融合也是一個不錯的主意。

寫在最後的心聲

最後,我們再來談談引入新的技術,給專案帶來技術紅利。一個新的技術需要考慮學習成本和維護成本,以及可用性保障和可運維性。例如,我在公司在運維的護航下,我可以輕鬆自如的使用各種技術等,但是,我不一定敢在另外一個公司使用 MongoDB,因為我知道我並不是這方面的運維專家,如果出現問題,我可能沒辦法解決。那麼,引入一個新技術可能存在的技術風險。很多時候,我們要基於失敗設計,這恰恰是初級工程師和資深工程師之間的差距。例如,Redis 實現分散式鎖,很多人都只想到來如何通過程式碼實現這套邏輯,但是,如果 Redis 叢集中主服務掛了,直接切換到從服務,因為是主從非同步同步,而分散式鎖講究的是一定是最新的鎖資料才管用,就是在一瞬間才起作用,這時候丟了分散式鎖資料,你的業務就會造成重複請求,而分散式鎖如果應用在了業務中,必須是非常重要的場景,尤其是金融和支付,所以單點版 Redis 分散式鎖不是好方法,不能使用,如果要用,就得解決穩定性問題。(引用《高可用可伸縮微服務架構:基於Dubbo、Spring Cloud和Service Mesh》作者「程超」在群裡分享的案例,特別精彩。)這裡,小小的偏題了下,回到正題,我們會經常發現新專案嘗試使用新技術,而老專案更加保守,因為前者試錯成本更低。有趣的是,新公司對技術的發展更加敏銳,例如很多小公司在雲原生方面有諸多實踐與落地。此時,你可能大概明白了我表述的觀點:通常情況下,技術棧的使用背後是公司的運維保障,以及對技術深度的把控力。所以,我們需要對新技術有提前的儲備,以備隨時上戰場,但是絕大多數情況下,我們要保證利用現有的技術(工具)實現業務價值的最大化。

總結一下,本篇文章沉澱了很多我在工作以來的所見所聞和實戰思考,核心觀點並不是唱衰微服務,而是讓大家保持獨立思考,跳出純技術的視角去思考架構,去看待微服務,要保證利用現有的技術(工具)實現業務價值的最大化。

寫在末尾

【服務端思維】:我們一起聊聊服務端核心技術,探討一線網際網路的專案架構與實戰經驗。讓所有孤軍奮戰的研發人員都找到屬於自己的圈子,一起交流、探討。在這裡,我們可以認知升級,連線頂級的技術大牛,連線優秀的思維方式,連線解決問題的最短路徑,連線一切優秀的方法,打破認知的侷限。

更多精彩文章,盡在「服務端思維」!

相關文章