📌 30 秒摘要(Layer 3)
微服務的權威實作指南。它不只講「怎麼拆」,更講「該不該拆、拆的代價」。核心洞見兩條:服務邊界要對齊業務領域(用 DDD 的限界上下文找切點);組織結構會決定系統結構(康威定律)——所以拆服務也是在拆團隊。其餘的分散式取捨(容錯、事務、部署)與 鳳凰架構 共通,見既有工具。
🗺 心智圖(Canvas)
微服務設計
Link to original 微服務設計
Sam Newman|該不該拆、拆的代價
🧭 邊界對齊業務領域 高內聚低耦合
🎯 什麼情境該想到我
當系統/模型越長越大越糊,或發現「同一個名詞在不同部門意義不同」時。
⚙️ 怎麼用
- 劃出邊界:在某個邊界內,一套模型與 工具-通用語言 保持一致、無歧義。
- 一個詞在不同上下文意義不同 → 就該分成不同上下文(例:「商品」在銷售 vs 物流意義不同)。
- 用 Context Map 描述上下文之間的關係(上下游、防腐層 ACL、共享核心…)。
- 邊界也是團隊、部署(含微服務拆分)的自然切點。
★ 核心洞察:同一個詞,在不同上下文是不同的東西
「訂單 Order」 銷售上下文:客戶、商品、金額、折扣 ← 關心「賣了什麼」 倉儲上下文:SKU、數量、重量、儲位 ← 關心「拿什麼出貨」 財務上下文:應收金額、稅、發票號碼、帳期 ← 關心「錢怎麼算」 ❌ 建一個「統一的 Order 模型」滿足三方 → ★ 30 個欄位,每一方只用 10 個,但每次改動都要三方一起 review → 這就是「統一模型」必然的下場★ 限界上下文的主張是反直覺的:不要追求統一模型,而是承認「同一個名詞有多個模型」,並明確劃出邊界。
邊界內:術語一致、模型統一(這就是 工具-通用語言)。
邊界外:透過明確的轉換來往,不共用模型。★ 怎麼找出邊界(四個實用訊號)
訊號 說明 ★ 語言的斷裂 同一個詞在兩群人口中意思不同 → 那裡就是邊界 ★ 一起改變的東西 用 git log看哪些檔案總是一起被改(見 工具-影響分析)★ 組織結構 不同團隊負責的部分天然會形成邊界(工具-康威定律) 交易一致性的需求 ★ 必須在同一個交易裡的東西,不該跨上下文 ★ 最後一條特別重要:如果你發現「切開之後需要分散式事務」,
那八成是切錯了(見 工具-分散式事務的取捨)。★ 上下文之間的七種關係(上下文對應 Context Map)
關係 意思 何時 ★ 防腐層 ACL 我方寫一層翻譯,把對方的模型轉成我的 ★ 對方模型很糟或會變時的標準做法 開放主機服務 OHS 我提供一個公開的、穩定的協定給所有下游 下游很多時 發布語言 PL 用一個大家同意的中介格式(Protobuf schema、事件定義) 配合 OHS 客戶-供應商 下游能影響上游的排程 同公司內 追隨者 下游完全接受上游的模型,不做轉換 ★ 上游穩定且模型合用時(省事,但耦合) 共享核心 兩個上下文共用一小塊模型與程式碼 ⚠️ 要非常小心,見下 各行其道 完全不整合 沒有整合需求時 ★ 共享核心是最危險的一個:
共用的那一塊同時屬於兩個團隊,改它要兩邊同意。
它會慢慢長大,最後變成一個誰都不敢改的巨石。
→ 預設用防腐層;只在「共用的部分極小且極穩定」時才考慮共享核心。★ 邊界 ≠ 微服務
限界上下文 = ★ 邏輯邊界(模型與語言的邊界) 微服務 = ★ 部署邊界(可獨立部署的單元)★ 一個限界上下文「可以」是一個微服務,但不必是。
在單體裡用模組/套件劃出限界上下文,是完全正確且通常更務實的做法
——你拿到了模型的清晰,卻不用付分散式系統的代價。★ 先在單體內劃清邊界,等邊界被驗證穩定了,再考慮拆成服務(見 工具-服務邊界與演進式拆分)。
★ 實際長什麼樣(Go)
internal/ sales/ ← ★ 限界上下文 order.go type Order struct { CustomerID, Items, Discount ... } warehouse/ ← ★ 另一個限界上下文 order.go type Order struct { SKUs, Weight, Location ... } ★ 同名不同型別,這是對的 acl.go ★ 防腐層:把 sales 的事件翻譯成 warehouse 的模型 billing/ invoice.go★ 關鍵:
warehouse不 importsales的型別,只透過事件或 ACL 取得它需要的資訊。
這可以用架構測試自動驗證(見 工具-好萊塢原則與依賴反轉)。🧪 我實際套用的紀錄
- 2026-07-14:(待填)
⚠️ 注意 / 什麼時候不適用
- ★ 小系統不需要。整個團隊五個人、一個產品、術語一致 → 一個上下文就好。
限界上下文解決的是「不同群人對同一個詞有不同理解」的問題——沒有這個問題就不需要它。- ★ 一開始就切邊界通常會切錯。你還不夠了解領域。
→ 先寫成一個模組化的單體,讓邊界在實作中浮現(見 工具-服務架構的演進與拆分)。- ★ 「重複」是刻意的,不是疏失。兩個上下文各有一個
Order,這不是違反 DRY——
它們是不同的概念碰巧同名(見 工具-DRY原則 對「知識的重複 vs 程式碼的重複」的區分)。
硬要合併才是錯的。- 邊界要有人守。沒有 review 紀律或架構測試,
import會慢慢穿透邊界,最後回到泥球。- 上下文對應要畫出來並維護。它是團隊之間的契約,不是一張畫完就丟的圖。
- 不要為了「以後好拆微服務」而過度切割。切太細會讓每個改動都要跨好幾個上下文。
🔗 相關工具
- 工具-通用語言 —— 邊界內的語言一致性
- 防腐層 —— 最常用的整合方式
- 工具-服務邊界與演進式拆分 —— 從邏輯邊界到部署邊界
- 工具-康威定律 —— 組織結構會決定邊界
- 工具-分散式事務的取捨 —— ★ 需要跨服務事務 = 邊界切錯了
- 工具-實體值物件與聚合 —— 上下文內部的建模
- 工具-DRY原則 —— 為什麼「重複」有時是對的
- 領域驅動設計
🎯 什麼情境該想到我
當你要決定「一個微服務該多大、邊界畫在哪、從單體怎麼拆」時。
⚙️ 怎麼用
- 按業務能力/限界上下文切(見 工具-限界上下文),不按技術分層切。
- 檢驗好邊界:服務能獨立變更、獨立部署嗎?若一個改動總要同時改好幾個服務 → 邊界錯了。
- 每個服務擁有自己的資料:不共用資料庫,透過 API/事件互動(避免隱性耦合)。
- 服務間別太聊天:若兩服務呼叫極頻繁,可能該合併——高內聚該在同一服務內。
- 演進式拆分:從單體開始,沿著真實痛點與清楚的接縫逐步剝離(見 工具-架構隨業務規模演進)。
📌 這一組三張卡:工具-服務架構的演進與拆分=「有哪些選項」|工具-架構隨業務規模演進=「什麼時候動」|本卡=「切在哪、怎麼切」
★ 檢驗邊界對不對的五個問題
① ★ 能獨立部署嗎? —— 每次發布都要和別的服務按順序一起上 → ★ 分散式單體,邊界錯了 ② ★ 一個典型的功能改動,會動到幾個服務? —— 常常要改 3 個以上 → 邊界橫切了業務能力 ③ ★ 服務之間的呼叫頻率高嗎? —— 一個請求要來回呼叫 5 次 → ★ 高內聚的東西被切開了,該合併 ④ ★ 需要跨服務的交易嗎? —— 需要 → 聚合被切開了(見 [[工具-分散式事務的取捨]]) ⑤ ★ 資料是各自擁有的嗎? —— 共用同一張表 → 沒有真的拆開,只是兩個程式連同一個 DB★ 第 ① 題是最實用的單一判準:「能不能只部署這一個?」
★ 絞殺者模式(Strangler Fig):怎麼安全地剝離
① 在單體前面放一層「門面」(proxy / API gateway / 路由層) Client ──▶ [Facade] ──▶ 單體 ② 建新服務,門面把「一小部分流量」導過去(先唯讀、先內部使用者) Client ──▶ [Facade] ─┬─▶ 單體 └─▶ 新服務 ← ★ 可隨時切回 ③ ★ 雙寫 + 對帳:兩邊都寫,比對結果,確認新服務行為一致 ④ 逐步提高流量比例,觀察指標 ⑤ 舊路徑流量歸零後,★ 觀察一段時間再刪除★ 關鍵是「每一步都可以立刻回退」。
大爆炸式重寫(big bang rewrite)失敗率極高,因為你在一個無法驗證的狀態下走了幾個月。★ 最難的部分:拆資料
❌ 兩個服務共用同一個資料庫 → ★ 這不是微服務,是「兩個程式共用一個全域變數」 任一方改 schema 都會弄壞對方,而且沒有編譯器會警告你拆資料的步驟:
① 先拆「程式碼層」:所有存取都走一個 repository 介面(見 [[工具-接縫]]) ② 拆「schema 層」:不同的表分開、拿掉跨表的外鍵與 JOIN ③ ★ 拆「實體層」:真正搬到不同的資料庫 ④ 需要對方的資料時 → API 呼叫、事件複製、或 CQRS 讀模型★ 步驟 ② 通常最痛:
JOIN不見了,要在應用層組合資料。
這也是為什麼「需要頻繁 JOIN 的資料不該被切開」——它是高內聚的訊號。★ 切錯的三種典型形態
錯誤切法 症狀 正解 ★ 按技術分層切 api-service/logic-service/data-service
→ 每個功能都要改三個按業務能力切 ★ 切得太細(奈米服務) 20 個服務,每個 200 行;一個請求跨 8 個服務 合併。服務的大小該由「團隊能負責的範圍」決定 ★ 切在聚合中間 需要分散式事務才能維持不變式 聚合不可切開(見 工具-實體值物件與聚合) ★ 服務之間該怎麼通訊
方式 何時 同步 HTTP/gRPC ★ 需要立即的回應、且呼叫鏈短 ★ 非同步事件 預設優先考慮:解耦、可重放、天然支援最終一致 共用資料庫 ❌ 不要 檔案/批次 大量資料的定期同步 ★ 同步呼叫會傳遞失敗:A → B → C,C 掛了 A 也掛。
每一層同步呼叫都在降低整體可用性(可用性相乘)。
→ 能非同步就非同步(見 工具-訊息傳遞整合模式)。★ 拆之前先問:能不能不拆
合併回單體也是一個正當的架構決定。
如果兩個服務總是一起改、一起部署、互相頻繁呼叫——它們本來就是一個東西。
★ 「拆了之後發現不對就合併回去」不是失敗,是正確的演進。🧪 我實際套用的紀錄
- 2026-07-15:(待填)
⚠️ 注意 / 什麼時候不適用
- ★★ 不要大爆炸重寫。用絞殺者模式,每一步可回退。
- ★ 共用資料庫 = 沒有拆開。這是最常見的假微服務。
- ★ 拆分期間會同時維護兩套,成本更高。要有明確的完成期限與「刪除舊路徑」的任務,
否則會永遠停在中間狀態——那是最糟的狀態。- 雙寫期間的一致性:兩邊寫入可能不一致 → 一定要有對帳(見 工具-分散式事務的取捨)。
- ★ 邊界會改變。業務變了,昨天對的邊界今天就錯了。保持能再切一次的能力,
而不是追求「一次切對」。- 拆分的收益要能說清楚。「為了微服務」不是收益;「讓 A 團隊不用等 B 團隊部署」才是。
- 先在單體內劃清邊界(模組化單體)再拆——這樣切錯的成本只是改資料夾。
🔗 相關工具
- 工具-限界上下文 —— 邏輯邊界是拆分的依據
- 工具-服務架構的演進與拆分 —— 各種架構的代價
- 工具-架構隨業務規模演進 —— 什麼時候該動
- 工具-接縫 —— 剝離的第一步:先在程式碼層建立接縫
- 工具-實體值物件與聚合 —— ★ 聚合不可切開
- 工具-分散式事務的取捨 —— 切開之後的代價
- 工具-訊息傳遞整合模式 —— 非同步通訊
- 工具-低風險發布模式 —— 絞殺者的流量切換
👥 組織決定架構 康威定律
🎯 什麼情境該想到我
當你發現「系統架構總是長得像組織圖」、或跨團隊協作成本高到架構改不動時。
⚙️ 怎麼用
「設計系統的組織,其產出的架構會複製該組織的溝通結構。」
- 想要某種架構,先設計對應的團隊結構(逆康威):想要獨立的微服務,就要有能獨立負責的小團隊。
- 一個服務由一個團隊擁有:邊界對齊團隊,減少跨團隊的協調摩擦。
- 團隊之間的介面 = 服務之間的介面:把高頻溝通留在團隊內,團隊間走清楚的契約。
- 組織重組時,預期架構也會(該)跟著變。
★ 原始陳述與它真正的含意
“Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations.”
—— Melvin Conway, 1967白話:系統的架構,會長成你的組織溝通結構的樣子。
為什麼必然如此:
兩個模組要能協作 → 兩群人要能溝通 溝通成本高的兩群人 → ★ 會傾向把介面定義得「簡單、少變動」 溝通成本低的一群人 → ★ 會傾向讓模組之間緊密耦合(因為改起來很方便)★ 這不是「應該」的規範,是「已經發生」的觀察。
你的架構圖畫什麼不重要;真正決定架構的是「誰跟誰能輕鬆講上話」。★ 逆康威手法(Inverse Conway Maneuver)
既然組織結構會決定架構,那就「先改組織,讓它長成你要的架構」。
想要:三個獨立部署、鬆耦合的服務 ❌ 做法:一個大團隊分工寫三個服務 → ★ 因為他們天天在一起,介面會越切越細、越耦合,最後還是一坨 ✅ 做法:先分成三個「有完整能力、能獨立出貨」的團隊 → 跨團隊溝通有成本 → 自然會把介面定得粗、穩定、少變動★ 這是組織設計成為技術決策的少數場合之一,而且它的效果遠大於任何架構規範文件。
★ 「完整能力團隊」的判準
❌ 依技能分(水平切) ✅ 依價值流分(垂直切) 前端組、後端組、DBA 組、QA 組 ★ 每個團隊有前端、後端、資料、測試 ★ 一個功能要跨四個組排程 ★ 一個團隊能從需求到上線自己走完 交接多、等待多 交接少 ★ 沒人對「結果」負責 團隊對結果負責 ★ 判準:這個團隊能不能「自己把一個功能送到使用者手上」?
不能 → 它會產生大量的跨團隊協調成本,而那個成本最終會以「耦合的架構」的形式固化下來。★ 團隊拓撲(Team Topologies)的四種團隊
類型 做什麼 ★ 串流對齊團隊(stream-aligned) 對某條價值流負責,端到端。大多數團隊都該是這種 賦能團隊(enabling) 幫其他團隊提升能力,不代做 複雜子系統團隊 需要專業知識的部分(推薦演算法、風控引擎) ★ 平台團隊 提供自助服務的內部平台,降低其他團隊的認知負荷 ★ 平台團隊的關鍵是「自助」:
如果其他團隊要「開單請平台團隊幫忙」,那它不是平台,是瓶頸
——而且這個瓶頸會在架構上表現為「所有服務都依賴某個中心元件」
(見 自助服務平台)。★ 認知負荷才是團隊規模的真正限制
一個團隊能負責多少東西,取決於它的認知負荷上限,不是人數。
團隊負責的服務越多、技術棧越雜、領域越廣 → ★ 認知負荷超載 → 品質下降、交付變慢、人員流失★ 這解釋了「兩個披薩團隊」為什麼有效——不是因為 6–8 人這個數字神奇,
而是因為超過這個規模,溝通路徑(n(n−1)/2)與認知負荷就開始失控
(見 兩個披薩團隊、工具-布魯克斯定律)。降低認知負荷的手段:縮小團隊負責的領域、提供好的平台與工具、減少團隊需要知道的「別人的細節」(這正是好的邊界在做的事)。
🧪 我實際套用的紀錄
- 2026-07-15:(待填)
⚠️ 注意 / 什麼時候不適用
- ★ 逆康威手法的代價很高。改組織會影響人的職涯、歸屬感與士氣。
不要為了一個架構偏好就重組——要有清楚的業務理由。- ★ 改組織不會自動改好架構。既有的程式碼不會因為組織變了就重構——
它只是讓「往正確方向演進」變得比較容易。 還是要有人去做那個重構。- 康威定律是描述性的,不是規範性的。它告訴你「會發生什麼」,不告訴你「該怎麼組織」。
- ★ 地理與時區也是溝通結構。跨時區的團隊之間,介面自然會變粗——
這可能是好事(強制解耦),也可能是災難(需要頻繁協調的部分被硬切開)。- 遠端工作改變了「溝通成本」的結構,所以也改變了康威定律的作用方式。不要照搬 1967 年的假設。
- 小團隊不適用。十個人的公司,康威定律說的是「你們會產生一個單體」——而那是對的。
🔗 相關工具
- 工具-限界上下文 —— 組織邊界與模型邊界的對應
- 工具-服務邊界與演進式拆分 —— 架構邊界怎麼演進
- 兩個披薩團隊 —— 團隊規模與溝通路徑
- 工具-布魯克斯定律 —— 加人不會讓延遲的專案更快
- 自助服務平台 —— 平台團隊不該是瓶頸
- 康威定律作為設計工具 —— DevOps 視角的同一個概念
- 工具-心理安全感 —— 團隊能不能有效溝通的前提
- 人月神話
🌱 演進式拆分 先單體,痛了再拆
🎯 什麼情境該想到我
當你在猶豫「要不要從單體走向微服務、怎麼拆」,或被「微服務很潮」帶風向時。
⚙️ 怎麼用
- 認清各階段的取捨:單體(簡單、部署一體)→ SOA → 微服務(獨立部署/擴展,但引入分散式複雜度)→ 無伺服器。微服務不是免費的——你用開發簡單度換來運維與分散式的複雜度。
- 先具備前置條件再拆:自動化部署、監控、服務治理都到位,才適合微服務。
- 按業務邊界拆,而非技術分層——切點對齊 工具-限界上下文。
- 能單體就先單體(modular monolith),真的遇到擴展/團隊瓶頸再拆。
📌 這一組有三張卡,分工如下:
本卡=「有哪些選項、各自的代價」|工具-架構隨業務規模演進=「什麼時候該演進」|工具-服務邊界與演進式拆分=「切在哪、怎麼切」★ 四種架構的代價表
單體 模組化單體 微服務 無伺服器 部署 一次全部 一次全部 ★ 獨立 獨立 本地開發 ★ 跑一個就好 ★ 跑一個就好 ⚠️ 要跑一堆或用 mock 難 跨模組重構 ★ 一次 commit,編譯器幫你檢查 ★ 同左 ⚠️ 跨服務的改動要協調部署順序 同微服務 除錯 ★ 一份堆疊追蹤 ★ 同左 ⚠️ 需要分散式追蹤 更難 交易 ★ 本地 ACID ★ 同左 ⚠️ 分散式事務(見 工具-分散式事務的取捨) 同微服務 獨立擴展 ❌ ❌ ★ 可以 ★ 自動 團隊自主 ❌ ⚠️ 有限 ★ 高 高 技術異質 ❌ ❌ ★ 可以(但通常不該) 可以 運維複雜度 ★ 低 ★ 低 ⚠️ 高 中(換成廠商鎖定) ★ 最容易被忽略的是第 3、4、5 列:
微服務讓「跨邊界的改動」從「一次 commit」變成「多個 repo、多次部署、要考慮順序與相容性」。
這個成本天天都要付,而「獨立擴展」的好處多數團隊一年用不到幾次。★ 微服務的前置條件(沒有這些就是自找麻煩)
□ ★ 自動化部署(每個服務都能一鍵、安全地上線與回滾) □ ★ 集中式日誌 + 分散式追蹤(不然事故時完全瞎) □ 監控與告警(每個服務都有) □ ★ 服務發現與設定管理 □ 契約測試或 schema 相容性檢查(見 [[工具-資料編碼與演進]]) □ ★ 團隊真的能獨立負責一個服務(見 [[工具-康威定律]])★ 沒有這六項就拆微服務 = 把「一個難維護的系統」變成「N 個難維護的系統 + 一個難懂的網路」。
→ 這正是「分散式單體」的定義:拆開了,但還是要一起部署。★ 模組化單體:最被低估的選項
internal/ sales/ ← 邊界清楚,只透過公開介面往來 warehouse/ billing/ cmd/server/ ← 一個二進位檔
你拿到了 你避開了 ★ 模型與團隊的邊界清晰 ❌ 網路呼叫的延遲與失敗 ★ 編譯期就能驗證邊界(架構測試) ❌ 分散式事務 ★ 本地交易、本地重構 ❌ 部署協調 ★ 需要時可以再拆出去 ❌ 運維成本 ★ 「先模組化單體,被驗證穩定的邊界才拆出去」是截至 2026-10 業界最主流的建議(判斷,非統計),
而且它讓你用低成本試錯邊界——切錯了只要改個資料夾,不是改一個 repo。★ 什麼時候真的該拆
拆的正當理由 不是理由 ★ 團隊互相擋住彼此的部署 「微服務比較現代」 ★ 某個部分的資源需求差異極大(ML 推論要 GPU、影片轉檔要 CPU) 「想試試 Kubernetes」 ★ 不同的可用性/合規要求(金流要更嚴的稽核) 「檔案太大了」 不同的發布節奏(一個一天十次、一個一季一次) 「聽說單體不好」 需要用不同語言(真的有技術理由) 「想用 Rust」 ★ 注意共同點:前四個都是「組織或營運的理由」,不是技術的理由。
★ 無伺服器的真實取捨
✅ ❌ 零運維、自動擴縮、按用量計費 ★ 冷啟動(延遲敏感的服務不適合) 事件驅動天然契合 ★ 執行時間上限 突發流量友善 ★ 廠商鎖定、本地測試困難 ★ 低流量時極省錢 ★ 高且穩定的流量時反而比 VM 貴 🧪 我實際套用的紀錄
- 2026-07-15:(待填)
⚠️ 注意 / 什麼時候不適用
- ★★ 不要一開始就微服務。你還不知道邊界在哪,而錯的邊界比單體更難改(跨服務重構極貴)。
- ★ 微服務不會讓程式碼變乾淨。爛的設計拆成十個服務只會變成十份爛設計 + 網路問題。
先把模組邊界理清楚(那在單體裡就能做)。- ★ 「分散式單體」是最糟的結果:拆開了,但每次發布都要按順序一起部署。
判準:能不能只部署其中一個服務?不能 → 你只是把方法呼叫換成了 HTTP。- 技術異質是雙面刃。五種語言 = 五套工具鏈、五種監控接法、人員無法流動。
★ 多數團隊應該「刻意限制」技術選擇(見 技術選型收斂)。- ★ 拆分是難以逆轉的。合併回去比拆開更痛。所以要在「有足夠證據」時才拆。
- 團隊規模是硬限制。少於 15–20 人基本不該考慮微服務——你沒有足夠的人來維運。
🔗 相關工具
- 工具-架構隨業務規模演進 —— ★ 什麼時候該演進(觸發訊號)
- 工具-服務邊界與演進式拆分 —— ★ 切在哪、怎麼切(絞殺者模式)
- 工具-限界上下文 —— 邏輯邊界(拆分的依據)
- 工具-康威定律 —— 組織結構決定可行的架構
- 工具-分散式事務的取捨 —— 拆分後最大的技術代價
- 技術選型收斂 —— 為什麼不該濫用技術異質
- 工具-部署管線與持續交付 —— 前置條件
- 微服務設計
⚙️ 分散式的代價 容錯 × 事務 × 分流
🎯 什麼情境該想到我
當一個下游服務變慢/掛掉,就把整條鏈路甚至整個系統拖垮(雪崩)時。
⚙️ 怎麼用(組合使用)
- 熔斷(Circuit Breaker):偵測到下游持續失敗就「跳閘」,快速失敗、暫停呼叫,給它時間恢復,避免堆積。
- 艙壁隔離(Bulkhead):把資源(執行緒池/連線)按依賴分艙,一個依賴爆掉不會吃光全部資源。
- 重試 + 退避:對暫時性失敗重試,但要指數退避 + 上限 + 冪等,避免重試風暴(見 工具-MQ消費端防禦三原則)。
- 降級(Fallback):失敗時回傳合理的預設/快取結果,保住核心體驗。
- 流量控制(限流):超過容量就擋,保護系統不被打垮。
★ 為什麼需要它:連鎖故障的機制
下游變慢(不是掛,只是慢) → 你的請求堆積,執行緒/連線被佔住 → ★ 你自己的資源耗盡 → 你也開始變慢 → 上游的請求在你這裡堆積 → 上游也垮 → ★ 整條鏈一起倒★ 最危險的不是「下游掛了」,是「下游變慢」。
掛了會快速失敗(連線被拒),資源立刻釋放;慢會持續佔住資源直到耗盡。
所以逾時是第一道防線,比斷路器更基本。★ 五種手段的正確順序(由外而內)
限流 Rate Limit ← ★ 最外層:擋掉超過容量的流量,保護自己 艙壁 Bulkhead ← 隔離資源,一個依賴爆掉不影響其他 斷路器 Circuit Breaker ← 下游持續失敗就快速失敗 逾時 Timeout ← ★ 必須在重試外層,否則重試會超出總時限 重試 Retry ← 只對暫時性錯誤 實際呼叫★ 順序錯了會失效:Retry 包在 Timeout 外面 → 總時間變成
n × timeout,SLO 直接破
(見 裝飾者模式 的 middleware 順序)。★ 斷路器的三個狀態與參數
失敗率超過閾值 CLOSED ──────────────▶ OPEN ──等待 cooldown──▶ HALF-OPEN ▲ │ │ │ ★ 試探成功 │ ★ 直接快速失敗 │ 放少量請求試探 └─────────────────────┴────────────────────────┘ 試探失敗 → 回 OPEN★ 它本質上是一個狀態機(見 狀態模式)。
參數 怎麼定 失敗率閾值 常見 50%。★ 要用「比例」不是「絕對次數」,否則低流量時永遠觸發不了 最小請求數 ★ 必要:10 次請求裡錯 5 次不該跳閘(樣本太小) cooldown 幾秒到幾十秒。太短 → 反覆試探打死下游;太長 → 恢復太慢 half-open 的試探量 1~數個。不要一次全放行(會瞬間打垮剛恢復的下游) ★ 「什麼算失敗」要想清楚:逾時、5xx 算;4xx(參數錯)不算(那是你的問題,重試也沒用)。
★ 重試的三條鐵律
鐵律 說明 ① 只重試暫時性錯誤 429、503、逾時、連線失敗 可以;400、401、404、422 絕對不要 ② ★ 必須冪等 非冪等的操作重試 = 重複下單、重複扣款。★ 帶冪等鍵(見 工具-MQ消費端防禦三原則) ③ ★ 指數退避 + jitter 沒有 jitter,所有客戶端會同步醒來再打一次,製造週期性尖峰 // ★ full jitter:業界標準做法 delay := time.Duration(rand.Int63n(int64(min(base<<attempt, maxDelay))))★★ 重試放大是連鎖故障最常見的加速器:
三層服務,每層重試 3 次 → 最底層收到 3³ = 27 倍的流量 ★ 下游已經在掙扎了,你送 27 倍過去 = 徹底打死對策:
- ★ 只在最靠近失敗點的那一層重試,其他層直接往上回報
- 重試預算(retry budget):全域限制「重試佔總請求的比例」(如 10%)
- 尊重
Retry-Afterheader★ 降級要事先設計,不能臨時想
降級層次 例子 回傳快取的舊資料 ★ 最常用。要標示「資料更新於 X」 回傳預設值 / 空結果 推薦系統掛了 → 回熱門商品 關閉非核心功能 購物車能結帳,但「你可能也喜歡」消失 ★ 排隊 / 非同步化 同步做不到 → 收下請求、回 202、之後處理 拒絕 ★ 明確的錯誤 + Retry-After,遠勝於超時等待 ★ 降級開關要能手動觸發(feature flag),不要只靠自動偵測——
事故當下你需要能立刻關掉某個功能,而不是等斷路器自己判斷。★ 艙壁隔離
❌ 共用一個執行緒池/連線池 → 呼叫 A 服務的請求全部卡住 → 池子耗盡 → ★ 呼叫 B 服務的也沒資源了 ✅ 每個下游依賴各自一個池(或 semaphore 限制併發數) → A 爆掉只影響 A 那一艙★ Go 裡最簡單的實作就是每個依賴一個
semaphore(golang.org/x/sync/semaphore或帶緩衝的 channel)。🧪 我實際套用的紀錄
- 2026-07-15:(待填)
⚠️ 注意 / 什麼時候不適用
- ★★ 逾時是第一優先,不是斷路器。 沒有逾時,其他手段全部失效——因為資源永遠不會被釋放。
而且逾時要端到端傳遞(context),不是每層各設一個(見 工具-select超時與競速)。- ★ 重試放大是連鎖故障的加速器(見上)。只在一層重試。
- ★ 斷路器在「低流量」時不可靠。一小時只有 20 次請求時,失敗率的統計意義很弱。
→ 設最小請求數,或對低流量服務不用斷路器。- ★ 降級的品質要能被察覺。默默回傳空結果 → 使用者以為「真的沒有資料」。
要有 metric 追蹤降級觸發率,而且降級中的回應要能被識別(header 或欄位標示)。- 這些機制本身會製造 bug。斷路器誤跳、重試造成重複、降級邏輯有 bug——
★ 每一個容錯機制都要被測試(故障注入,見 工具-注入故障與韌性演練)。- 不要對所有依賴都套用全套。核心依賴掛了就是該失敗;只有「可以沒有」的依賴才值得降級。
→ 先分清楚關鍵路徑與非關鍵路徑。- 用成熟的函式庫(
sony/gobreaker、failsafe-go、Envoy/Istio 的 outlier detection),不要自己寫。🔗 相關工具
- 工具-select超時與競速 —— ★ 逾時必須沿 context 端到端傳遞
- 工具-故障偵測 —— 「掛了還是只是慢」與健康檢查
- 工具-MQ消費端防禦三原則 —— 冪等與重試
- 工具-注入故障與韌性演練 —— 這些機制要被測試
- 裝飾者模式 —— middleware 的順序原則
- 狀態模式 —— 斷路器是經典狀態機
- 工具-生產故障反模式清單 —— 連鎖故障的真實案例
- 工具-限流器設計 —— 最外層的保護
🎯 什麼情境該想到我
當一個業務動作要跨多個服務/資料庫、卻無法用單機交易保證「要嘛全成、要嘛全敗」時。
⚙️ 怎麼用(依一致性需求選方案,沒有銀彈)
- 可靠訊息佇列(本地訊息表 = Outbox):把業務寫入與事件記錄放同一本地交易,再由 relay 投遞 → 最終一致。最常用、最務實(見 工具-Transactional-Outbox-Pattern)。
- TCC(Try-Confirm-Cancel):業務層自己實作預留/確認/取消,強一致但侵入性高、開發成本大。
- Saga:把長交易拆成一連串本地交易 + 對應的補償動作,失敗就反向補償。適合長流程。
- 2PC/XA:強一致但同步阻塞、協調者單點,效能差,少用於高併發。
先問:這個場景需要強一致,還是最終一致就好?多數選最終一致(1)。
★ 先問三個問題(多數情況根本不需要分散式事務)
① ★ 這兩件事真的必須在「不同的服務/資料庫」嗎? → 常常是切錯了邊界。同一個聚合內的東西不該跨服務(見 [[工具-限界上下文]]) ② ★ 真的需要「強一致」,還是「最終一致 + 使用者看得到狀態」就好? → 「下單後 3 秒庫存才扣」在業務上幾乎總是可以接受的 ③ ★ 失敗時可以「人工處理」嗎? → 每天 3 筆異常 → 對帳報表 + 人工補償,★ 遠比建一套 Saga 框架划算★ 最好的分散式事務是「不要有分散式事務」。
大多數「需要分散式事務」的需求,實際上是服務邊界切錯了的症狀。★ 四種方案的完整對照
方案 一致性 複雜度 可用性 ★ 什麼時候用 ★ Outbox(本地訊息表) 最終 低 高 ★ 預設選這個。90% 的場景 Saga 最終 中 高 長流程、多步驟、有明確的補償動作 TCC 準強 高 中 需要「預留資源」的場景(訂票、扣庫存) 2PC / XA 強 中 ★ 低 幾乎不要用(見下) ★ Outbox:為什麼它是預設答案
❌ 雙寫問題(dual write) BEGIN; UPDATE orders ...; COMMIT; kafka.Publish(event) ← ★ 這裡掛掉 → 訂單建了但沒人知道 ✅ Outbox BEGIN; INSERT INTO orders ...; INSERT INTO outbox (event) VALUES (...); ← ★ 同一個本地交易,要嘛都成功要嘛都失敗 COMMIT; -- 背景 relay(或 CDC 讀 WAL)把 outbox 推到訊息佇列★ 它的天才之處:把「跨系統的原子性」化約成「單一資料庫的本地交易」,
而單一資料庫的交易是可靠的(見 工具-交易與隔離等級)。代價:至少一次投遞 → ★ 消費端必須冪等(見 工具-MQ消費端防禦三原則)。
★ Saga:補償的設計才是難點
正向: 建訂單 → 扣款 → 扣庫存 → 出貨 補償: 取消訂單 ← 退款 ← 回補庫存 ← (已出貨無法補償)三件必須想清楚的事:
問題 說明 ★ 補償不等於回滾 退款 ≠ 沒扣過款(帳上留下兩筆紀錄、可能有手續費)。
Saga 沒有「原子性」,只有「最終達到某個可接受的狀態」★ 有些步驟不可補償 已出貨、已寄出的 email、已呼叫的第三方 API。
→ ★ 把不可逆的步驟排在最後,或改成「可逆的形式」(延遲執行、草稿)★ 補償本身也會失敗 退款 API 也可能掛。→ 補償必須可重試 + 冪等 + 最終有人工介入的出口(dead letter) 編排(orchestration)vs 協同(choreography):
Orchestration Choreography 流程在哪 ★ 一個中央協調者(看得到整條流程) 散在各服務的事件監聽裡 好處 ★ 流程可讀、可追蹤、易改 鬆耦合,沒有中心 壞處 協調者是新的耦合點 ★ 沒有人知道完整流程長什麼樣 建議 ★ 步驟 ≥ 3 就用編排 只有兩步時可以協同 ★ 為什麼 2PC 幾乎不該用
協調者 → 「準備好了嗎?」→ 所有參與者鎖住資源並回「好了」 ★ 此時協調者掛掉 → 所有參與者「永遠持鎖等待」(in-doubt) → 資源被鎖住,無法自行決定,必須等協調者回來
問題 說明 ★ 協調者單點 它掛了,參與者全部卡住 ★ 同步阻塞 整個 prepare→commit 期間所有參與者持鎖 可用性差 任一參與者不可用,整個交易失敗(可用性是各方的乘積) 效能差 至少兩輪網路往返 + 持鎖時間長 ★ 它在單一資料庫叢集內(同一個 DBMS 的多個分片)還算堪用;跨異質系統基本上是災難。
★ 產品端要配合的事(技術解不了的部分)
最終一致性必須在產品層面被承認,不能偷偷做:
- ★ 顯示中間狀態:「訂單處理中」而不是假裝已完成
- ★ 提供查詢入口:使用者能看到目前進度
- 定義補償後的使用者體驗:退款要通知、要說明原因
- ★ 對帳機制:定期比對兩邊,找出不一致並告警——
這是最被忽略但最重要的一環。沒有對帳,你永遠不知道系統有沒有出錯。🧪 我實際套用的紀錄
- 2026-07-15:(待填)
⚠️ 注意 / 什麼時候不適用
- ★★ 先檢討服務邊界。頻繁需要跨服務事務 = 邊界切錯了(見 工具-服務邊界與演進式拆分)。
合併回同一個服務往往是正確答案,而不是蓋一套 Saga 框架。- ★ 一定要有對帳。任何最終一致的系統都會飄移,只是你知不知道。 排定期對帳 + 告警 + 人工修補流程。
- ★ 消費端冪等不是可選的。所有方案都是「至少一次」投遞。
- 不可逆的步驟要排在最後,或延遲執行(見 可逆性、審計與人類把關 的 L3 降級技巧)。
- Saga 的狀態要持久化。流程執行到一半服務重啟 → 要能從中斷處續跑(見 狀態模式)。
- 不要用「分散式鎖」代替分散式事務。鎖不安全(見 工具-線性一致性與共識 的 fencing token)。
- ★ 測試很難。要測「第 3 步失敗時補償有沒有正確執行」——故障注入是必要的(見 工具-注入故障與韌性演練)。
- 監控要能看到「卡住的 Saga」:超過 N 分鐘還沒完成的流程要告警。
🔗 相關工具
- 工具-Transactional-Outbox-Pattern —— ★ 預設方案的完整做法
- 工具-交易與隔離等級 —— 單機交易是 Outbox 的基礎
- 工具-MQ消費端防禦三原則 —— 冪等與重試
- 工具-服務邊界與演進式拆分 —— ★ 先檢討邊界
- 工具-線性一致性與共識 —— 為什麼分散式鎖不安全
- 工具-預寫日誌與崩潰復原 —— CDC 作為 Outbox 的替代
- 狀態模式 —— Saga 的狀態持久化
- 工具-注入故障與韌性演練 —— 補償路徑要被測試
🎯 什麼情境該想到我
當你在設計「使用者請求如何一路被導到正確、健康的服務節點」,或思考 CDN/負載均衡/網關該怎麼擺時。
⚙️ 怎麼用(一層層把流量導到對的地方,越前面擋掉越多越好)
- DNS:把網域解析到最近/可用的入口。
- CDN:靜態內容就近快取,別讓請求都打到源站。
- 負載均衡:把流量分散到多個健康節點(四層/七層)。
- API 網關:統一入口,做認證、限流、路由、聚合。
- 服務發現:讓呼叫端動態找到可用的服務實例(配合健康檢查)。
原則:能在越外層解決/擋掉的流量,就別讓它進到越內層,層層減壓。
★ 完整的分流鏈(越前面擋掉越便宜)
瀏覽器快取 ← ★ 最便宜:連請求都不發出 ↓ DNS / GSLB ← 地理就近、機房級故障轉移 ↓ CDN / 邊緣快取 ← ★ 靜態資源與可快取的 API 不進源站 ↓ WAF / DDoS 防護 ← 惡意流量在這裡被擋 ↓ L4 負載均衡 ← 四層:只看 IP:Port,極快 ↓ L7 負載均衡/API 閘道 ← ★ 認證、限流、路由、聚合 ↓ 服務(+ 服務發現、服務網格) ↓ 應用快取(Redis) ↓ 資料庫(+ 讀取副本、連線池)★ 核心原則:「每往內一層,成本大約高一個數量級。」
一個能在 CDN 命中的請求,成本大約是打到資料庫的千分之一。
所以架構優化的第一個問題永遠是:「這個請求能不能在更外層就被解決?」★ L4 vs L7 負載均衡
L4(傳輸層) L7(應用層) 看得到什麼 IP、Port、TCP 連線 ★ HTTP 方法、路徑、header、cookie 能做什麼 依連線分流 ★ 依路徑路由、依使用者黏著、重寫、壓縮、TLS 終止 效能 ★ 極高(可以只改封包不拆解) 較低(要解析 HTTP) 健康檢查 TCP 連得上就算活 ★ 能打 /healthz看真實狀態例子 LVS、AWS NLB Nginx、Envoy、AWS ALB ★ 實務上常兩層都有:L4 做第一層分散與 DDoS 吸收,L7 做智慧路由。
★ API 閘道:該放什麼、不該放什麼
✅ 該放(橫切關注點) ❌ 不該放 認證與 token 驗證 ★ 業務邏輯 限流與配額(見 工具-限流器設計) ★ 資料轉換與聚合的複雜邏輯 路由與版本 需要頻繁改動的東西 請求/回應日誌與 trace 注入 任何「屬於某個團隊」的規則 TLS 終止、壓縮、CORS ★★ API 閘道最大的風險:它會變成新的單體。
每個團隊都想在閘道加一點邏輯 → 它變成所有人都要改、誰都不敢動的中心元件,
而且它掛了全站都掛。
→ 閘道只放「所有服務都一樣」的東西;只要有「某個服務特別」的邏輯,就該放回服務裡。★ 快取的層次與失效難度
層 命中的價值 ★ 失效的難度 瀏覽器 最高(零請求) ★ 最難(你無法主動清除別人的瀏覽器) CDN 很高 中(可以 purge,但有延遲) 閘道/反向代理 高 中 應用內(Redis) 中 ★ 最容易(你完全控制) ★ 這張表解釋了一個重要的取捨:
越外層越省,但也越難改。
→ 內容會變的東西用短 TTL 或放內層;不會變的(帶 hash 的靜態資源)才用長 TTL 放最外層。
→ 靜態資源用 content hash 檔名(app.a3f1c2.js)+ 永久快取,是這個取捨的標準解法。★ 服務發現
方式 例子 DNS ★ 簡單,但有快取與 TTL 問題 註冊中心 Consul、etcd、Eureka ★ 平台內建 Kubernetes Service(多數情況的正解) 服務網格 Istio / Linkerd —— ★ 把重試、逾時、mTLS、追蹤下沉到 sidecar ★ 服務網格的取捨:它把容錯邏輯從應用抽出來(好),但多一層代理的延遲、更多的運維複雜度、更難除錯(壞)。
服務數量少於一二十個時,通常不值得。🧪 我實際套用的紀錄
- 2026-07-15:(待填)
⚠️ 注意 / 什麼時候不適用
- ★★ 每一層都是一個可能的單點。加一層 = 多一個會掛的東西、多一跳延遲、多一個要監控的對象。
不要為了「架構完整」而把每一層都加上。- ★ 快取失效是最難的部分。加快取容易,「什麼時候該讓它失效」才是問題。
沒想清楚就加 → 「資料明明改了但使用者看到舊的」(見 工具-快取與資源優化)。- ★ 閘道不要變成單體(見上)。
- DNS 的 TTL 不可靠。很多解析器會忽略你設的 TTL → 不要依賴 DNS 做快速故障轉移。
- TLS 終止的位置決定了內網是否加密。合規要求端到端加密時,不能只在邊緣終止。
- 健康檢查要檢查「真的能做事」,不是只回 200(見 工具-故障偵測)。
- 小系統不需要這麼多層。一個 Nginx + 應用 + 資料庫能撐很久。先量再加。
🔗 相關工具
- 工具-快取與資源優化 —— 快取策略與失效
- 工具-限流器設計 —— 閘道層的保護
- 工具-服務容錯設計 —— 每一層都該有的容錯
- 工具-故障偵測 —— 健康檢查的設計
- 工具-網站高可用與伸縮性設計 —— 更上層的可用性設計
- 工具-前端效能優化規則 —— 瀏覽器那一端
- 工具-網站架構模式九式 —— 架構手法的完整目錄
- 鳳凰架構
🧰 這本書給我的工具
- 工具-康威定律 — 拆服務/組團隊、發現架構跟組織打架時
- 工具-服務邊界與演進式拆分 — 決定「一個服務該多大、邊界畫在哪」時
分散式事務、容錯、分流等與 鳳凰架構 共用:工具-分散式事務的取捨、工具-服務容錯設計、工具-透明多級分流、工具-服務架構的演進與拆分。
✨ 關鍵重點(Layer 1–2)
- 邊界對齊業務:用 工具-限界上下文 找切點,而非按技術分層拆。
- 康威定律:系統架構會反映組織的溝通結構 → 想要好架構,先設計好團隊。
- 微服務不是免費的:換來獨立部署/擴展,代價是分散式複雜度、運維、資料一致性。
- 演進式:先單體、痛點出現再拆;每個服務獨立部署、擁有自己的資料。
- 高內聚低耦合:能獨立變更與部署,是好服務邊界的檢驗。
💬 金句原文(Layer 0)
- 「微服務帶來的好處,都伴隨著相應的複雜度成本。」