我最近在整理以前讀過的書。這篇想寫《微服務架構設計模式》。

英文原著是《Microservices Patterns》,Chris Richardson 寫的。
我翻這本書的原因很具體。那時候我要做 Contract Testing,公司一位資深架構師說這本裡面有講,你去看。我就去看了。契約測試確實有講,第九、十章。但整本看完,留在我腦子裡的不是那兩章,是 Saga 跟 Outbox——而且我是先讀了這本書,之後才在工作上做了兩套用得到它們的系統,一套是把管理命令下發到 Android 裝置,一套是十萬台裝置的告警。這個順序讓我把「讀懂」跟「做出來」的差距看得特別清楚。
這本書在講什麼?講你把系統拆成微服務之後會遇到的所有麻煩。作者有個網站叫 microservices.io,44 個模式都列在上面,不過網站看看目錄就好,書值得讀的是推導的過程。
這本書有個地方我蠻喜歡的:作者完全不推銷微服務。他開頭就說,微服務是拿營運複雜度去換部署獨立性,組織沒大到那個程度就別拆。整本書用一個外送平台 FTGO 當例子,一個模式一個模式過,每個都把代價寫清楚。最後一章還在教你怎麼從單體慢慢搬過去,不是打掉重寫。
整本書真正的核心,我覺得只有一條:Database per Service。每個服務有自己的資料庫,別人不准碰。這條約束定下去,麻煩就全來了。以前在單體裡,跨模組的一致性一個資料庫交易就搞定,現在資料散在好幾個資料庫,ACID 沒了。書後面的 Saga、Event Sourcing、CQRS,全是在收拾這個後果。
分散式交易我一直知道很難,但那個「難」對我其實一直是模糊的——就是知道會出事,講不清楚會出什麼事。第四章是第一次有人幫我把這件事攤開。
跨服務的交易,直覺會想用兩階段提交:大家先投票,都說可以再一起 commit。問題是這要所有參與者同步鎖住資源等彼此,一個慢全部卡,參與者越多越脆,而且 Kafka 這類東西根本不支援。
Saga 的做法是把一筆大交易拆成一串本地交易接力。每個服務做自己那步、commit 自己的資料庫,把棒子交下去。中間哪步失敗,就回頭把前面做過的用反向操作抵銷掉——扣過的款退回去。書裡叫補償交易。代價是隔離性沒了:每步 commit 完結果就對外可見,整條 Saga 還沒走完,別人已經看得到中間狀態,ACID 少了 I。書給的補法是在應用層自己擋,比如拿一個 PENDING 狀態欄位當鎖,別人看到 PENDING 就知道這筆還沒定案。
後來我做了一條真的 Saga。我們的系統發命令,丟進 Kafka,執行端收下、去打 Google 的 API,把 policy 推到裝置上,結果再回報回來。骨架就是書上那個接力。但搭起來之後,剩下的全是書上沒有的。
最花時間的是這個:命令發出去,結果不會馬上回來。有時候繞過第三方的非同步 API,隔很久回執才飛回來,而你手上同時有幾百條在飛的命令——這張回執是誰的?書裡有 correlation ID 這個詞,放在可觀測性那章。但在這裡它不是可觀測性問題,是整條 Saga 能不能閉環的問題。我得把第三方回來的非同步回覆接住、翻譯回我自己的結果事件,才關得回原本那條命令。這段我做了很久。
再來是順序。同一台裝置連續下兩條命令,執行順序反了就出事。解法是拿裝置當 partition key,同一台的命令進 Kafka 同一個 partition,天然有序。書幾乎沒講這個。還有訊息版本——對方送來一個 schema 版本不對的訊息,吞掉還是拒掉?我選拒,因為靜默吞一個你看不懂的訊息,等於在資料裡埋雷。
還有一種坑書永遠不會寫,因為那不是設計問題。我修過一個 goroutine 洩漏,context 取消的時候鎖沒放掉。這種東西書不會教,是 on-call 教的。
Outbox 的故事反而更好笑,因為這次我是真的想照書做,結果做不成。
告警系統的問題很經典:記一筆告警,發一則通知。記資料是資料庫,發通知是另一個系統。先記後發,中間當機,資料有了、通知沒發,客戶該收到的告警沒收到。這叫 dual write。書給的解法是 Transactional Outbox:把「要發的通知」也當成一筆資料,跟業務資料塞進同一個資料庫交易一起存。同一個交易,不可能一個成一個敗,之後再由別的機制把它慢慢發出去。我讀的時候心想,好,就這個。
坐下來做才發現,我的告警是從上游 Kafka 消費進來的。我寫 outbox 那一下是一個單獨的寫入,旁邊沒有任何「業務狀態」跟它在同一個交易裡一起 commit。教科書 Outbox 的整個保證就建立在「同一個交易」上,而我根本沒有那個交易。我做的是一個長得像 Outbox、但前提不成立的東西。
後來我在筆記裡留了一句話給未來的自己:
絕不可說 same-transaction commit。
只要嘴滑一次,把它講成教科書那個 Outbox,懂的人問一句「你的交易邊界在哪」就穿幫了。所以我老實叫它別的:一筆持久化的 record,加上冪等的 at-least-once 投遞。真相只有那筆 outbox row,寫完馬上送只是降延遲,送掛了輪詢會補。at-least-once 一定重複,就一層一層擋——來源端 dedup key、跨 replica 靠資料庫唯一鍵、下游各自冪等。裝置離線那種每輪都會觸發的告警,用小時桶收斂,同一小時同一台只算一筆,不然告警自己把自己洗版。
這些一項都不在書裡。但反過來說,沒有書,我連「我做的這個不是 Outbox」這個判斷都下不了——你要先知道標準版長什麼樣,才看得出自己的哪裡不一樣。
所以這本書對我是一張地圖,不是施工圖。它把坑跟解法的名字都給你了,讓你動手前就知道方向,也講得出自己在做什麼。但真正花掉我最多時間的那些事,回執怎麼關回命令、怎麼保序、怎麼冪等,在地圖上都只是一個點,有的根本沒畫上去。
那 AI 時代還要讀它嗎?44 個模式 AI 背得比我熟,你問它 Saga、問它 Outbox,它講得比書還整齊。但我做告警系統的時候如果去問它「我這個 Outbox 對不對」,它會信誓旦旦告訴我 Outbox 要同交易提交——它不知道我的資料是從 Kafka 進來的,不知道我沒有那個交易。它知道模式,但看不出我的系統不符合模式的前提,因為它手上沒有我的系統。
書可以給你名字跟方向。自己的系統長什麼樣,還是得自己踩過才知道。