>
> [原文地址](https://vladikk.com/2020/04/09/untangling-microservices/)
>
> 翻譯:時序
>
----------

微服務的蜜月期已經結束了。Uber正在將成千記的微服務重構成一個更加可管理的方案[1];Kelsey Hightower正在預測單體架構將是未來[2]; Sam Newman甚至聲明微服務不應該是第一選擇,而應該是最後一個選擇[3]。
這是怎麼回事?儘管微服務承諾了簡單和靈活,為什麼這麼多的項目變得難以維護?或者難道最終單體架構更好?
在這篇文章中,我想要討論這些問題。你會看到一些將微服務編程一團分佈式大泥球的常見設計問題 - 當然,也會看到如何避免他們。
但最開始,讓我們先了解下什麼是單體架構。
# 單體架構
微服務一直是被認為是單體應用代碼的解決方案。但是單體應用是不是一個問題呢?根據維基百科的定義[4],一個單體應用是自包含且與其他計算應用獨立的。與哪些其他應用獨立呢?這不是我們在設計微服務時追求的嗎?David Heinemeier Hansson[5]指出了單體應用的缺陷。他
因此,微服務不是“修復”單體應用。微服務需要解決的真正問題是交付業務目標的無力。一般,團隊是由於指數級增長的 - 或更糟的不可預測性 - 進行變更的成本才交付不了業務目標的。換一句話說,系統不能滿足業務的需要。不可控的變更成本不是單體應用的特性,而是大泥球的特性[6]:
> 大泥球是雜亂的結構,無序,泥濘,纏在一起的電線和膠帶,麵條代碼的叢林。系統顯示出無節制增長,重複,臨時修復的明顯跡象。系統中混亂的將信息在很多極長鏈路的系統部分中共享,這表示大部分重要信息都變成了全局的或被重複複製的。
對大泥球的複雜性的修改和進化可以由於多個原因引起:協調眾多團隊的工作,非功能性需求的衝突,或一個複雜的業務域。無論怎樣,我們經常試圖將這種複雜問題分解成微服務來解決。
# 微什麼?
文字“微服務”指明瞭服務的一部分可以被度量並且它的價值應該是最小化的。但微服務到底意味著什麼?我們看下一些常見的用法。
## 微團隊
第一個工作在服務上的團隊大小。而這個尺度可以按披薩來度量。你沒聽錯。 他們說如果工作在服務上的團隊可以被2個披薩餵飽, 那麼這就是微服務。 我發現這很有啟發,我曾經做一個項目而團隊可以被一個披薩餵飽... 而我敢對任何人說這團大泥球是微服務。
## 微代碼庫
另一種廣泛使用的方法時基於它的代碼庫來設計微服務。有些人將這個概念發揮到了極致,將服務的大小限制到了某些確定的代碼行數。就是說,可以構成一個微服務的確切代碼行數還沒被找到。當這個軟件架構的聖盃被發現,我們會進入下一個問題 - 構建微服務團建的編輯器寬度是多少?
有個更嚴重的問題,這個方法一個沒那麼極端的版本更流行。代碼庫的大小常被用來決定它是否是一個微服務。
某些時候,這個方法管用。更小的代碼庫,更小的業務域。因此,這容易理解,實現,發展。而且,更小的代碼庫不太可能變成一個大泥球 - 如果發生了,也比較容易重構。
不幸的是,前面提到的簡單只是一個錯覺。當我們開始基於服務本身來評估服務的設計時,我們忽略了系統設計的核心部分。我們忘記了*系統*自己,服務作為系統的*組成*。
> “有很多有用和有啟發性的方法來定義一個服務的邊界。大小是最不重要的部分。” - Nick Tune
>
## 我們開發系統!
我們開發系統,而不是服務的集合。我們使用基於微服務的架構來優化系統的設計,而不是設計獨立的服務。無論別人怎麼說,微服務不能,也永遠不會完全解耦,和獨立。 *你不能打造用完全獨立的組件來打造系統!* 現在我們看下“系統”的定義[7]:
> 1. 一組連接在一起並可一起操作的物件或設備
> 2. 一組為了一個特定目的一起使用的計算機設備或程序
>
服務會與其他服務進行不斷交互來形成系統。如果你通過優化服務來設計一個系統,卻忽略了他們之間的交互,最終你可能是這樣的結局:

這些“微服務”可能自身很簡單,但系統卻變成了複雜性的地獄!
所以我們如何不只是處理了服務的複雜性,而是也考慮了整個系統的複雜性來進行微服務設計呢?
這是個困難的問題,但幸運的是,在很早以前就有答案。
# 系統視角的複雜性
四十年前,還沒有云計算,沒有全球規模的需求,不需要每11.7秒部署一次系統。但工程師仍然需要控制系統複雜度。儘管這些工具與現在不一樣,但挑戰 - 更重要的是, 解決方案 - 都是類似的,也可以被用於基於微服務設計的系統。
在他的書裡,“組合/結構設計”[8],Glenford J. Myers討論瞭如何用結構化的過程代碼來降低複雜度。在書的第一頁,他寫到:
> 關於複雜性的主題中有比簡單的嘗試最小化程序中一部分的本地複雜度更重要的事。一個更重要的複雜度類型是全局複雜度:程序或系統的全局結構的複雜度(比如,程序主要部分的關聯或獨立程度)。
>
在我們的語境裡,*本地複雜度就是每個獨立微服務的複雜度,*而*全局複雜度是整個系統的複雜度*。 本地複雜度以來與一個服務的*實現部分*;全局複雜度是被服務間的*交互和依賴*所定義的。
所以哪一個複雜度更重要 - 本地還是全局?讓我們看看當只有一種複雜度被關心時的情況。
要將全局複雜度降到最小實際非常簡單。我們只要評估下任何系統組件間的交互 - 即,將所有功能在一個單體服務中實現。就像我們早前看到的,這個策略在某些特定場景是有用的。而在其他場景,它會導致恐怖的*大泥球 - 可能是最高級別的本地複雜度。*
從另一方面,我們很清楚當你只優化本地複雜度而忽視系統全局複雜度時會發生什麼 - 更大的*分佈式大泥團。*

因此,當我們只關注複雜度的某一種,選哪一個並不重要。在一個複雜分佈式系統,對向的複雜度都會暴漲。所以,我們不能只優化一個。相反,我們要平衡本地和全局複雜度。
有意思的是,在“組合/結構設計”一書中描述的複雜度平衡不僅與分佈式系統有關,其也提供瞭如何設計微服務的見解。
# 微服務
讓我們先從精確定義什麼是服務和微服務來開始。
## 什麼是服務?
根據OASIS標準 [9](https://docs.oasis-open.org/soa-rm/v1.0/soa-rm.html) ,一個服務是:
> 通過規定好的接口提供能訪問一種或多種能力的機制
規定好的接口這部分很重要。服務的接口定義了它暴露給外界的功能。根據Randy Shoup [10](https://www.youtube.com/watch?v=E8-e-3fRHBw)的說法,服務的公共接口簡單來說就是任何讓數據進出服務的機制。它可以是同步化的,如簡單的請求/響應模型,或者異步化的,一個生產事件一個消費事件。不管怎麼說,同步或異步化,公共接口只代表讓數據進出一個服務。Randy也表達了服務的公共接口就跟**前門**是一樣的。
服務是被公共接口定義的,這個定義對於定義什麼服務是微服務也足夠了。
## 什麼是微服務?
如果一個服務是被它的公共接口定義的,那麼 -
**一個微服務是指一個用了微型公共接口的服務 - 微型前門**
這條在過程式編程中被遵守的規則,如今在分佈式系統領域更加有關聯性。你暴露的服務越小,它的實現越簡單,它的本地複雜性越小。從全局複雜度來看,更小的公共接口會在服務間產生更少的依賴和連接。
微接口的概念也解釋了廣泛使用的微服務不暴露數據庫的實踐。沒有微服務可以訪問另一個微服務的數據庫,只能通過其提供的公共接口。為什麼?因為,數據庫實際是一個巨大的公共接口!只要想想你可以在一個關係數據庫上能執行多少種操作。
因此,再重申下,在分佈式系統中,我們通過將服務的公共接口最小化的方式來平衡局部與全局複雜度,然後服務就變成了微服務。
## 警告
這聽起來很簡單但其實不然。如果一個微服務只是有微型公共接口的服務,那我們可以直接將公共接口限制到只有一個方法。由於這個“前門”已經小的不能再小了,這應該是完美的微服務,對嗎?為了解釋為什麼不這麼做,我會使用我另一篇博文[11](https://vladikk.com/2018/02/28/microservices/)裡的一個例子:
加入我們有如下庫存管理服務:

如果我們將它拆成八個服務,每個只有一個簡單的公共方法,我們可以得到完美的低本地複雜度的服務:

但我們能將它們連入系統來真正管理庫存嗎?並不行。要形成系統,服務需要與其他服務交互並共享對於每個服務的狀態。但它們不行。服務的公共接口不支持。
因此,我們要繼承這個“前門”並讓這些公共方法可以支持服務間的集成:

完了!如果我們通過將每個服務完全獨立的方式來優化複雜度,那麼解耦的是很徹底。但是,當我們將服務連入系統,全局複雜度又升高了。不只是導致系統捲入了一團亂麻;為了集成 - 繼承公共接口也超出了我們原來的意圖。引自Randy Shoup,除了建設了一個小“前門”,我們也建了一個巨大的“員工專用”入口!這告訴我們一個重要的觀點:
**一個服務有比業務方法更多的集成方法有成長為分佈式大泥球的巨大可能!**
因此,一個服務的公共接口可以被最小化到什麼程度不只是依賴於服務本身,也取決(主要)於它在系統中是哪一部分。一個微服務何時的解耦應該同時考慮系統的全局複雜度和服務的局部複雜度。
# 設計服務邊界
> “要找到服務邊界太難了... 完全沒有流程圖!” -Udi Dahan
上面Udi Dahan說的話對於基於微服務的系統來說也很對。設計微服務的邊界很難,基本上第一次很難做對。折讓設計一個合適複雜度的微服務變成了一個迭代流程。
因此,從更大的邊界開始是比較安全的 - 從上下文邊界開始比較合適[12](https://vladikk.com/2018/01/21/bounded-contexts-vs-microservices/) - 有更多關於系統各它的業務域的知識後,再將它們解耦成微服務。這對那些包含了核心業務域的服務特別重要[13](https://vladikk.com/2018/01/26/revisiting-the-basics-of-ddd/)。
# 分佈式系統之外的微服務
儘管微服務只是最近才被“發明”出來,你仍可以在工業界發現很多有同樣設計理念的實現。這些包括:
## 跨功能團隊
我們知道跨功能團隊是最有效的。這種團隊讓不同專業能力的小組工作在同一個任務上。一個有效的跨功能團隊能最大化團隊內的交流,最小化團隊外的交流。
我們的工業只是最近才發現跨功能團隊,但任務組是一直存在的。其底層的原理與基於微服務的系統是一樣的:在團隊內高聚合,團隊間低耦合。團隊的“公共接口”通過需要達成任務的技能來進行最小化(如實現的細節)。
## 微處理
我要通過Vaughn Vernon那經典的[相關主題的博文](https://kalele.io/microservices-and-microservices/)來舉這個例子。在他的博客裡,Vaughn描繪了一個微服務與微處理器間有趣的相似點。他講述了處理器與微處理器間的不同:
> 我發現了一個通過大小規格來幫助確定一個處理器是中央處理器(CPU)還是微處理器:數據總線[21](https://kalele.io/microservices-and-microservices/)的大小
>
微處理器的數據總線就是他的公共接口 - 它定義了可以被傳給微處理器與其他組件間的數據量。對於公共接口有嚴格的尺寸規格來定義這個中央處理器(CPU)是不是一個微處理器。
## Unix哲學
Unix哲學,或Unix方式,是一種秉承了極簡主義的模塊化軟件開發規範和文化。[22](https://en.wikipedia.org/wiki/Unix_philosophy)
有人可能會反駁我Unix哲學與我的情況不符,你不能用完全獨立的組件來組裝一個系統。難道unix程序不是完全獨立,然後形成一個可工作的系統的嗎?事實正相反。Unix方式幾乎字面上定義了程序需要暴露的微交互操作。讓我們看看Unix哲學與微服務相關的部分:
第一條原理讓程序暴露一個與其功能相關的公共接口,而不是與其原始目標不想關的:
> 讓程序只做一件事並做好。要做另一件事,**寫個新的而不是在老程序里加新“特性”**。
>
儘管Unix命令被認為是彼此間完全獨立的,但並不是。它們之間需要通信,並且第二條原則定義了通信的接口如何設計:
> 預期所有程序的輸出會作為其他程序的輸入,儘管可能現在還不知道。**不要讓輸出有不相關的信息**。避免嚴格的列式或二進制輸入格式。不要強制要求交互式命令有輸入。
>
不只是通信接口被嚴格限制(標準輸入,標準輸出,標準錯誤),基於這個原則,在命令間的數據傳輸也被嚴格限制住了。例如,Unix命令需要暴露微-接口並永遠不依賴於其他命令的實現細節。
## 那麼Nano服務呢?
文字nanoservice經常是用來描述一個服務太小了。有人會說上面例子介紹的一個方法的服務就是nano服務。我不同意這個觀點。
nano服務用用來在忽略了整體系統時描述單獨服務時用的。在上面例子中,一旦我們將系統放入方程中,服務的接口就會增長。實際上,當我們比較一下原來的單服務實現與解耦後的實現,我們可以看到一旦將服務連入系統,系統從8個公開接口增長到38個。而且,每個服務公開方法的平均數量從1漲到4.75.
因此,當我們又花了服務(公共接口),數據nano服務不再成立,因為服務被迫開始增長來支持系統的用例。
## 這些夠了嗎?
不。儘管最小化服務的公共接口是一個設計微服務的好原則,它仍然只是一種探索式的方式而不能取代常識。實際上,微接口只是更加基礎,且更復雜的耦合與內聚設計原則的抽象。
比如,如果兩個服務有微-公開接口,它們讓需要在分佈式事務中協調,它們仍是互相高耦合的。
針對微-接口在解決不同類型的耦合,比如函數,開發,語義仍然是有啟發的。但那就是另一篇博客的主題了。
# 從理論到實踐
不幸的是,我們沒有一個客觀方式來量化局部與全局複雜度。從另一方面,我們確實有一些設計方式可以優化分佈式系統的設計。
這篇文章主要的內容就是想告訴你在評估服務的公共接口是你要不停的問自己:
* 業務的佔比是多少 - 給定服務是面向集成的endpoint嗎?
* 這是在業務上不想關的endpoint嗎?在不引入面向集成的endpoint的前提下你可以將它們分離成2個或更多服務嗎?
* 合併兩個服務是否能消除當初為了集成原始服務而產生的endpoint?
可以用這些原則來指導你在服務邊界和接口的設計。
# 概要
我想最後用Eliyahu Goldratt的觀點來總結下。在他的書裡,他經常重複下面這些句子:
> "告訴我你如何度量我,我會告訴你我怎樣表現" - Eliyahu Goldratt
當設計基於微服務的系統時,很重要的就是度量和優化正確的指標。為微服務代碼庫設計邊界,則微小組的定義會更容易。所以,開發一個系統,我們要學會算賬。微服務是用來設計系統的,而不是獨立的服務。
回到這片的標題-“在分佈式系統中解決,或平衡微服務的複雜度”。**解開微服務問題的唯一辦法就是平衡每個服務的局部複雜度與整個系統的全局複雜度**。
# 引用索引
1. [Gergely Orosz’s tweet on Uber](https://twitter.com/GergelyOrosz/status/1247132806041546754)
2. [Monoliths are the future](https://changelog.com/posts/monoliths-are-the-future)
3. [Microservices guru warns devs that trendy architecture shouldn’t be the default for every app, but ‘a last resort’](https://www.theregister.co.uk/2020/03/04/microservices_last_resort/)
4. [Monolithic Application(Wikipedia)](https://en.wikipedia.org/wiki/Monolithic_application)
5. [The Majestic Monolith - DHH](https://m.signalvnoise.com/the-majestic-monolith/)
6. [Big Ball of Mud(Wikipedia)](https://en.wikipedia.org/wiki/Big_ball_of_mud)
7. [Definition of a System](https://dictionary.cambridge.org/dictionary/english/system)
8. [Composite/Structures Design - book by Glenford J. Myers](https://www.amazon.com/Composite-Structured-Design-Glenford-Myers/dp/0442805845)
9.Reference Model for Service Oriented Architecture
10.Managing Data in Microservices - talk by Randy Shoup
11.Tackling Complexity in Microservices
12.Bounded Contexts are NOT Microservices
13.Revisiting the Basics of Domain-Driven Design
14.Implementing Domain-Driven Design - book by Vaughn Vernon
15.Modular Monolith: A Primer - Kamil Grzybek
16.A Design Methodology for Reliable Software Systems - Barbara Liskov
17. Designing Autonomous Teams and Services
18. Emergent Boundaries - a talk by Mathias Verraes
19. Long Sad Story of Microservices - talk by Greg Young
20. Principles of Design - Tim Berners-Lee
21. Microservices and [Micro]services - Vaughn Vernon
22. Unix Philosophy