天天看點

【譯】在分布式系統中解決,或平衡微服務的複雜度單體架構微什麼?系統視角的複雜性微服務設計服務邊界分布式系統之外的微服務從理論到實踐概要引用索引

原文标題: Untangling Microservices, or Balancing Complexity in Distributed Systems 原文位址 翻譯:時序
【譯】在分布式系統中解決,或平衡微服務的複雜度單體架構微什麼?系統視角的複雜性微服務設計服務邊界分布式系統之外的微服務從理論到實踐概要引用索引

微服務的蜜月期已經結束了。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

,一個服務是:

通過規定好的接口提供能通路一種或多種能力的機制

規定好的接口這部分很重要。服務的接口定義了它暴露給外界的功能。根據Randy Shoup

10

的說法,服務的公共接口簡單來說就是任何讓資料進出服務的機制。它可以是同步化的,如簡單的請求/響應模型,或者異步化的,一個生産事件一個消費事件。不管怎麼說,同步或異步化,公共接口隻代表讓資料進出一個服務。Randy也表達了服務的公共接口就跟前門是一樣的。

服務是被公共接口定義的,這個定義對于定義什麼服務是微服務也足夠了。

什麼是微服務?

如果一個服務是被它的公共接口定義的,那麼 -

一個微服務是指一個用了微型公共接口的服務 - 微型前門

這條在過程式程式設計中被遵守的規則,如今在分布式系統領域更加有關聯性。你暴露的服務越小,它的實作越簡單,它的本地複雜性越小。從全局複雜度來看,更小的公共接口會在服務間産生更少的依賴和連接配接。

微接口的概念也解釋了廣泛使用的微服務不暴露資料庫的實踐。沒有微服務可以通路另一個微服務的資料庫,隻能通過其提供的公共接口。為什麼?因為,資料庫實際是一個巨大的公共接口!隻要想想你可以在一個關系資料庫上能執行多少種操作。

是以,再重申下,在分布式系統中,我們通過将服務的公共接口最小化的方式來平衡局部與全局複雜度,然後服務就變成了微服務。

警告

這聽起來很簡單但其實不然。如果一個微服務隻是有微型公共接口的服務,那我們可以直接将公共接口限制到隻有一個方法。由于這個“前門”已經小的不能再小了,這應該是完美的微服務,對嗎?為了解釋為什麼不這麼做,我會使用我另一篇博文

11

裡的一個例子:

加入我們有如下庫存管理服務:

【譯】在分布式系統中解決,或平衡微服務的複雜度單體架構微什麼?系統視角的複雜性微服務設計服務邊界分布式系統之外的微服務從理論到實踐概要引用索引

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

【譯】在分布式系統中解決,或平衡微服務的複雜度單體架構微什麼?系統視角的複雜性微服務設計服務邊界分布式系統之外的微服務從理論到實踐概要引用索引

但我們能将它們連入系統來真正管理庫存嗎?并不行。要形成系統,服務需要與其他服務互動并共享對于每個服務的狀态。但它們不行。服務的公共接口不支援。

是以,我們要繼承這個“前門”并讓這些公共方法可以支援服務間的內建:

【譯】在分布式系統中解決,或平衡微服務的複雜度單體架構微什麼?系統視角的複雜性微服務設計服務邊界分布式系統之外的微服務從理論到實踐概要引用索引

完了!如果我們通過将每個服務完全獨立的方式來優化複雜度,那麼解耦的是很徹底。但是,當我們将服務連入系統,全局複雜度又升高了。不隻是導緻系統卷入了一團亂麻;為了內建 - 繼承公共接口也超出了我們原來的意圖。引自Randy Shoup,除了建設了一個小“前門”,我們也建了一個巨大的“員工專用”入口!這告訴我們一個重要的觀點:

一個服務有比業務方法更多的內建方法有成長為分布式大泥球的巨大可能!

是以,一個服務的公共接口可以被最小化到什麼程度不隻是依賴于服務本身,也取決(主要)于它在系統中是哪一部分。一個微服務何時的解耦應該同時考慮系統的全局複雜度和服務的局部複雜度。

設計服務邊界

“要找到服務邊界太難了... 完全沒有流程圖!” -Udi Dahan

上面Udi Dahan說的話對于基于微服務的系統來說也很對。設計微服務的邊界很難,基本上第一次很難做對。折讓設計一個合适複雜度的微服務變成了一個疊代流程。

是以,從更大的邊界開始是比較安全的 - 從上下文邊界開始比較合适

12

- 有更多關于系統各它的業務域的知識後,再将它們解耦成微服務。這對那些包含了核心業務域的服務特别重要

13

分布式系統之外的微服務

盡管微服務隻是最近才被“發明”出來,你仍可以在工業界發現很多有同樣設計理念的實作。這些包括:

跨功能團隊

我們知道跨功能團隊是最有效的。這種團隊讓不同專業能力的小組工作在同一個任務上。一個有效的跨功能團隊能最大化團隊内的交流,最小化團隊外的交流。

我們的工業隻是最近才發現跨功能團隊,但任務組是一直存在的。其底層的原理與基于微服務的系統是一樣的:在團隊内高聚合,團隊間低耦合。團隊的“公共接口”通過需要達成任務的技能來進行最小化(如實作的細節)。

微處理

我要通過Vaughn Vernon那經典的

相關主題的博文

來舉這個例子。在他的部落格裡,Vaughn描繪了一個微服務與微處理器間有趣的相似點。他講述了處理器與微處理器間的不同:

我發現了一個通過大小規格來幫助确定一個處理器是中央處理器(CPU)還是微處理器:資料總線 21 的大小

微處理器的資料總線就是他的公共接口 - 它定義了可以被傳給微處理器與其他元件間的資料量。對于公共接口有嚴格的尺寸規格來定義這個中央處理器(CPU)是不是一個微處理器。

Unix哲學

Unix哲學,或Unix方式,是一種秉承了極簡主義的子產品化軟體開發規範和文化。

22

有人可能會反駁我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
  2. Monoliths are the future
  3. Microservices guru warns devs that trendy architecture shouldn’t be the default for every app, but ‘a last resort’
  4. Monolithic Application(Wikipedia)
  5. The Majestic Monolith - DHH
  6. Big Ball of Mud(Wikipedia)
  7. Definition of a System
  8. Composite/Structures Design - book by Glenford J. Myers

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

  1. Designing Autonomous Teams and Services
  2. Emergent Boundaries - a talk by Mathias Verraes
  3. Long Sad Story of Microservices - talk by Greg Young
  4. Principles of Design - Tim Berners-Lee
  5. Microservices and [Micro]services - Vaughn Vernon
  6. Unix Philosophy