技術觀念 | 淺談分散式系統 - 微服務
微服務 (Microservices) 是一種軟體架構風格,它是以專注於單一責任與功能的小型功能區塊 (Small Building Blocks) 為基礎,利用模組化的方式組合出複雜的大型應用程式,各功能區塊使用與語言無關 (Language-Independent/Language agnostic) 的 API 集相互通訊。
微服務的起源是由 Peter Rodgers 博士於 2005 年度雲端運算博覽會提出的微 Web 服務 (Micro-Web-Service) 開始,Juval Löwy 則是與他有類似的前導想法,將類別變成細粒服務 (granular services),以作為 Microsoft 下一階段的軟體架構,其核心想法是讓服務是由類似 Unix 管道的存取方式使用,而且複雜的服務背後是使用簡單 URI 來開放介面,任何服務,任何細粒都能被開放 (exposed)。這個設計在 HP 的實驗室被實現,具有改變複雜軟體系統的強大力量。
2014年,Martin Fowler 與 James Lewis 共同提出了微服務的概念,定義了微服務是由以單一應用程式構成的小服務,自己擁有自己的行程與輕量化處理,服務依業務功能設計,以全自動的方式部署,與其他服務使用 HTTP API 通訊。同時服務會使用最小的規模的集中管理 (例如 Docker) 能力,服務可以用不同的程式語言與資料庫等元件實作。
微服務是一種受到服務導向架構(Service-oriented Architecture,SOA)所啟發的架構風格,這幾年隨著容器興起而開始流行起來,在介紹這領域當前最新技術之前,先回顧軟體架構的歷史,進而導致了物件和服務漸增之原因,以及後來的微服務。最後,將介紹未解決的問題和未來的挑戰。
一開始,用於開發後端伺服器應用程式(如Java,C/C++和Python)的主流語言,提供了抽象化,以便將複雜的程式分解成模組。然而,這類語言是針對建立單一可執行系統(亦稱為單體,monoliths)所設計的,它們的抽象化模組倚賴著共用同一台機器的記憶體、檔案、資料庫等資源,由於多個模組每一個皆依賴著相關的共享資源,因此它們無法獨立執行。
定義1:單體式(monolith)
單體式是一種軟體應用程式的風格,其模組不能單獨執行,最經典的便是IBM Mainframe大型主機,使用特定的作業系統ZOS、程式和函式庫,只能執行在IBM Z系列硬體,這使得單體架構很難在沒有特定框架或特殊解決方案的分散式系統中使用。常見分散式方法如Network Objects、Java RMI或OMG CORBA,但即便如此,這類方式仍舊受到單體式中常見的問題所困擾和影響;下面列出最常出現的問題:
由於其複雜性,大規模的單體架構難以開發及維護,追踪錯誤Bugs需要透過長時間詳細閱讀程式碼庫。
單體架構還會遭遇到「依賴地獄(Dependency Hell)」或「陣列地獄(Matrix Hell)」(2013年Docker在行銷宣傳時便使用此一口號),維護期間常因新增或更新程式庫導致不一致的系統無法編譯和執行,甚至更糟糕的是,營運環境中無法預料到的運行不一致現象。
單體架構上每一項模組中的任何變更修改都需要重新啟動整個應用系統,對於大型專案,重新啟動通常需要相當長的停機時間,這會阻礙專案的開發、測試和維護。
由於必須面對模組整併成之系統,使用者需求在資源面上所造成的衝突,單體式應用程式的部署通常就變成是次要的;有些系統可能是記憶體密集型,另一些是運算密集型的,而另一些需要特定相依元件(例如SQL關聯式資料庫),在選擇部署環境時,開發人員必須被迫接受,妥協於單一尺寸的所有配置,這對於各別單一模組而言,成本較昂貴,即便運行小模組,也因相依性問題必須使用高規格的硬體資源。
單體式架構限制了可擴展性,為了處理漸漸增加的流入請求,常用策略是建立相同應用系統的新實例,並分攤負載到這些伺服器實例之間。然而,有可能的情況是,額外增加的流量僅需要模組的一部分而已,這使得當要為了其他模組分配新資源的情況時就會非常不方便。
單體架構亦代表開發人員會被技術鎖定,開發人員被迫必須沿用原始應用程式的相同語言和框架。
微服務架構風格便是為了解決上述這些問題而發展出來,對於微服務的定義中,大多使用「內聚性」術語來表示需要實作哪些功能面關係密切的服務。
定義2:微服務(Microservice)
微服務是一支透過消息進行互動且具備內聚性的獨立程序,舉例來說,如果要將用於執行計算的服務,稱之為微服務,它應該要透過消息請求來提供可用的算術運算,但不應提供其他功能(主要為了鬆散耦合的緣故),如繪製和顯示等功能。
從技術角度來看,微服務應該是獨立的元件,概念上應該被隔離,並具備記憶體專用的持久性儲存工具(例如資料庫)。由於微服務架構的所有元件都是微服務,因此區別其微服務行為的方法是,透過元件之間的訊息組合與協調方式來決定。
定義3:微服務架構(Microservice Architecture)
微服務架構是一種分散式應用程式,其中所有模組都是微服務,舉個微服務架構的範例,假設想要提供一個繪製函數圖形的功能,且已存在著兩項微服務:計算器和顯示器。
第一項是前面所提到的計算器微服務,第二項是渲染和顯示圖片,為了達成服務拆分的目標,可以引入一項新的微服務,稱為「繪圖器」,它可協調計算器來運算圖形的形狀,並呼叫顯示器來渲染計算出的形狀,下圖來解說這微服務架構的流程步驟。
上述架構的開發人員可以各自單獨實作微服務的基礎功能、計算器和顯示器,可以使用繪圖器實現分散式應用程式的行為:
- 使用者利用繪圖器提供的API功能。
- 繪圖器與計算器相互溝通,以圖形函式功能計算需要呈現之圖案標記。
- 要求顯示器顯示結果。
- 傳回給使用者,為了說明微服務如何透過事先存在的微服務架構來達成橫向擴展,在圖1中繪出了計算器,協同呼叫兩項實作數學基礎和特殊功能的額外微服務(運算模組)。
微服務架構風格並不會偏好或禁止使用任何特定的撰寫程式範例,它只是提供了將分散式應用程式之元件劃分為各自獨立單元的指導原則,每一個單元只解決它所負責的問題,這意味著如果微服務藉由訊息傳遞提供其功能,便可如前文所提到的,在微服務內部可使用任何主流程式語言來實作其商業邏輯。
微服務架構原則上可以幫助專案經理和開發人員,它為分散式應用程式的設計和實作提供了完整指南。遵循此一原則,開發人員專注於實作和測試一些內聚性之功能,這也適用於更高層次的微服務,像涉及到協調其他微服務等功能。
總結一下整個敘述,微服務該如何應付上述所提到的單體式應用系統之六個問題:
微服務實作了有限的功能,這使得它們的程式碼庫很小,因此先天就限制了Bug的範圍。此外,由於微服務是獨立的,開發人員可以遵循系統隔離方法,在Testing或UAT環境,便可直接測試和檢查它們的功能面問題。
規劃逐步轉換到新版本微服務便成為可能,新版本可以部署到舊版本旁,同時並存,只要符合既有API規格,相依之服務可於後續逐步修改,這助長了持續整合並大量地簡化了軟體維護工作。
接續著上一項的優點,更改微服務架構的模組不需要完全重啟整個系統,重新啟動只會涉及到該模組的微服務,由於微服務的範圍很小,只需要很短的停機時間並在重新部署之後,程式開發人員便可以開發、測試和維護服務。
因此微服務很自然地適用於「容器化」,且開發人員樂於在他們需要部署的環境中擁有高度自由的自主配置權力,進而可找出兼具成本和服務質量上的最適當設定。
橫向擴展微服務架構,並不表示就是重複所有元件,開發人員可以更方便地部署和處理那些高負載相關的服務實例。
唯一的限制是,運作微服務的網路所使用的通訊技術,如通訊媒體、通訊協定和通訊編碼等。除此之外,微服務不會產生額外的技術鎖定或限制,開發人員可以自由選擇最佳方案和資源(例如程式語言、框架等)來實作每一項微服務。
微服務規劃
微服務的規劃與單體式應用程式十分不同,微服務中每個服務都需要避免與其他服務有所牽連,且都要能夠自主,並在其他服務發生錯誤時不受干擾。
資料庫
微服務理念中有數個資料庫的規劃方式。
- 每個服務都各有一個資料庫,同屬性的服務可共享同個資料庫。
- 所有服務都共享同個資料庫,但是不同表格,並且不會跨域存取。
- 每個服務都有自己的資料庫,就算是同屬性的服務也是,資料庫並不會共享。
資料庫並不會只存放該服務的資料,而是「該服務所會用到的所有資料」。更深層一點的舉例:假設有個文章服務,而這個服務可能會需要判斷使用者的帳號⋯⋯等。那麼文章服務的資料庫就可以放入使用者的部分資料。此舉是為了避免服務之間的相依性,避免文章服務呼叫使用者服務。
微服務中每個服務都能夠有自己的資料庫。
資料庫的可棄性
實踐微服務有許多的做法,但其中一種做法是將資料庫作為短期的儲存空間而不是儲存長期的資料。這意味著資料庫可以在離線時被清空。因為它們可以在上線時從事件儲存中心恢復,因此也能以記憶體快取(如:Redis) 作為資料庫伺服器。但這種做法需要將每個請求當作事件來進行廣播。如此一來就可以從事件儲存中心重播所有的事件來找回所有的資料。
如果資料庫都是分開的,那麼新服務上線時就會遇到資料庫為空的窘境。我們並不能從另一個服務複製資料過來,因為我們無法確定該服務擁有最新的資料。 此時應該從事件儲存中心重播所有事件,如此一來就可以找回先前的所有、最新的資料。
溝通與事件廣播
NSQ 是一個訊息佇列系統、平台。在微服務中所扮演的角色是將訊息、資料傳遞到其他服務。 此舉是異步執行,所以不需要等到其他服務接收到訊息就能夠執行下一步。這種方式能夠避免服務之間有所牽連、呼叫。
微服務中最重要的就是每個服務的獨立與自主,因此服務與服務之間也不應該有所溝通。倘若真有溝通,也應採用異步溝通的方式來避免緊密的相依性問題。要達到此目的,則可用下列兩種方式:
事件儲存中心(Event Store)
這可以讓你在服務叢集中廣播事件,並且在每個服務中監聽這些事件並作處理,這令服務之間沒有緊密的相依性,而這些發生的事件都會被儲存在事件儲存中心裡。這意味著當微服務重新上線、部署時可以重播(Replay)所有的事件。這也造就了微服務的資料庫隨時都可以被刪除、摧毀,且不需要從其他服務中取得資料。
訊息佇列(Message Queue)
這令你能夠在服務叢集中廣播訊息,並傳遞到每個服務中。具有這個功能的像是 NSQ 或是 RabbitMQ。你能夠在 A 服務上廣播一個「建立新使用者」的事件,這個事件可以順便帶有新使用者的資料。而 B 服務可以監聽這個事件並在接收到之後有所處理。這些過程都是異步處理的,這意味著 A 服務並不需要等到 B 服務處理完該事件後才能繼續,而這也代表 A 服務無法取得 B 服務的處理結果。與事件儲存中心近乎相似,但有所不同的是:訊息佇列並不會儲存事件。一旦事件被消化(接收)後就會從佇列中消失,這很適合用在像發送歡迎信件的時機。
NSQ 是一個訊息佇列系統、平台。在微服務中所扮演的角色是將訊息、資料傳遞到其他服務。 此舉是異步執行,所以不需要等到其他服務接收到訊息就能夠執行下一步。這種方式能夠避免服務之間有所牽連、呼叫。
服務探索
單個微服務在上線的時候,會向服務探索中心(如:Consul)註冊自己的 IP 位置、服務內容,如此一來就不需要向每個微服務表明自己的 IP 位置,也就不用替每個微服務單獨設定。當服務需要呼叫另一個服務的時候,會去詢問服務探索中心該服務的 IP 位置為何,得到位置後即可直接向目標服務呼叫。
這麼做的用意是可以統一集中所有服務的位置,就不會分散於每個微服務中,且服務探索中心可以每隔一段時間就向微服務進行健康檢查(如透過:TCP 呼叫、HTTP 呼叫、Ping),倘若該服務在時間內沒有回應,則將其從服務中心移除,避免其他微服務對一個無回應的服務進行呼叫。
微服務特性
一個微服務架構的應用程式有下列特性:
- 每個服務都容易被取代。
- 服務是以能力來組織的,例如使用者介面、前端、推薦系統、帳單或是物流等。
- 由於功能被拆成多個服務,因此可以由不同的程式語言、資料庫實作。
- 架構是對稱而非分層(即生產者與消費者的關係)。
一個微服務架構:
- 適用於具持續交付 (Continuous Delivery) 的軟體開發流程。
- 與服務導向架構 (Service-Oriented Architecture) 不同,後者是整合各種業務的應用程式,但微服務只屬於一個應用程式。
註:以上參考了
漫谈分布式系统中的技术 —— 从 IPC/RPC,SOA,Web Service/REST 到 micro services(微服务)
走入軟體架構演進史 見證微服務發展今昔
嫻熟微服務架構運作 方能建構可靠安全系統
架構師觀點 - 轉移到微服務架構的經驗分享 (Part 1)
維基百科-微服務
導入微服務前一定要知道的事