本篇是 API 設計範式總覽 中 gRPC 的深入版;與 REST / GraphQL 的選型比較看總覽篇。
目錄
- 什麼是 gRPC?
- Protocol Buffers
- 服務定義
- 四種串流模式
- 程式碼生成流程
- 為什麼用 HTTP/2
- 錯誤模型
- Metadata、Deadline 與 Interceptor
- gRPC-Web 與瀏覽器限制
- 版本相容
- 最佳實踐
- 常見問題
- 總結
什麼是 gRPC?
gRPC 是 Google 開源的高效能 RPC(Remote Procedure Call,遠端程序呼叫) 框架。核心理念:讓呼叫遠端服務的方法像呼叫本地函式一樣。
三大支柱:
介面定義:Protocol Buffers(.proto,強型別契約)
傳輸: HTTP/2(多工、二進位、支援串流)
序列化: Protobuf 二進位(比 JSON 小且快)
核心特點
- 契約優先:先寫
.proto定義服務與訊息,再由工具產生各語言程式碼 - 二進位高效:payload 比 JSON 小、序列化快
- 原生串流:HTTP/2 支援四種串流模式
- 跨語言:一份
.proto產生 Go / Java / Python / C++… 的 client 與 server - 典型場景:微服務之間的內部通訊(東西向流量),不是給瀏覽器/公開 API
Protocol Buffers
Protocol Buffers(protobuf) 是 gRPC 的介面定義語言與序列化格式。用 .proto 檔(proto3 語法)定義訊息結構:
syntax = "proto3";
message User {
int32 id = 1; // = 1 是「欄位編號」,不是預設值
string name = 2;
string email = 3;
repeated Order orders = 4; // repeated = 陣列
}
message Order {
int32 id = 1;
double amount = 2;
}
重點:欄位編號(Field Number)
int32 id = 1;
↑ 這個數字是「欄位在二進位中的標籤」,不是值
- 序列化時用編號而非欄位名 → payload 小、解析快
- 編號一旦用過就不能改(改了等於換欄位)——這是版本相容的關鍵(見後文)
- 常用型別:
int32/int64、float/double、bool、string、bytes、enum、map<k,v>、repeated(陣列)
protobuf 是二進位、不可人類閱讀——這是效能的代價,除錯時要用工具反序列化。
服務定義
在 .proto 裡用 service 定義 RPC 方法:
service UserService {
rpc GetUser (UserRequest) returns (UserReply);
}
message UserRequest { int32 id = 1; }
message UserReply { User user = 1; }
每個 rpc 方法都明確標出輸入訊息與輸出訊息,形成強型別契約。
四種串流模式
gRPC 靠 HTTP/2 原生支援四種呼叫模式:
service Demo {
rpc Unary (Req) returns (Reply); // 1. 一元
rpc ServerStream (Req) returns (stream Reply); // 2. server 串流
rpc ClientStream (stream Req) returns (Reply); // 3. client 串流
rpc BiDiStream (stream Req) returns (stream Reply); // 4. 雙向串流
}
| 模式 | 流向 | 典型場景 |
|---|---|---|
| Unary(一元) | 一問一答 | 一般方法呼叫(最常見) |
| Server streaming | client 送 1,server 回多筆 | 推送列表、訂閱更新 |
| Client streaming | client 送多筆,server 回 1 | 上傳、批次彙整 |
| Bidirectional | 雙向同時串流 | 即時對話、雙向同步 |
這是 gRPC 相較 REST 的一大優勢:串流是一等公民,不必額外靠 SSE/WebSocket。
程式碼生成流程
gRPC 的開發流程是「契約 → 生成 → 實作」:
1. 寫 user.proto(定義 service + message)
│ protoc(protobuf 編譯器)+ 語言外掛
▼
2. 產生程式碼:
- server 端 stub(介面,你去實作邏輯)
- client 端 stub(直接像呼叫本地方法)
3. server 實作介面、client 直接呼叫
# 用 protoc 產生 Go 程式碼(範例)
protoc --go_out=. --go-grpc_out=. user.proto
- 多語言:同一份
.proto可產生不同語言的 client/server,天然跨語言互通 - client 拿到的 stub 把「網路呼叫」包裝成本地方法呼叫
為什麼用 HTTP/2
gRPC 建立在 HTTP/2 上,這是它高效與支援串流的基礎:
| HTTP/2 特性 | 對 gRPC 的意義 |
|---|---|
| 二進位分幀(binary framing) | 傳二進位 protobuf,不必文字解析 |
| 多工(multiplexing) | 單一連線同時跑多個請求,無 HTTP/1.1 的隊頭阻塞 |
| 雙向串流 | 四種串流模式的基礎 |
| 標頭壓縮(HPACK) | 減少重複標頭開銷 |
| 長連線 | 連線重用,省去反覆握手 |
相對地,REST 多跑在 HTTP/1.1、傳文字 JSON,這是 gRPC 通常更快、payload 更小的根本原因。
錯誤模型
gRPC 有自己的一套狀態碼,與 HTTP 狀態碼不同(雖然底層走 HTTP/2):
| gRPC 狀態 | 對應情境 | 近似 HTTP |
|---|---|---|
OK |
成功 | 200 |
INVALID_ARGUMENT |
參數錯 | 400 |
UNAUTHENTICATED |
未認證 | 401 |
PERMISSION_DENIED |
無權限 | 403 |
NOT_FOUND |
找不到 | 404 |
ALREADY_EXISTS |
已存在 | 409 |
DEADLINE_EXCEEDED |
逾時 | 504 |
UNAVAILABLE |
服務不可用 | 503 |
INTERNAL |
內部錯誤 | 500 |
對照 REST 用 HTTP 狀態碼、GraphQL 用 errors 陣列——三者錯誤模型各不同。gRPC 的錯誤含 status code + message + 可選的 details。
Metadata、Deadline 與 Interceptor
Metadata
類似 HTTP 標頭的 key-value,用來帶認證 token、trace id 等:
metadata: { "authorization": "Bearer ...", "x-trace-id": "..." }
Deadline / Timeout
gRPC 鼓勵每次呼叫都設 deadline(絕對時間點),逾時自動取消,避免資源卡死:
client 設 deadline = now + 2s
→ 超過未回 → 自動回 DEADLINE_EXCEEDED
Interceptor
等同中介層(middleware),在呼叫前後插入邏輯:認證、日誌、metrics、重試等,client 與 server 端都有。
gRPC-Web 與瀏覽器限制
瀏覽器無法直接用標準 gRPC。 原因:gRPC 需要操控 HTTP/2 的底層 framing 與 trailer,而瀏覽器的 fetch/XHR 無法完全控制。
解法是 gRPC-Web:
瀏覽器 ──gRPC-Web──► 代理(Envoy / grpc-web proxy)──標準 gRPC──► gRPC server
- 瀏覽器用 gRPC-Web 協定,經代理轉成標準 gRPC
- 功能受限(如不支援 client streaming 與雙向串流)
- 這也是為何 gRPC 主要用於後端服務之間,對外/瀏覽器多仍用 REST 或 GraphQL
版本相容
protobuf 的相容性建立在欄位編號上,遵守規則就能前後相容地演進:
message User {
int32 id = 1;
string name = 2;
string email = 3; // ✅ 新增欄位用「新編號」→ 舊 client 忽略,向後相容
// ❌ 不可改既有欄位的編號
// ❌ 不可重用已刪除欄位的編號(用 reserved 保留)
}
規則:
- 新增欄位 → 用沒用過的新編號,舊端忽略不認得的欄位(雙向相容)
- 刪除欄位 → 用
reserved保留其編號與名稱,避免日後誤用 - 絕不更改或重用既有編號 → 否則新舊端對同一編號解讀不同,資料錯亂
這與 REST 的版本控制 思路不同:REST 多靠
/v1、/v2換版;protobuf 靠欄位編號規則做就地向後相容演進,多數情況不必「換版」。
最佳實踐
- 契約優先:先把
.proto設計好,它是跨團隊/跨語言的單一真相來源 - 欄位編號只增不改,刪欄位用
reserved,維持相容 - 每次呼叫設 deadline,避免逾時卡死資源
- 錯誤用 gRPC status code,必要時帶 details
- 橫切關注用 interceptor(認證、日誌、重試、metrics)
- 對內用 gRPC、對外/瀏覽器用 REST 或 GraphQL(或加 gRPC-Web 代理)
- 善用串流:大量資料 / 即時更新用 server/bidi streaming,而非反覆一元呼叫
常見問題
問題 1:gRPC 為什麼比 REST 快?
二進位 protobuf(比 JSON 小且解析快)+ HTTP/2(多工、無隊頭阻塞、標頭壓縮、長連線)。但差距視場景,且 gRPC 不可讀、瀏覽器不能直連,要權衡。
問題 2:為什麼瀏覽器不能直接用 gRPC?
gRPC 需要操控 HTTP/2 底層 framing 與 trailer,瀏覽器 API 做不到。需用 gRPC-Web + 代理(如 Envoy)轉換,且功能受限(不支援 client/雙向串流)。
問題 3:欄位編號可以改嗎?
不行。編號是 protobuf 二進位的識別依據,改了等於換欄位、造成新舊端解讀不一致。刪除欄位要用 reserved 保留編號防誤用。
問題 4:gRPC 和 REST 該怎麼選?
微服務之間高頻內部通訊、要效能與強型別、要串流 → gRPC;對外公開 API、要瀏覽器直連、想善用 HTTP 快取 → REST。實務常「對內 gRPC、對外 REST」。
問題 5:gRPC 一定要用 Protocol Buffers 嗎?
預設且絕大多數用 protobuf。技術上可換序列化(如 JSON codec),但會失去二進位效能優勢,少見。protobuf 是 gRPC 生態的事實標準。
總結
核心要點
- gRPC = Protocol Buffers(契約 + 二進位序列化) + HTTP/2 傳輸 的高效 RPC 框架
.proto定義 service/message,protoc 生成多語言 client/server- 欄位用編號識別 → payload 小、解析快,但編號不可改(相容關鍵)
- 原生支援四種串流(一元 / server / client / 雙向)
- 錯誤用 gRPC status code;呼叫該設 deadline;橫切用 interceptor
- 瀏覽器不能直連(需 gRPC-Web + 代理)→ 適合後端服務之間
- 版本演進靠欄位編號規則做向後相容,不必像 REST 換
/v1、/v2
快速參考
| 項目 | 重點 |
|---|---|
| IDL | Protocol Buffers(.proto, proto3) |
| 傳輸 | HTTP/2(多工、二進位、串流) |
| 串流 | 一元 / server / client / 雙向 |
| 錯誤 | gRPC status code(≠ HTTP 碼) |
| 相容 | 欄位編號只增不改,刪用 reserved |
| 瀏覽器 | 需 gRPC-Web 代理,功能受限 |
| 場景 | 微服務內部通訊 |
建立日期:2026-06-18