gRPC 與 Protocol Buffers 完全指南

深入 gRPC:Protocol Buffers、服務定義、四種串流、HTTP/2、錯誤模型、gRPC-Web 與版本相容

本篇是 API 設計範式總覽 中 gRPC 的深入版;與 REST / GraphQL 的選型比較看總覽篇。


目錄


什麼是 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/int64float/doubleboolstringbytesenummap<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 靠欄位編號規則做就地向後相容演進,多數情況不必「換版」。


最佳實踐

  1. 契約優先:先把 .proto 設計好,它是跨團隊/跨語言的單一真相來源
  2. 欄位編號只增不改,刪欄位用 reserved,維持相容
  3. 每次呼叫設 deadline,避免逾時卡死資源
  4. 錯誤用 gRPC status code,必要時帶 details
  5. 橫切關注用 interceptor(認證、日誌、重試、metrics)
  6. 對內用 gRPC、對外/瀏覽器用 REST 或 GraphQL(或加 gRPC-Web 代理)
  7. 善用串流:大量資料 / 即時更新用 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

🔗相關文章