結論先講
對外用 REST(或 GraphQL),對內用 gRPC。 前端打後端用 REST/GraphQL——因為瀏覽器原生支援、工具鏈成熟、debug 容易。微服務之間用 gRPC——因為 Protobuf 比 JSON 小 50% 快 30%(第 22 篇 壓測數據),而且有 schema 強制驗證。但如果你的微服務只有 3-5 個,REST 就夠了,不需要為了 30% 的效能上 gRPC。
四種協定一覽
| 協定 | 格式 | 傳輸 | 方向 | 適合 |
|---|---|---|---|---|
| REST | JSON | HTTP/1.1 or 2 | Request-Response | 對外 API、CRUD |
| gRPC | Protobuf | HTTP/2 | 雙向 Streaming | 服務間通訊 |
| GraphQL | JSON | HTTP/1.1 or 2 | Request-Response | 前端彈性查詢 |
| WebSocket/SSE | 自定 / JSON | TCP / HTTP | 持久連線 | 即時通訊、推送 |
REST:最簡單,大部分場景夠用
優點
- 所有人都會:每個後端框架原生支援
- 工具鏈最成熟:Postman、Swagger、curl
- Debug 容易:看 HTTP status code + JSON body 就知道問題
- 瀏覽器原生支援:fetch / axios 直接打
缺點
- Over-fetching:
GET /users/123回了 30 個欄位,前端只要 3 個 - Under-fetching:要拿用戶 + 訂單 + 商品,要打 3 個 API
- JSON 效率低:文字格式,比 binary 大 2-3 倍
- 沒有 schema 強制:API 改了 response 格式,client 不知道(直到 runtime 壞掉)
在微服務裡的使用
我們的系統就是用 REST(第 28 篇)。3-5 個微服務之間用 REST 通訊,簡單直接。
REST 在微服務的瓶頸不在「協定慢」,而在「序列化/反序列化」。 JSON.parse + JSON.stringify 在高流量下是有成本的。壓測數據:Protobuf 比 JSON 小 50%、快 30%。
什麼時候 REST 不夠
- 微服務 > 10 個,服務間呼叫鏈很長(A → B → C → D),每一層的 JSON 序列化都是成本
- 需要 streaming(上傳進度、即時資料流)
- 前端查詢模式變化大,REST endpoint 爆炸
gRPC:微服務間通訊的最佳選擇
什麼是 gRPC
Google 開源的 RPC 框架。用 Protobuf 定義 schema,用 HTTP/2 傳輸,支援四種通訊模式。
// user.proto — schema 定義
syntax = "proto3";
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc ListUsers (ListUsersRequest) returns (stream User); // server streaming
}
message GetUserRequest {
string id = 1;
}
message User {
string id = 1;
string name = 2;
string email = 3;
}編譯後自動產生各語言的 client/server code。schema 是合約——改了 .proto 檔,client 和 server 都會在編譯時發現不相容。
四種通訊模式
| 模式 | 說明 | 場景 |
|---|---|---|
| Unary | 一問一答(和 REST 一樣) | 一般查詢 |
| Server Streaming | Client 問一次,Server 連續回多次 | 即時報價、進度更新 |
| Client Streaming | Client 連續送,Server 最後回一次 | 上傳大檔分片 |
| Bidirectional | 雙向持續通訊 | 聊天室、協作編輯 |
Server Streaming 可以取代 SSE,Bidirectional 可以取代 WebSocket——而且效能更好(Protobuf binary vs JSON text)。
壓測數據
| 格式 | 大小 | 速度 |
|---|---|---|
| JSON | 基準 | 基準 |
| Protobuf | -50% | +30% |
小 50% = 網路傳輸省一半。快 30% = 序列化/反序列化效率高。在微服務間高頻呼叫(每秒幾千次),這個差距很顯著。
缺點
- 瀏覽器不支援原生 gRPC:前端要用 gRPC-Web(多一層 proxy)
- Debug 困難:Protobuf 是 binary,
curl看不懂。需要用grpcurl或 Postman gRPC - 學習成本:.proto 語法、code generation、streaming 狀態管理
- 生態比 REST 小:不是所有語言/框架都有好用的 gRPC library
什麼時候用 gRPC
微服務之間通訊:
├── < 5 個服務,呼叫頻率低 → REST(簡單就好)
├── > 5 個服務,呼叫鏈長 → gRPC(省序列化成本)
├── 需要 streaming → gRPC(原生支援)
└── 需要 schema 強制 → gRPC(.proto 是合約)
GraphQL:前端的彈性查詢
解決的問題
REST 的 Over-fetching 和 Under-fetching:
# 一個 query 拿到用戶 + 最近訂單 + 訂單商品
# REST 要打 3 個 API,GraphQL 一次搞定
query {
user(id: "123") {
name
email
orders(last: 5) {
id
total
products {
name
price
}
}
}
}優點
- 前端自己決定要什麼欄位:不需要後端改 API
- 一次拿到多個資源:減少 HTTP roundtrip
- 強型別 schema:前端可以自動生成 TypeScript type
缺點和陷阱
1. N+1 查詢問題
上面的 query 如果 naive 實作,會觸發:
- 1 次查 user
- 1 次查 orders(5 筆)
- 5 次查 products(每筆訂單各查一次) = 7 次 DB 查詢
要用 DataLoader 做 batching 才能避免。
2. 快取困難
REST 的 GET /users/123 可以用 HTTP cache。GraphQL 的 POST body 每次不同,HTTP cache 不管用。需要 Apollo Client 的 normalized cache 或自己處理。
3. 在微服務裡的複雜度
每個微服務有自己的 GraphQL schema → 需要 schema stitching 或 Apollo Federation 合併 → 多一層複雜度。
什麼時候用 GraphQL
- 前端需要高度彈性的查詢(不同頁面要不同的欄位組合)
- 多個前端共用同一個 API(Web、iOS、Android 各自需要不同欄位)
- 團隊有 GraphQL 經驗
什麼時候不用
- CRUD 為主的後台管理系統(REST 就夠了)
- 微服務間通訊(用 gRPC,不用 GraphQL)
- 團隊沒有 GraphQL 經驗(學習成本高)
混合使用才是正解
大部分成熟的微服務架構不會只用一種協定:
前端 ←→ API Gateway: REST 或 GraphQL
API Gateway ←→ 微服務: gRPC
微服務 ←→ 微服務: gRPC
即時推送: WebSocket 或 gRPC Streaming
事件通知: Kafka/RabbitMQ(不是 HTTP)
我們的做法
目前用 REST(因為團隊 Python 背景,FastAPI 的 REST 支援最好)。如果服務數量成長到 10+ 且服務間呼叫頻率高,會在核心路徑上加 gRPC。
前端仍然用 REST——因為加 GraphQL 的 ROI 在我們的場景下不夠高(主要是 CRUD,不是複雜的嵌套查詢)。
選型速查
| 場景 | 推薦 |
|---|---|
| 前端打後端(CRUD 為主) | REST |
| 前端打後端(查詢彈性大) | GraphQL |
| 微服務間(< 5 個服務) | REST |
| 微服務間(> 5 個、高頻) | gRPC |
| 即時推送(server → client) | SSE 或 gRPC Server Streaming |
| 即時雙向(聊天、協作) | WebSocket 或 gRPC Bidirectional |
| 事件通知(非同步) | Kafka / RabbitMQ |
下一篇
Race Condition 與分散式鎖 — 通訊方式選好了,接下來看併發控制——微服務裡兩個人同時搶最後一件商品怎麼辦。
本系列文章
完整 68 篇目錄見 系列首頁
← 上一篇:基於 ELK 的推薦系統:能做嗎?該怎麼規劃? → 下一篇:Race Condition 與分散式鎖:微服務裡的搶購怎麼不超賣