微服務一覽#

微服務(microservices)是圍繞業務領域(business domain)建模、可獨立發佈的服務。每個服務封裝一段功能,並透過網路端點(例如 REST API 或訊息佇列)暴露給其他服務使用,許多服務組合起來才構成完整系統。

Figure 1.1:微服務透過 REST API 與訊息佇列暴露功能

  • 屬於服務導向架構(Service-Oriented Architecture, SOA)的一種,但對「服務邊界該怎麼切」與「獨立部署」有更明確的主張
  • 技術中立(technology agnostic):每個服務可以選擇最合適的語言、框架與資料儲存方式
  • 從外部看每個微服務都是一個黑盒子;內部實作細節(程式語言、資料儲存)對外完全隱藏

微服務架構幾乎不使用共享資料庫——每個服務各自封裝自己需要的資料庫。

資訊隱藏(Information Hiding)#

微服務擁抱「資訊隱藏」的概念——這個概念最早由 David Parnas 於 1971 年提出。

  • 核心精神:盡量把資訊藏在元件內部,對外暴露越少越好
  • 效果:把「容易變動的部分」(內部實作)與「難以變動的部分」(對外契約)切開
  • 只要對外契約不破壞向後相容性(backward compatibility),內部就可以自由演化
  • 內部變動不會擴散到上游消費者,因此服務可以單獨被開發、單獨被釋出

清楚且穩定的服務邊界讓系統具有「鬆耦合(loose coupling)+ 高內聚(high cohesion)」的特質。

與 SOA 的差別#

SOA 本身是合理的概念:多個服務協同提供能力,鼓勵軟體重用,理論上可以替換實作而不被察覺。但業界長年缺乏「服務該切多大」「如何避免過度耦合」的具體共識,許多失敗案例導致 SOA 名聲不佳。失敗的真正原因常是:

  • 通訊協定的選擇(例如 SOAP)
  • 廠商中介軟體(vendor middleware)的綁架
  • 缺乏服務粒度(granularity)的指引
  • 切分系統時挑錯切口

微服務可視為「把 SOA 做好」的一個具體做法,地位類似於:

  • Extreme Programming(XP)或 Scrum 之於 Agile
  • 微服務之於 SOA

核心概念#

獨立可部署性(Independent Deployability)#

如果整本書只能帶走一個觀念,就是這個:確保每個微服務都能獨立部署上線,不需要連動部署其他服務。

這不只是「能不能」,更是「日常如何發佈」——把它當成預設的發佈紀律。

要達成獨立部署,必須讓服務之間鬆耦合,並擁有明確、穩定的對外契約(contract)。共用資料庫是其中最常見的破壞因子。

獨立部署本身就是巨大的好處。但更重要的是,把它當作目標會反過來迫使你做對許多其他事情(明確介面、資訊隱藏、領域邊界等),形成正向循環——這是一種強制函式(forcing function)

圍繞業務領域建模#

跨服務變更代價很高:要協調多個服務(甚至多個團隊)、安排正確的上線順序。因此設計時希望讓跨服務變更越少越好

傳統三層式架構(Three-Tiered Architecture)以技術作為服務邊界(前端團隊、後端團隊、DBA 團隊各管一層),但業務功能的修改通常會橫跨多層,導致每次變更都需要跨團隊協調。

Figure 1.2:傳統的三層式架構

Figure 1.4:Music Corp 系統採用傳統三層式架構

Figure 1.5:橫跨三層的變更牽涉更廣

微服務改以業務領域為邊界,採用領域驅動設計(Domain-Driven Design, DDD)的概念,讓每個服務內部包含 UI、業務邏輯、資料儲存的端到端切片:

  • 業務功能高內聚,技術功能反而散落在各服務內部
  • 這是一種取捨:放棄「技術同質性高內聚」,換取「業務功能高內聚」

Figure 1.3:每個微服務可封裝表現層、業務邏輯與資料儲存

Figure 1.6:獨立的 Customer 服務讓記錄音樂喜好變得簡單

擁有自己的狀態(Owning Their Own State)#

服務不應該共享資料庫。若 A 服務需要 B 服務的資料,必須呼叫 B 提供的介面,而非直接讀取 B 的資料庫。

  • 對應到物件導向(Object-Oriented, OO)程式設計中的封裝(encapsulation)
  • 將「內部實作細節」與「外部契約」清楚切分,可以大幅減少向後不相容(backward-incompatible)變更
  • 隱藏資料庫即同時降低耦合並提高內聚

除非真的非要不可,否則絕對不要共享資料庫。共享資料庫是破壞獨立部署性最常見的元兇。

大小(Size)#

ThoughtWorks 技術總監 James Lewis 的名言:「a microservice should be as big as my head」——一個微服務應該小到能輕易理解的程度。

另一位作者 Chris Richardson 則說,微服務的目標應該是「as small an interface as possible」(介面盡可能小),這也呼應了資訊隱藏的精神。

微服務的「大小」其實是最不重要的問題。程式碼行數沒有意義(Java 25 行可能等於 Clojure 10 行),且大小是高度脈絡相關的——同一份十萬行程式碼,老手覺得簡單、新人覺得龐大。重點不是大小,而是:

  • 你能管理幾個微服務? 服務數量越多,系統複雜度越高,需要更多技能與工具——這也是強烈建議「漸進式遷移」的原因
  • 如何劃定邊界? 邊界沒切好就會變成緊耦合的爛攤子

彈性(Flexibility)#

James Lewis 另一句名言:「microservices buy you options」——微服務帶來的是選擇權,但選擇權有成本。

  • 把採用微服務想成轉動旋鈕,而非按下開關
  • 服務越多,組織、技術、規模、韌性的彈性越大,但複雜度也成正比上升
  • 強烈建議漸進式採用,邊轉旋鈕邊評估痛點,遇到問題可隨時停下

架構與組織對齊(Conway’s Law)#

康威定律(Conway’s Law):「任何組織所設計的系統,其結構必然反映該組織的溝通結構。」

——Melvin Conway,《How Do Committees Invent?》

三層架構之所以普及,正是因為傳統 IT 組織以核心技能分組(DBA 一隊、後端一隊、前端一隊)。但業務團隊正在改採跨技能、產品導向的編組,架構也應該跟著改變——把人按技能分組,自然產出按技術分層的系統;把人按業務分組,才會產出按業務分服務的系統。

巨石(Monolith)的型態#

書中討論的巨石(monolith)指的是部署單元:所有功能必須一起部署的系統。常見三種型態:

型態說明代表特徵
單一行程巨石(Single-Process Monolith)所有程式碼打包成一個行程多執行個體共享相同程式碼
模組化巨石(Modular Monolith)行程內切分成獨立模組Shopify 即為此模式
分散式巨石(Distributed Monolith)拆成多服務,但仍須一起部署兼具分散式與巨石的缺點

Figure 1.7:單一行程巨石將所有程式碼打包成一個行程

Figure 1.8:模組化巨石把行程內的程式碼切分為多個模組

Figure 1.9:搭配資料庫拆分的模組化巨石

分散式巨石(distributed monolith)通常是因為缺乏資訊隱藏與業務內聚而形成的——它擁有分散式系統的所有缺點,卻沒有微服務的好處。Leslie Lamport 說過:「分散式系統就是當一台你不知道存在的電腦故障,會讓你自己的電腦無法使用的系統。」

交付競爭(Delivery Contention)#

當人多了起來,就會互相妨礙:搶改同一段程式碼、發佈時程互相打架、誰擁有什麼界線不清。Newman 把這個現象稱為交付競爭

  • 巨石不必然導致交付競爭,微服務也不保證能避免
  • 但微服務提供更具體的邊界,讓所有權的劃分更容易,減緩競爭

巨石的優勢#

巨石不該被妖魔化,它是有效的選擇,特別在以下情況:

  • 部署拓樸簡單:減少分散式系統的陷阱
  • 開發者體驗好:監控、除錯、端到端測試容易
  • 程式碼重用直接:無需切分函式庫或共享服務

作者立場:把巨石視為預設選擇,要有理由才採用微服務,而不是反過來。

賦能技術(Enabling Technology)#

不需要在採用微服務的第一天就導入所有新技術,但下列幾類工具值得認識:

日誌聚合與分散式追蹤#

  • 日誌聚合(log aggregation)是採用微服務前的先決條件(pre-requisite)
  • 透過關聯 ID(correlation ID)串起一次請求觸發的所有服務呼叫
  • 工具:Humio、Jaeger(聚焦分散式追蹤)、LightStep、Honeycomb(新一代可觀測性平台)

Figure 1.10:Honeycomb 中呈現的分散式追蹤

容器與 Kubernetes#

  • 容器(containers)提供輕量、隔離的執行環境,啟動速度快
  • Kubernetes 提供容器編排(orchestration)能力
  • 建議:服務數量還少時不必導入;若要使用 Kubernetes,盡量交給雲端託管服務

串流(Streaming)#

  • Apache Kafka 已成為微服務之間串流資料的事實標準
  • 訊息持久化、壓縮(compaction)、大流量處理是其關鍵能力
  • 可搭配 KSQL 或 Apache Flink 做串流處理
  • Debezium 可將傳統資料來源接入 Kafka

公有雲與無伺服器#

  • 透過託管服務把運維工作外包給雲端供應商(GCP、Azure、AWS)
  • 無伺服器(serverless)讓你跳過底層機器、直接關心業務
  • Function as a Service(FaaS)讓你只關心程式碼,平台自動依需求拉起執行個體

微服務的優勢#

  • 技術異質性(Technology Heterogeneity):每個服務挑選最適合的技術棧;新技術引入風險較低
    • 例如社群網站可用圖資料庫存社交圖譜、用文件資料庫存貼文
    • Netflix 與 Twitter 主要採用 Java 虛擬機(Java Virtual Machine, JVM)並不代表完全沒有其他技術,而是有意控制多樣性

Figure 1.11:微服務讓你更容易採用不同技術

  • 韌性(Robustness):服務邊界形成天然隔艙(bulkhead),單一故障不會擴散
    • 但要意識到:網路與機器都可能故障,需要面對分散式系統的失敗模式
  • 規模化(Scaling):只擴展真正需要擴展的服務(Gilt 從 Rails 巨石轉向微服務的主因;2009 年無法承載流量,今日已超過 450 個微服務)

Figure 1.12:只針對需要擴展的微服務進行擴展

  • 部署容易:單服務變更可獨立部署,降低風險、加快回滾;Amazon、Netflix 採用此架構主因即在此
  • 組織對齊:小團隊管理小程式碼庫,效率最佳;組織重組時可一併調整服務所有權
  • 可組合性(Composability):同一份功能可在 Web、Native App、Mobile Web、平板、穿戴裝置等不同通路重複使用——把系統開出許多接縫,外部能以多種方式重新組合

微服務的痛點#

  • 開發者體驗:JVM 等重量級執行環境,本機跑不動 10–20 個服務;極端做法是「在雲端開發」(developing in the cloud),但回饋週期會變慢,作者不推薦
  • 技術過載(Technology Overload):新工具琳瑯滿目,容易陷入「技術崇拜」——能用不同語言/資料庫是「選項」而非「義務」
  • 報表(Reporting):資料分散後,跨服務 join 不再容易;需要透過串流或集中報表資料庫(甚至資料湖)來支撐

Figure 1.13:直接在巨石的資料庫上執行報表

  • 監控與排錯:服務眾多時,「單一服務 100% CPU」是否需要值班人員凌晨三點起床?需要重新設計告警邏輯
  • 安全(Security):資料在服務間流動,需要保護傳輸(防止竊聽與中間人攻擊)並控管端點存取
  • 測試:端到端測試成本激增,需轉向契約測試(contract testing)等新做法
  • 延遲(Latency):原本行程內呼叫變成跨網路呼叫——需要量測(distributed tracing 工具有幫助),並對「夠快即可」做明確界定
  • 資料一致性:無法依賴單一資料庫交易(transaction),需採用 Saga、最終一致性(eventual consistency)等模式

該不該用微服務?#

微服務是一種架構選擇,不是唯一的架構選擇。要先評估自身問題、技能與技術環境。

不適合的場景#

  • 新創公司或新產品:領域模型還在劇烈變動,過早切分服務邊界代價高昂——Uber 一開始做的是禮車、Flickr 是從多人線上遊戲分支出來的,最終產品常與初始想像差很遠
  • 小團隊:所謂「微服務稅(microservice tax)」會吃掉珍貴的開發頻寬;五人團隊有一人專職處理微服務基礎設施,比例偏高
  • 客戶自行部署的軟體:沒辦法要求客戶會用 Kubernetes;從「Windows installer」轉成「請放 20 個 pod 到你的 Kubernetes 集群」會讓客戶崩潰

適合的場景#

  • 規模成長中的組織:採用微服務可以讓更多開發者平行工作、減少交付競爭——五人新創難受其益,百人成長型公司則受益甚大
  • SaaS 產品:需要 24/7 運作、彈性伸縮,與獨立可部署性高度互補
  • 想充分利用雲端:不同服務挑不同的雲端部署選項(FaaS、PaaS、VM)
  • 數位轉型:解鎖既有系統的功能,提供新的客戶體驗(多通路、多裝置)

小結#

  • 微服務的核心價值在於彈性:技術、韌性、規模、組織編組都能更靈活
  • 但這份彈性帶有顯著的複雜度成本,必須有對應的問題與目標才值得採用
  • 微服務不該是預設選項——遇到問題時,常常有更簡單的解
  • 若能正確理解並落實核心觀念(資訊隱藏、領域建模、獨立部署),微服務確實能讓系統的整體價值大於部件加總