系統穩定代表它能在已定義的處理量與失敗情境下維持必要功能,或在允許時間內恢復。增加執行副本、拆分服務或導入容器編排平臺,只能處理其中一部分問題。程式錯誤、不正確的重試、並行更新衝突及無法復原的保存資料,不會因為系統拆得更細就消失。
設計目標系統時,應該先把穩定需求改寫成可檢查條件,再判斷單一部署單位是否已經無法滿足需求。只有獨立部署、獨立擴充、故障隔離或跨地區運作等需求具有明確價值時,才需要承擔分散式架構(Distributed Architecture)新增的通訊與維運成本。
「系統要很穩定」無法用來選擇架構。相同的中斷對不同系統可能具有完全不同的影響,因此要先記錄必要功能、限制值及失敗後的預期結果。
| 確認面向 | 要回答的問題 | 可檢查結果範例 |
|---|---|---|
| 必要功能 | 發生部分故障時,哪些功能仍要繼續運作?哪些功能可以暫停? | 指定相依項目失效時,必要功能仍能完成或回覆明確狀態 |
| 中斷時間 | 每次故障允許中斷多久?多久內要恢復? | 從偵測故障到恢復必要功能的時間沒有超過限制 |
| 保存結果 | 可以遺失多少已完成變更?復原後要如何確認內容正確? | 復原結果符合已核准的資料範圍與一致性檢查 |
| 處理能力 | 日常與尖峰情境需要完成多少工作?允許多少延遲與失敗? | 在指定處理量下,回應時間與失敗比例符合門檻 |
| 功能降級 | 相依項目暫時無法使用時,可以停止、改用較簡單流程或稍後處理嗎? | 每種降級方式都有觸發條件、對外結果及恢復方式 |
| 變更影響 | 發布一項功能時,可以中斷或重新部署哪些部分? | 指定變更能在允許範圍內完成部署與驗證 |
這些條件要連回已確認的需求與風險。沒有可檢查結果時,「高可用性」或「自動擴充」只是一項技術描述,無法證明架構改善了甚麼。
分散式架構適合處理特定的部署與執行邊界問題,無法代替一般的程式品質與復原設計。決定拆分前,應該根據測試結果、執行記錄與故障紀錄,將問題分成可以採取不同處理方式的類型:
如果問題來自錯誤的功能規則,拆成多個服務只會讓錯誤經過網路傳播。如果瓶頸來自共同保存機制,單純增加程式副本也可能讓競爭更嚴重。必須先找出限制發生的位置,才能判斷要改善程式、調整部署單位或改變架構。
模組化單體(Modular Monolith)在程式內建立清楚的模組責任,正式執行時仍維持單一部署單位。微服務(Microservices)則將部分責任設計成可以獨立建置、部署及運作的服務。兩者都能建立良好邊界,主要差異在於邊界是否跨越程序與通訊環境。
| 比較面向 | 模組化單體 | 微服務 |
|---|---|---|
| 開發 | 可以在同一程式中修改與重新整理模組介面 | 跨服務變更要管理介面契約、版本與相容順序 |
| 部署 | 所有模組共用建置成品與發布時程 | 每個服務可以獨立部署,但要管理多個版本與設定 |
| 擴充 | 通常增加整個部署單位的執行數量或資源 | 可以只擴充受影響服務,但跨服務相依仍可能形成瓶頸 |
| 故障隔離 | 同一程序的資源耗盡或未處理錯誤可能影響整體 | 程序邊界可以限制直接影響,但逾時與重試仍可能造成連鎖故障 |
| 資料一致性 | 同一保存邊界內較容易使用單一交易完成變更 | 跨服務變更通常要處理部分成功、延遲同步與補償操作 |
| 測試 | 可以在同一執行環境驗證多個模組的組合 | 除了各服務測試,還要驗證契約、網路失敗與不同版本組合 |
| 問題調查 | 呼叫與狀態通常集中在較少執行單位 | 要以關聯識別碼、集中記錄與分散式追蹤串連處理過程 |
| 維運 | 部署單位、設定與監控對象較少 | 還要處理服務探索、通訊保護、個別容量與故障復原 |
Microsoft 的微服務架構說明也將獨立部署、資料擁有權與擴充能力列為微服務的特性,同時指出服務間通訊、資料一致性、測試及問題調查會增加整體複雜度。架構選擇應該比較完整成本,不能只比較單一服務的程式碼大小。
當需求仍在變動、模組邊界尚未穩定,或所有功能必須一起發布與驗收時,模組化單體通常能用較低成本保留清楚責任。等到實際結果顯示某個邊界需要獨立運作,再評估拆成服務,也能避免一開始就建立大量不穩定的通訊介面。
程式中的類別、資料夾或模組不會自動成為適當的服務。候選服務至少要能回答下列問題:
只符合「程式碼很多」或「名稱看起來像一個模組」不構成拆分理由。如果兩個候選服務經常需要同時修改、共同查詢內部結構,而且不能分開驗收,跨程序邊界會把原本的程式內耦合改成較難發現的通訊耦合。
服務能獨立演進的前提,是每項狀態變更都有明確擁有者。其他服務可以透過已定義的互動方式取得結果或提出變更要求,不應該直接修改擁有者的內部保存結構。
「一個服務擁有資料」不代表每個服務都必須採用不同的資料庫產品,也不要求使用不同的實體執行環境。即使因部署限制而共用同一個保存平臺,也應該分開結構與寫入責任,避免一項結構變更迫使所有服務同時調整。Microsoft 的微服務資料設計說明也將避免共用結構與直接跨服務讀寫列為降低耦合的重點。
單一功能如果必須同步修改多個服務的狀態,代表架構需要處理部分成功。可以先重新檢查服務邊界,確認這些變更是否其實屬於同一個一致性範圍。如果確實需要跨服務流程,再定義每一步的擁有者、可重試條件、完成狀態與補償操作。最終一致性(Eventual Consistency)、訊息傳遞及交易寄件匣模式(Transactional Outbox Pattern)的詳細設計會由後續章節說明。
程序內呼叫通常只有成功或拋出例外。跨服務呼叫還可能遇到連線失敗、回應延遲、對方已完成但回覆遺失,以及不同版本暫時並存。呼叫端不能無限等待,也不能把所有失敗都直接重試。
| 處理方式 | 要解決的問題 | 設計時要確認的內容 |
|---|---|---|
| 逾時 | 避免呼叫長時間占用處理能力 | 每一段呼叫的時間上限、整體操作期限及逾時後結果 |
| 有限重試 | 處理短暫且再次執行可能成功的失敗 | 可重試錯誤、次數上限、退避間隔及整體重試額度 |
| 冪等性(Idempotency) | 避免相同操作因重試而重複產生效果 | 操作識別碼、重複判斷範圍及既有結果的回覆方式 |
| 斷路器(Circuit Breaker) | 暫停持續呼叫已經失效的相依項目 | 開啟條件、等待時間、試探方式及恢復條件 |
| 艙壁模式(Bulkhead Pattern) | 限制一項相依故障耗盡所有共用處理能力 | 可以隔離的工作佇列、連線、並行數量或執行單位 |
| 降級與補償 | 在完整結果暫時無法取得時維持可接受行為 | 可省略功能、待處理狀態、補償動作及人工介入條件 |
Azure 暫時性故障處理建議指出,重試要有次數與總量限制,並且避免大量請求以固定間隔持續重試。艙壁模式則用隔離資源的方式限制故障擴散。這些機制要按照實際失敗語意組合,重試無法修復的錯誤只會增加相依項目負擔。
同步呼叫適合需要立即取得結果,而且呼叫端能接受等待與失敗的情境。非同步訊息傳遞可以分開處理時間,但會新增訊息重複、順序、積壓與延遲結果。兩者都要先定義失敗後的狀態,不能只用通訊方式判斷可靠性。
增加相同程式的執行副本後,多個操作可能同時讀取相同狀態並寫入不同結果。這種衝突在單一部署單位也可能發生,只是在水平擴充後更容易出現。處理方式應該放在真正擁有狀態的邊界,避免每個呼叫端各自實作不同規則。
| 處理方式 | 適合評估的情況 | 必須驗證的失敗情境 |
|---|---|---|
| 原子操作或唯一限制 | 保存機制可以直接完成不可分割的檢查與更新 | 重複要求、同時建立及限制衝突時的明確結果 |
| 樂觀鎖定(Optimistic Locking) | 衝突不常發生,可以使用版本值判斷讀取後是否遭修改 | 版本不符時中止更新、重新讀取及有限次重試 |
| 悲觀鎖定(Pessimistic Locking) | 同一狀態同時修改會造成不可接受結果,需要依序執行 | 等待上限、鎖定範圍、死結處理及持有者失敗 |
| 分散式鎖定(Distributed Locking) | 多個執行者必須協調保存邊界以外的互斥工作 | 鎖逾時、續期、錯誤釋放、網路分割及互斥保證 |
優先使用保存機制提供的原子操作或衝突控制,因為它最接近狀態擁有者。只有工作跨越單一保存交易,而且確實需要互斥時,才評估分散式鎖定。鎖定也不能代替冪等性,因為呼叫端仍可能在結果不明時再次送出相同操作。
如果系統已確認使用 Redis,Redis 交易說明中的 WATCH 可以監看鍵值是否在交易執行前遭到修改,衝突時中止交易,屬於樂觀鎖定的做法。需要跨多個執行者使用 Redis 鎖定時,還要按照Redis 分散式鎖定說明驗證期限、釋放、故障與互斥條件。加入 Redis 本身不會自動消除並行更新衝突。
如果目標系統已確認採用容器、具有多個可替換副本,而且需要由叢集維持預期執行狀態,可以評估 Kubernetes。它能協助調度工作負載、維持副本數量、替換失效的 Pod,並且把服務互動導向可用端點。Kubernetes 自我修復說明也明確區分平臺可以替換失效容器,但應用程式本身的錯誤仍要另外處理。
在這種情境下,負載分流與自動擴充仍需要應用程式配合:
Kubernetes 水平 Pod 自動擴充會根據已設定的資源或自訂指標調整目標工作負載的副本數量。它不會替目標系統決定容量門檻,也不會修正過慢的功能規則、錯誤的資料一致性設計或無上限的重試。
微服務讓部署單位可以獨立變更,也把原本由程式內環境提供的能力轉成需要明確設計與維護的系統功能。採用前,至少要估算下列工作:
這些工作如果沒有明確負責人與可執行流程,服務數量增加後會直接降低變更速度與故障處理能力。維運能力也是架構限制,不能等到正式部署後才補上。
架構圖只能顯示預定邊界,無法證明獨立擴充與故障隔離真的有效。可以選擇一個最有拆分理由的候選模組進行概念驗證(Proof of Concept, POC),並且與模組化單體方案使用相同條件比較:
如果拆分後不能獨立擴充、不能隔離故障,或每次變更仍要同步部署多個服務,就沒有達成原本的架構目標。此時應該保留模組邊界並維持較簡單的部署方式,等重新評估條件成立後再開啟決策。
完成需求確認與 POC 後,使用架構決策紀錄(Architecture Decision Record, ADR)保存採用或不採用分散式架構的理由。
| ADR 欄位 | 應記錄的內容 |
|---|---|
| 決策問題 | 哪一項穩定、部署或擴充問題需要架構處理 |
| 已確認條件 | 中斷、恢復、處理量、發布、故障隔離與資料一致性要求 |
| 候選方案 | 單一應用程式、模組化單體、部分獨立服務或其他可行方案 |
| 邊界與擁有權 | 每個候選服務的責任、互動契約、資料擁有者及失敗範圍 |
| 驗證結果 | POC 的容量、故障、部署、測試、復原及問題調查結果 |
| 選擇與代價 | 採用方案、解決的問題、新增工作及仍存在的風險 |
| 重新評估條件 | 發布頻率、處理量、故障影響或責任分工達到何種條件時重新比較 |
「微服務比較有彈性」或「Kubernetes 可以自動擴充」不足以形成決策。ADR 要能指出哪一項需求、哪一個候選邊界及哪一組驗證結果支持選擇,也要記錄不採用分散式架構的理由。
決定目標系統的部署架構前,可以使用下列問題確認: