到目前為止,PokeThreads 已經具備了 Load Balancer、Read Replica、Cache、Stateless Server、CDN 與 Message Queue,是時候回頭想想,這些元件之間該怎麼有效率地互相溝通了。
不過在談溝通方式之前,先看一眼現在的 Application Server 到底裝了多少東西。
到目前為止,不管是發文、看 Feed、Follow,還是接收 Message Queue 的 Notification Worker,全部都活在同一包程式碼裡:
PokeThreads Application
┌─────────────────────────┐
│ User │
│ Post │
│ Feed │
│ Follow │
│ Notification │
└─────────────────────────┘
↓
Database
這種「所有功能都打包在同一個部署單位」的做法,叫做 Monolith。
Monolith 代表整個 Application 以一個主要部署單位存在,就像是一棟「獨立賣場」,例如 3C 家電連鎖通路(全國電子、黃色鬼屋),所有的服務(會員、訂單、金流、UI介面)都在同一棟建築物內,共用同一個地基與水電系統。
這在系統還小的時候完全適用,兩個模組要合作直接呼叫彼此的函式就好,不用處理任何網路問題。
但 PokeThreads 走到今天這一步,Monolith 開始出現幾個明顯的痛點。
假設 Feed 的流量暴增,需要更多運算資源,但 Notification Worker 平常負載很輕。
在 Monolith 架構下,Server 裡什麼功能都有,只能整包一起複製、一起擴展,沒辦法只加強 Feed 那一部分。
假設只是想調整 Notification 的文案,理論上跟 Feed、Follow 一點關係都沒有。
但因為所有程式碼綁在同一個部署單位裡,任何一次上線都是整個 Application 重新部署一次,風險跟影響範圍都被放大了。
硬把這些完全不同性質的工作塞進同一個 Runtime,只會讓每個功能都無法用最適合自己的方式運作。
既然 Monolith 卡在「所有東西綁在一起」,那就反過來把系統拆成多個服務,每個都各自獨立部署、各自負責一個明確業務範圍的服務,這就是 Microservices。
如果 Monolith 是一間大賣場,Microservices 更像百貨公司,每一家專櫃各自有自己的店面、自己的人力配置,彼此透過走廊(也就是 API)交換東西,但誰也不用管誰的內部庫存怎麼擺。
回到 PokeThreads,第一個值得拆出去的候選者很明顯:Notification。
它和跟主要的 Post/Feed API 完全不同、依賴的元件(Message Queue、Worker)也不同,拆分之後,Notification Service 可以:
一旦 Notification 變成一個獨立的 Service,跑在自己的 Process、甚至自己的機器上,原本一行函式呼叫就能解決的事,現在得改成一次網路請求。
對外(Client 端)我們已經用 REST 溝通了,但如果 Service 之間內部溝通也用 REST + JSON,會有一些不太划算的地方:JSON 是文字格式,序列化/反序列化比較慢;而且 REST 沒有強制的 Schema,A 服務改了欄位名稱,B 服務可能要等到執行期才會發現出錯。
內部服務之間更常見的選擇是 gRPC:
API Service ⇄ gRPC ⇄ Notification Service

gRPC 有幾個特性特別適合「內部」溝通:
.proto 檔定義好訊息結構跟型別,再產生對應語言的程式碼。跟 JSON 比起來,訊息是二進位格式,體積更小、解析更快,而且欄位型別在編譯期就會檢查,不會等到 Production 才發現「這個欄位應該是數字,結果傳了字串」。值得注意的是:gRPC 適合的是服務之間的內部溝通,不是用來取代 Day 4 討論的 REST(或 GraphQL)。
對外面對瀏覽器、App 的 API,仍然會維持 REST/GraphQL 這類對前端友善、方便除錯的格式;gRPC 負責的是 Server 與 Server 之間看不到的那一段。
拆分不是沒有成本,把 Monolith 打散成多個 Service,等於把原本「函式呼叫」的問題,換成了「網路呼叫」的問題:
回頭看,如果 PokeThreads 從第一天就拆成 Notification Service、Feed Service、Search Service……那我們大概還在處理跨服務 bug/debug,根本還沒空理會使用者真正在乎的功能。
比較實際的做法是:先讓系統用 Monolith 跑起來,隨著規模成長,持續觀察
當答案越來越明確,再逐步、一個一個把它們拆出去,而不是一口氣把整個系統打散。
PokeThreads 拆出 Notification Service,正是因為它符合上面所有條件;Feed、Post、Follow 這些彼此高度相關的功能,暫時還是留在同一個 Service 裡比較划算。
| 比較維度 | Monolith | Microservices |
|---|---|---|
| 結構 | 單一程式碼庫,功能都在一起 | 拆成多個獨立部署的 Service |
| 溝通方式 | 函式呼叫 | 網路請求(gRPC / Message Queue) |
| Scaling | 整包一起放大 | 各自獨立擴展 |
| 除錯難度 | 相對單純 | 需要良好的 Observability |
| 適合階段 | 早期、規模小 | 規模夠大、邊界夠清楚之後 |
PokeThreads 現在多了一個 Notification Service,也多了一種內部溝通方式(gRPC)。
但只要 Service 數量開始變多,馬上會冒出下一個問題:
Client 要怎麼知道該打哪一個 Service?