微服務 | Martin Fowler
“微服務架構”這一術語在前幾年橫空出世,用於描述這樣一種特定的軟體設計方法,即以若干組可獨立部署的服務的方式進行軟體應用系統的設計。儘管這種架構風格尚無明確的定義,但其在下述方面還是存在一定的共性,即圍繞業務功能的組織、自動化部署、端點智慧、以及在程式語言和資料方面進行去中心化的控制。
本文目錄
微服務架構的九大特性
-
特性一:“元件化”與“多服務”
-
特性二:圍繞“業務功能”組織團隊
-
特性三:“做產品”而不是“做專案”
-
特性四:“智慧端點”與“傻瓜管道”
-
特性五:“去中心化”地治理技術
-
特性六:“去中心化”地管理資料
-
特性七:“基礎設施”自動化
-
特性八:“容錯”設計
-
特性九:“演進式”設計
未來的方向是“微服務”嗎?
“微服務”——這是在“軟體架構”這條熙熙攘攘的大街上出現的又一個新詞語。我們很容易對它不屑一顧,但是這個小小的術語卻描述了一種引人入勝的軟體系統風格。在近幾年中,我們越來越多的看到許多專案使用了這種風格,而且就目前來說結果都是不錯的,以至於許多ThoughtWorker都把它看作構建企業應用系統的預設風格。然而,很不幸的是,我們找不到有關它的概要資訊,即什麼是微服務風格,以及如何設計微服務風格的架構。
簡而言之,微服務架構風格這種開發方法,是以開發一組小型服務的方式來開發一個獨立的應用系統。其中每個小型服務都執行在自己的程序中,並經常採用HTTP資源API這樣輕量的機制來相互通訊。這些服務圍繞業務功能進行構建,並能通過全自動的部署機制來進行獨立部署。這些微服務可以使用不同的語言來編寫,並且可以使用不同的資料儲存技術。對這些微服務,我們僅做最低限度的集中管理。
在開始介紹微服務風格之前,將其與單塊(monolithic)風格進行對比還是很有意義的:一個單塊應用系統是以一個單個單元的方式來構建的。企業應用系統經常包含三個主要部分:客戶端使用者介面、資料庫和服務端應用系統。客戶端使用者介面包括HTML頁面和執行在使用者機器的瀏覽器中的JavaScript。資料庫中包括許多表,這些表被插入一個公共的且通常為關係型的資料庫管理系統中。這個服務端的應用系統就是一個單塊應用——一個單個可執行的邏輯程式。對於該系統的任何改變,都會涉及構建和部署上述服務端應用系統的一個新版本。
這樣的單塊伺服器是構建上述系統的一種自然的方式。處理使用者請求的所有邏輯都執行在一個單個的程序內,因此能使用程式語言的基本特性,來把應用系統劃分為類、函式和名稱空間。通過精心設計,得以在開發人員的膝上型電腦上執行和測試這樣的應用系統,並且使用一個部署流水線來確保變更被很好地進行了測試,並被部署到生產環境中。通過負載均衡器執行許多例項,來將這個單塊應用進行橫向擴充套件。
單塊應用系統可以被成功地實現,但是漸漸地,特別是隨著越來越多的應用系統正被部署到雲端,人們對它們開始表現出不滿。軟體變更受到了很大的限制,應用系統中一個很小部分的一處變更,也需要將整個單塊應用系統進行重新構建和部署。隨著時間的推移,單塊應用逐漸難以保持一個良好的模組化結構,這使得它變得越來越難以將一個模組的變更所產生的影響控制在該模組內。當對系統進行擴充套件時,不得不擴充套件整個應用系統,而不能僅擴充套件該系統中需要更多資源的那些部分。
圖1: 單塊應用和微服務
這些不滿催生出了微服務架構風格:以構建一組小型服務的方式來構建應用系統。除了這些服務能被獨立地部署和擴充套件之外,每一個服務還能提供一個穩固的模組邊界,甚至能允許使用不同的程式語言來編寫不同的服務。這些服務也能被不同的團隊來管理。
我們並不認為微服務風格是一個新穎或創新的概念,它的起源至少可以追溯到Unix的設計原則。但是我們覺得,考慮微服務架構的人還不夠多,並且如果對其加以使用,許多軟體的開發工作能變得更好。
微服務架構的九大特性
雖然不能說存在微服務架構風格的正式定義,但是可以嘗試描述我們所見到的、能夠被貼上“微服務”標籤的那些架構的共性。下面所描述的這些共性,並不是所有的微服務架構都完全具備,但是我們確實期望大多數微服務架構都具備這些共性中的大多數特性。儘管我們兩位作者已經成為這個相當鬆散的社群中的活躍成員,但我們的本意還是描述我們兩人在自己所在和所瞭解的團隊工作中所看到的情況。特別要指出,我們不會制定大家需要遵循的微服務的定義。
特性一:“元件化”與“多服務”
自我們從事軟體行業以來,發現大家都有“把元件插在一起來構建系統”的願望,就像在物理世界中所看到的那樣。在過去幾十年中,我們已經看到,在公共軟體庫方面已經取得了相當大的進展,這些軟體庫是大多數程式語言平臺的組成部分。
當談到元件時,會碰到一個有關定義的難題,即什麼是元件?我們的定義是:一個元件就是一個可以獨立更換和升級的軟體單元。
微服務架構也會使用軟體庫,但其將自身軟體進行元件化的主要方法是將軟體分解為諸多服務。我們將軟體庫(libraries)定義為這樣的元件,即它能被連結到一段程式,且能通過記憶體中的函式來進行呼叫。然而,服務(services)是程序外的元件,它們通過諸如web service請求或遠端過程呼叫這樣的機制來進行通訊(這不同於許多面向物件的程式中的service object概念)。
以使用服務(而不是以軟體庫)的方式來實現元件化的一個主要原因是,服務可被獨立部署。如果一個應用系統由在單個程序中的多個軟體庫所組成,那麼對任一元件做一處修改,都不得不重新部署整個應用系統。但是如果該應用系統被分解為多個服務,那麼對於一個服務的多處修改,僅需要重新部署這一個服務。當然這也不是絕對的,一些變更服務介面的修改會導致多個服務之間的協同修改。但是一個良好的微服務架構的目的,是通過內聚的服務邊界和服務協議方面的演進機制,來將這樣的修改變得最小化。
以服務的方式來實現元件化的另一個結果,是能獲得更加顯式的(explicit)元件介面。大多數程式語言並沒有一個良好的機制來定義顯式的釋出介面。通常情況下,這樣的介面僅僅是文件宣告和團隊紀律,來避免客戶端破壞元件的封裝,從而導致元件間出現過度緊密的耦合。通過使用顯式的遠端呼叫機制,服務能更容易地規避這種情況。
如此使用服務,也會有不足之處。比起程序內呼叫,遠端呼叫更加昂貴。所以遠端呼叫API介面必須是粗粒度的,而這往往更加難以使用。如果需要修改元件間的職責分配,那麼當跨越程序邊界時,這種元件行為的改動會更加難以實現。
近似地,我們可以把一個個服務對映為一個個執行時的程序,但這僅僅是一個近似。一個服務可能包括總是在一起被開發和部署的多個程序,比如一個應用系統的程序和僅被該服務使用的資料庫。
特性二:圍繞“業務功能”組織團隊
當在尋求將一個大型應用系統分解成幾部分時,公司管理層往往會聚焦在技術層面上,這就意味著要組建使用者介面團隊、伺服器端團隊和資料庫團隊。當團隊沿著這些技術線分開後,即使要實現軟體中一個簡單的變更,也會發生跨團隊的專案時延和預算審批。在這種情況下,聰明的團隊會進行區域性優化,“兩害相權取其輕”,來直接把程式碼邏輯塞到他們能訪問到的任意應用系統中。換句話說,這種情況會導致程式碼邏輯散佈在系統各處。這就是康威定律的鮮活例項。
任何設計(廣義上的)系統的組織,都會產生這樣一個設計,即該設計的結構與該組織的溝通結構相一致。——梅爾文•康威(Melvyn Conway),1967年
圖2:康威定律在起作用
微服務使用不同的方法來分解系統,即根據業務功能(business capability)來將系統分解為若干服務。這些服務針對該業務領域提供多層次、廣泛的軟體實現,包括使用者介面、永續性儲存以及任何對外的協作性操作。因此,團隊是跨職能的,它擁有軟體開發所需的全方位的技能:使用者體驗、資料庫和專案管理。
圖3:被團隊邊界所強化的服務邊界
以上述方式來組織團隊的公司是www.comparethemarket.com。跨職能團隊負責構建和運維每個產品,而每個產品被拆分為多個獨立的服務,彼此通過一個訊息匯流排來通訊。
一個微服務應該有多大?
儘管許多人已經習慣於用“微服務”來概括描述這種這種架構風格,但是這個名字確實會不幸地引發大家對服務規模的關注,並且產生有關什麼是“微”的爭論。在與微服務從業者的交談中,我們看到了有關服務的一系列規模。所聽到的最大的一個服務規模,是遵循了亞馬遜的“兩個比薩團隊”(即一個團隊可以被兩個比薩所餵飽)的理念所形成的,這意味著這個團隊不會多於12人。對於規模較小的服務,我們已經看到一個6人的團隊在支援6個服務。
這引出了一個問題,即“每12人做一個服務”和“每人做一個服務”這樣有關服務規模的差距,是否已經大到不能將兩者都納入微服務之下?此時,我們認為最好還是把它們歸為一類,但是隨著探索的深入,我們將來極有可能會改變主意。
大型單塊應用系統也可以始終根據業務功能來進行模組化設計,雖然這並不常見。當然,我們會敦促構建單塊應用系統的大型團隊根據業務線來將自己分解為若干小團隊。在這方面,我們已經看到的主要問題是,他們往往是一個團隊包含了太多的業務功能。如果這個“單塊”跨越了許多模組的邊界,那麼這個團隊的每一個成員都難以記住所有模組的業務功能。此外,我們看到這些模組的邊界需要大量的團隊紀律來強制維持。而實現元件化的服務所必要的更加顯式的邊界,能更加容易地保持團隊邊界的清晰性。
特性三:“做產品”而不是“做專案”
我們所看的大部分應用系統的開發工作都使用專案模型:目標是交付某一塊軟體,之後就認為完工了。一旦完工後,軟體就被移交給維護團隊,接著那個構建該軟體的專案團隊就會被解散。
微服務的支持者們傾向於避免使用上述模型,而寧願採納“一個團隊在一個產品的整個生命週期中都應該保持對其擁有”的理念。通常認為這一點源自亞馬遜的“誰構建,誰執行”的理念,即一個開發團隊對一個在生產環境下執行的軟體負全責。這會使開發人員每天都關注軟體是如何在生產環境下執行的,並且增進他們與使用者的聯絡,因為他們必須承擔某些支援工作。
這樣的“產品”理念,是與業務功能的聯動繫結在一起的。它不會將軟體看作是一個待完成的功能集合,而是認為存在這樣一個持續的關係,即軟體如何能助其客戶來持續增進業務功能。
當然,單塊應用系統的開發工作也可以遵循上述“產品”理念,但是更細粒度的服務,能讓服務的開發者與其使用者之間的個人關係的建立變得更加容易。
特性四:“智慧端點”與“傻瓜管道”
當在不同的程序之間構建各種通訊結構時,我們已經看到許多產品和方法,來強調將大量的智慧特性納入通訊機制本身。其中一個典型例子,就是“企業服務匯流排”(Enterprise Service Bus, ESB)。ESB產品經常包括高度智慧的設施,來進行訊息的路由、編制(choreography)、轉換,並應用業務規則。
微服務社群主張採用另一種做法:智慧端點(smart endpoints)和傻瓜管道(dumb pipes)。使用微服務所構建的各個應用的目標,都是儘可能地實現“高內聚和低耦合”——他們擁有自己的領域邏輯,並且更像是經典Unix的“過濾器”(filter)那樣來工作——即接收一個請求,酌情對其應用業務邏輯,併產生一個響應。這些應用通過使用一些簡單的REST風格的協議來進行編制,而不去使用諸如下面這些複雜的協議,即"WS-編制"(WS-Choreography)、BPEL或通過位於中心的工具來進行編排(orchestration)。
微服務最常用的兩種協議是:帶有資源API的HTTP“請求-響應”協議,和輕量級的訊息傳送協議。對於前一種協議的最佳表述是:
成為Web,而不是躲著Web (Be of the web, not behind the web)——Ian Robinson
這些微服務團隊在開發中,使用在構建全球資訊網(world wide web)時所使用的原則和協議(並且在很大程度上,這些原則和協議也是在構建Unix系統時所使用的)。那些被使用過的HTTP資源,通常能被開發或運維人員輕易地快取起來。
最常用的第二種協議,是通過一個輕量級的訊息匯流排來進行訊息傳送。此時所選擇的基礎設施,通常是“傻瓜”(dumb)型的(僅僅像訊息路由器所做的事情那樣傻瓜)——像RabbitMQ或ZeroMQ那樣的簡單實現,即除了提供可靠的非同步機制(fabric)以外不做其他任何事情——智慧功能存在於那些生產和消費諸多訊息的各個端點中,即存在於各個服務中。
在一個單塊系統中,各個元件在同一個程序中執行。它們相互之間的通訊,要麼通過方法呼叫,要麼通過函式呼叫來進行。將一個單塊系統改造為若干微服務的最大問題,在於對通訊模式的改變。僅僅將記憶體中的方法呼叫轉換為RPC呼叫這樣天真的做法,會導致微服務之間產生繁瑣的通訊,使得系統表現變糟。取而代之的是,需要用更粗粒度的協議來替代細粒度的服務間通訊。
特性五:“去中心化”的治理技術
使用中心化的方式來對開發進行治理,其中一個後果,就是趨向於在單一技術平臺上制定標準。經驗表明,這種做法會帶來侷限性——不是每一個問題都是釘子,不是每一個方案都是錘子。我們更喜歡根據工作的不同來選用合理的工具。儘管那些單塊應用系統能在一定程度上利用不同的程式語言,但是這並不常見。
如果能將單塊應用的那些元件拆分成多個服務,那麼在構建每個服務時,就可以有選擇不同技術棧的機會。想要使用Node.js來搞出一個簡單的報表頁面?儘管去搞。想用C++來做一個特別出彩的近乎實時的元件?沒有問題。想要換一種不同風格的資料庫,來更好地適應一個元件的讀取資料的行為?可以重建。
微服務和SOA
當我們談起微服務時,一個常見的問題就會出現:是否微服務僅僅是十多年前所看到的“面向服務的架構”(Service Oriented Architecture,SOA)?這樣問是有道理的,因為微服務風格非常類似於一些支援SOA的人所贊成的觀點。然而,問題在於SOA這個詞兒意味著太多不同的東西。而且大多數時候,我們所遇到的某些被稱作"SOA"的事物,明顯不同於本文所描述的風格。這通常由於它們專注於ESB,來整合各個單塊應用。
特別地,我們已經看到如此之多的面向服務的拙劣實現——從將系統複雜性隱藏於ESB中的趨勢,到花費數百萬進行多年卻沒有交付任何價值的失敗專案,到頑固抑制變化發生的中心化技術治理模型——以至於有時覺得其所造成的種種問題真的不堪回首。
當然,在微服務社群投入使用的許多技術,源自各個開發人員將各種服務整合到各個大型組織的經驗。“容錯讀取”(Tolerant Reader)模式就是這樣一個例子。對於Web的廣泛使用,使得人們不再使用一些中心化的標準,而使用一些簡單的協議。坦率地說,這些中心化的標準,其複雜性已經達到令人吃驚的程度。(任何時候,如果需要一個本體來管理其他各個本體,那麼麻煩就大了。)
這種常見的SOA表現,已使得一些微服務的倡導者完全拒絕將自己貼上SOA的標籤。儘管其他人會將微服務看作是SOA的一種形式,也許微服務就是以正確的形式來實現面向服務的SOA。不管是哪種情況,SOA意味著如此之多的不同事物,這表明用一個更加乾淨利落的術語來命名這種架構風格是很有價值的。
當然,僅僅能做事情,並不意味著這些事情就應該被做——不過用微服務的方法把系統進行拆分後,就擁有了技術選型的機會。
相比選用業界一般常用的技術,構建微服務的那些團隊更喜歡採用不同的方法。與其選用一組寫在紙上已經定義好的標準,他們更喜歡編寫一些有用的工具,來讓其他開發者能夠使用,以便解決那些和他們所面臨的問題相似的問題。這些工具通常源自他們的微服務實施過程,並且被分享到更大規模的組織中,這種分享有時會使用內部開源的模式來進行。事實上,現在git和github已經成為首選版本控制系統。在企業內部,開源的做法正在變得越來越普遍。
Netflix公司是遵循上述理念的好例子。將實用且經過實戰檢驗的程式碼以軟體庫的形式共享出來,能鼓勵其他開發人員以相似的方式來解決相似的問題,當然也為在需要的時候選用不同的方案留了一扇門。共享軟體庫往往聚焦於解決這樣的常見問題,即資料儲存、程序間的通訊和下面要進一步討論的基礎設施的自動化。
對於微服務社群來說,日常管理開銷這一點不是特別吸引人。這並不是說這個社群並不重視服務契約。恰恰相反,它們在社群裡出現得更多。這正說明這個社群正在尋找對其進行管理的各種方法。像“容錯讀取”和“消費者驅動的契約”(Consumer-Driven Contracts)這樣的模式,經常被運用到微服務中。這些都有助於服務契約進行獨立演進。將執行“消費者驅動的契約”做為軟體構建的一部分,能增強開發團隊的信心,並提供所依賴的服務是否正常工作的快速反饋。實際上,我們瞭解到一個在澳洲的團隊就是使用“消費者驅動的契約”來驅動構建多個新服務的。他們使用了一些簡單的工具,來針對每一個服務定義契約。甚至在新服務的程式碼編寫之前,這件事就已經成為自動化構建的一部分了。接下來服務僅被構建到剛好能滿足契約的程度——這是一個在構建新軟體時避免YAGNI困境的優雅方法。這些技術和工具在契約周邊生長出來,由於減少了服務之間在時域(temporal)上的耦合,從而抑制了對中心契約管理的需求。
多種程式語言,多種選擇可能
做為一個平臺,JVM的發展僅僅是一個將各種程式語言混合到一個通用平臺的最新例證。近十年以來,通過在平臺外層實現更高層次的程式語言,來利用更高層次的抽象,已經成為一個普遍做法。同樣,在平臺底層以更低層次的程式語言編寫效能敏感的程式碼也很普遍。然而,許多單塊系統並不需要這種級別的效能優化,另外DSL和更高層次的抽象也不常用(這令我們感到失望)。相反,許多單塊應用通常就使用單一程式語言,並且有對所使用的技術數量進行限制的趨勢。
或許去中心化地治理技術的極盛時期,就是亞馬遜的“誰構建,誰執行”的理念開始普及的時候。各個團隊負責其所構建的軟體的所有工作,其中包括7x24地對軟體進行運維。“將運維這一級別的職責下放到團隊”這種做法,目前絕對不是主流。但是我們確實看到越來越多的公司,將運維的職責交給各個開發團隊。Netflix就是已經形成這種風氣的另一個組織。避免每天凌晨3點被枕邊的尋呼機叫醒,無疑是在程式設計師編寫程式碼時令其專注質量的強大動力。而這些想法,與那些傳統的中心化技術治理的模式具有天壤之別。
特性六:“去中心化”地管理資料
去中心化地管理資料,其表現形式多種多樣。從最抽象的層面看,這意味著各個系統對客觀世界所構建的概念模型各不相同。當在一個大型的企業中進行系統整合時,這是一個常見的問題。比如對於“客戶”這個概念,從銷售人員的視角看,就與從支援人員的視角看有所不同。從銷售人員的視角所看到的一些被稱之為“客戶”的事物,或許在支援人員的視角中根本找不到。而那些在兩個視角中都能看到的事物,或許各自具有不同的屬性。更糟糕的是,那些在兩個視角中具有相同屬性的事物,或許在語義上有微妙的不同。
上述問題在不同的應用程式之間經常出現,同時也會出現在這些應用程式內部,特別是當一個應用程式被分成不同元件時就會出現。思考這類問題的一個有效方法,就是使用領域驅動設計(Domain-Driven Design, DDD)中的“限界上下文”(Bounded Context)的概念。DDD將一個複雜的領域劃分為多個限界上下文,並且將其相互之間的關係用圖畫出來。這一劃分過程對於單塊和微服務架構兩者都是有用的,而且就像前面有關“業務功能”一節中所討論的那樣,在服務和各個限界上下文之間所存在的自然的聯動關係,有助於澄清和強化這種劃分。
“實戰檢驗”的標準與“強制執行”的標準
微服務的下述做法有點涇渭分明的味道,即他們趨向於避開被那些企業架構組織所制定的硬性實施標準,而愉快地使用甚至傳播一些開放標準,比如HTTP、ATOM和其他微格式的協議。
這裡的關鍵區別是,這些標準是如何被制定以及如何被實施的。像諸如IETF這樣的組織所管理的各種標準,只有達到下述條件才能稱為標準,即該標準在全球更廣闊的地區有一些正在執行的實現案例,而且這些標準經常源自一些成功的開源專案。
這些標準組成了一個世界,它區別於來自下述另一個世界的許多標準,即企業世界。企業世界中的標準,經常由這樣特點的組織來開發,即缺乏用較新技術進行程式設計的經驗,或受到供應商的過度影響。
如同在概念模型上進行去中心化的決策一樣,微服務也在資料儲存上進行去中心化的決策。儘管各個單塊應用更願意在邏輯上各自使用一個單獨的資料庫來持久化資料,但是各家企業往往喜歡一系列單塊應用共用一個單獨的資料庫——許多這樣的決策是被供應商的各種版權商業模式所驅動出來的。微服務更喜歡讓每一個服務來管理其自有資料庫。其實現可以採用相同資料庫技術的不同資料庫例項,也可以採用完全不同的資料庫系統。這種方法被稱作“多語種持久化”(Polyglot Persistence)。在一個單塊系統中也能使用多語種持久化,但是看起來這種方法在微服務中出現得更加頻繁。
圖4:微服務更喜歡讓每一個服務來管理其自有資料庫
在各個微服務之間將資料的職責進行“去中心化”的管理,會影響軟體更新的管理。處理軟體更新的常用方法,是當更新多個資源的時候,使用事務來保證一致性。這種方法經常在單塊系統中被採用。
像這樣使用事務,有助於保持資料一致性。但是在時域上會引發明顯的耦合,這樣一來,在多個服務之間處理事務時會出現一致性問題。分散式事務實現難度之大是不必多言的。為此,微服務架構更強調在各個服務之間進行“無事務”的協調。這源自微服務社群明確地認識到下述兩點,即資料一致性可能只要求資料在最終達到一致,並且一致性問題能夠通過補償操作來進行處理。
對於許多開發團隊來說,選擇這種方式來管理資料的“非一致性”,是一個新的挑戰。但這通常也符合在商業上的實踐做法。通常情況下,為了快速響應需求,商家們都會處理一定程度上的資料“非一致性”,通過做某種反向過程來進行錯誤處理。只要修復錯誤的成本低於“保持更大的資料一致性卻導致丟了生意所產生”的成本相比,那麼進行這種“非一致性”地資料管理就是值得的。
特性七:“基礎設施”自動化
基礎設施自動化技術在過去幾年裡已經得到長足的發展。雲的演進,特別是AWS的發展,已經降低了構建、部署和運維微服務的操作複雜性。
許多使用微服務構建的產品和系統,正在被這樣的團隊所構建,即他們都具備極其豐富的“持續交付”和其前身“持續整合”的經驗。用這種方法構建軟體的各個團隊,廣泛採用了基礎設施的自動化技術。如下圖的構建流水線所示:
圖5:基本的構建流水線
由於本文並不是一篇有關持續交付的文章,所以下面僅提請大家注意兩個持續交付的關鍵特點。為了儘可能地獲得對正在執行的軟體的信心,需要執行大量的自動化測試。讓可工作的軟體達到“晉級”(Promotion)狀態、從而“推上”流水線,就意味著可以在每一個新的環境中,對軟體進行自動化部署。
一個單塊應用程式,能夠相當愉快地在上述各個環境中,被構建、測試和推送。其結果是,一旦在下述工作中進行了投入,即針對一個單塊系統將其通往生產環境的通道進行自動化,那麼部署更多的應用系統似乎就不再可怕。記住,持續交付的目的之一,是讓“部署”工作變得“無聊”。所以不管是一個還是三個應用系統,只要部署工作依舊很“無聊”,那麼就沒什麼可擔心的了。
讓“沿著正確的方向做事”更容易
那些因實現持續交付和持續整合所增加的自動化工作的副產品,是一些對開發和運維人員有用的工具。現在,能完成下述工作的工具已經相當常見了,即建立工件(artefacts)、管理程式碼庫、啟動一些簡單的服務、或增加標準的監控和日誌功能。Web上最好的例子可能是Netflix提供的一套開源工具集,但也有其他一些好工具,包括我們已經廣泛使用的Dropwizard。
我們所看到的各個團隊在廣泛使用基礎設施自動化實踐的另一個領域,是在生產環境中管理各個微服務。與前面我們對比單塊系統和微服務所說的正相反,只要部署工作很無聊,那麼在這一點上單塊系統和微服務就沒什麼區別。然而,兩者在運維領域的情況卻截然不同。
圖6:兩者的模組部署經常會有差異
特性八:“容錯”設計
使用各個微服務來替代元件,其結果是各個應用程式需要被設計的能夠容忍這些服務所出現的故障。如果服務提供方不可用,那麼任何對該服務的呼叫都會出現故障。客戶端要儘可能優雅地應對這種情況。與一個單塊設計相比,這是一個劣勢。因為在處理這種情況時會引入額外的複雜性。為此,各個微服務團隊在不斷地反思:這些服務故障是如何影響使用者體驗的。Netflix公司所研發的開源測試工具Simian Army,能夠誘導服務發生故障,甚至能誘導一個數據中心在工作日發生故障,來測試該應用的彈性和監控能力。
這種在生產環境中所進行的自動化測試,足以讓大多數運維組織興奮得渾身顫慄,就像即將迎來一週的長假那樣。這並不是說單塊架構風格不能構建先進的監控系統——只是根據我們的經驗,這在單塊系統中並不常見罷了。
"斷路器"與“可隨時上線的程式碼”
“斷路器”(Circuit Breaker)一詞與其他一些模式一起出現在《Release It!》一書中,例如隔板(Bulkhead)和超時(Timeout)。當構建彼此通訊的應用系統時,將這些模式加以綜合運用就變得至關重要。Netflix公司的這篇很精彩的部落格解釋了這些模式是如何應用的。
因為各個服務可以在任何時候發生故障,所以下面兩件事就變得很重要,即能夠快速地檢測出故障,而且在可能的情況下能夠自動恢復服務。各個微服務的應用都將大量的精力放到了應用程式的實時監控上,來檢查“架構元素指標”(例如資料庫每秒收到多少請求)和“業務相關指標”(例如系統每分鐘收到多少訂單)。當系統某個地方出現問題,語義監控系統能提供一個預警,來觸發開發團隊進行後續的跟進和調查工作。
這對於一個微服務架構是尤其重要的,因為微服務對於服務編制(choreography)和事件協作的偏好,會導致“突發行為”。儘管許多權威人士對於偶發事件的價值持積極態度,但事實上,“突發行為”有時是一件壞事。在能夠快速發現有壞處的“突發行為”並進行修復方面,監控是至關重要的。
單塊系統也能構建的像微服務一樣來實現透明的監控系統——實際上,它們也應該如此。差別是,絕對需要知道那些執行在不同程序中的服務,在何時斷掉了。而如果在同一個程序內使用軟體庫的話,這種透明的監控系統就用處不大了。
“同步呼叫”有害
一旦在一些服務之間進行多個同步呼叫,就會遇到宕機的乘法效應。簡而言之,這意味著整個系統的宕機時間,是每一個單獨模組各自宕機時間的乘積。此時面臨著一個選擇:是讓模組之間呼叫非同步,還是去管理宕機時間?在英國衛報網站,他們在新平臺上實現了一個簡單的規則——每一個使用者請求都對應一個同步呼叫。然而在Netflix公司,他們重新設計的平臺API將非同步性構建到API的機制(fabric)中。
那些微服務團隊希望在每一個單獨的服務中,都能看到先進的監控和日誌記錄裝置。例如顯示“執行/宕機”狀態的儀表盤,和各種運維、業務相關的指標。另外我們經常在工作中會碰到這樣一些細節,即斷路器的狀態、當前的吞吐率和延遲,以及其他一些例子。
特性九:“演進式”設計
那些微服務的從業者們,通常具有演進式設計的背景,而且通常將服務的分解,視作一個額外的工具,來讓應用開發人員能夠控制應用系統中的變化,而無須減少變化的發生。控制變化並不一定意味著要減少變化——在正確的態度和工具的幫助下,軟體中的變化也可以發生得頻繁、快速且得到良好的控制。
每當要試圖將軟體系統分解為各個元件時,就會面臨這樣的決策,即如何進行切分——我們決定切分應用系統時應該遵循的原則是什麼?一個元件的關鍵屬性,是具有獨立更換和升級的特點——這意味著,需要尋找這些點,即想象著能否在其中一個點上重寫該元件,而無須影響該元件的其他合作元件。事實上,許多做微服務的團隊會更進一步,他們明確地預期許多服務將來會報廢,而不是守著這些服務做長期演進。
英國衛報網站是一個好例子。原先該網站是一個以單塊系統的方式來設計和構建的應用系統,然而它已經開始向微服務方向進行演進了。原先的單塊系統依舊是該網站的核心,但是在新增新特性時他們願意以構建一些微服務的方式來進行新增,而這些微服務會去呼叫原先那個單塊系統的API。當開發那些本身就帶有臨時性特點的新特性時,這種方法就特別方便,例如開發報道一個體育賽事的專門頁面。當使用一些快速的開發語言時,這樣的網站頁面就能被快速地整合起來。而一旦賽事結束,這樣頁面就可以被刪除。在一個金融機構中,我們已經看到了一些相似的做法,即針對一個市場機會,一些新的服務可以被新增進來。然後在幾個月甚至幾周之後,這些新服務就作廢了。
這種強調“可更換性”的特點,是模組化設計一般性原則的一個特例,通過“變化模式”(pattern of change)來驅動模組化的實現。大家都願意將那些能在同時發生變化的東西,放到同一個模組中。系統中那些很少發生變化的部分,應該被放到不同的服務中,以區別於那些正在經歷大量變動(churn)的部分。當發現兩個服務需要被同時、反覆變更,就意味著它們兩個需要被合併。
把一個個元件放入一個個服務中,提高了軟體釋出精細化的程度。對於一個單塊系統,任何變化都需要做一次整個應用系統的全量構建和部署。然而,對於一個個微服務來說,只需要重新部署修改過的那些服務就夠了。這能簡化並加快釋出過程。但缺點是:必須要考慮當一個服務發生變化時,依賴它並對其進行消費的其他服務將無法工作。傳統的整合方法是使用版本化來解決這個問題。但在微服務世界中,大家更喜歡將版本化作為最後萬不得已的手段來使用。我們可以通過下述方法來避免許多版本化的工作,即把各個服務設計得儘量能夠容錯,來應對其所依賴的服務所發生的變化。
未來的方向是“微服務”嗎?
我們寫這篇文章的主要目的,是解釋有關微服務的主要思路和原則。在花了一點時間做了這件事後,我們清楚地認識到,微服務架構風格是一個重要的理念——在研發企業應用系統時,值得對它進行認真考慮。我們最近已經使用這種風格構建了一些系統,並且瞭解到其他一些團隊也贊同並正在使用這種方法。
我們所瞭解到的那些在某種程度上可以被稱作這種架構風格的實踐先驅包括:亞馬遜、Netflix、英國衛報、英國政府數字化服務中心、realestate.com.au、Forward和comparethemarket.com。2013年的技術大會圈子充滿了各種各樣的、正在轉向可歸類為微服務的公司案例——包括Travis CI。另外還有大量的組織,它們長期以來一直在做著我們認為可以歸類為微服務的產品,卻從未使用過這個名字(這通常被標記為SOA——儘管正如我們所說,SOA會表現出各種自相矛盾的形式)。
儘管有這些正面的經驗,但這並不意味著我們確信微服務是軟體架構未來的方向。儘管到目前為止,與單塊應用系統相比,我們對於所經歷過的微服務的評價是積極的,但是我們也意識到這樣的事實,即能供我們做出完整判斷的時間還不夠長。
通常,架構決策所產生的真正效果,只有在該決策做出若干年後才能真正顯現。我們已經看到由帶著強烈的模組化願望的優秀團隊所做的一些專案,最終構建出一個單塊架構,並在幾年之內不斷腐化。許多人認為,如果使用微服務就不大可能出現這種腐化,因為服務的邊界是明確的,而且難以隨意搞亂。然而,對於那些開發時間足夠長的各種系統,除非我們已經見識得足夠多,否則我們無法真正評價微服務架構是如何成熟的。