題目與適用情境
一個平台有三種 API 邊界。第一種由瀏覽器、行動 App 和外部合作夥伴呼叫,需要容易串接、除錯和長期演進。第二種是可控資料中心內的服務間呼叫,尖峰為每秒 20,000 次,既有一般請求—回應,也需要伺服器持續推送結果。第三種則接收第三方系統送來的 webhook 回呼。
每秒 20,000 次是面試假設,不是通用的效能分界。候選人要為每個邊界選擇 REST 風格的 HTTP+JSON API、原生 gRPC,或有明確理由的組合,並解釋遷移和驗證。REST 是架構風格,不綁定 JSON 或 HTTP/1.1;此處寫成「REST/HTTP+JSON」,只是固定本題比較的常見實作。gRPC 也不是一個會自動取得低延遲、冪等和可靠性的開關。
本題歸在 backend,因為核心能力是服務介面、協定語意、用戶端契約和執行治理。回答不必設計整個業務系統,也不能只背「gRPC 比較快、REST 比較通用」的表格。
面試官評估重點
第一個訊號是先辨識消費者和網路邊界。公開瀏覽器與合作夥伴生態重視通用 HTTP 工具、可讀的資料內容、快取語意和低串接成本;可控的內部呼叫方較容易統一 .proto、產生程式碼、Proxy 和負載平衡。協定選擇應跟著邊界走,整個平台不必只保留一種介面。
第二個訊號是分清抽象與實作。REST 透過資源、HTTP 方法、狀態碼和快取表達語意,也可以使用 HTTP/2 或 HTTP/3;gRPC 以服務和方法為中心,預設以 Protocol Buffers 描述介面與訊息,並提供 unary、用戶端串流、伺服器串流和雙向串流。把「REST 只能用 HTTP/1.1」或「REST 不能串流回傳」當成結論,表示概念沒有分清。
第三個訊號是把契約和失敗語意寫完整。OpenAPI 同樣能為 HTTP API 提供機器可讀契約與程式碼產生;Protobuf 的二進位相容也不等於業務相容。無論選哪一邊,都要定義截止時間、取消、冪等、可重試錯誤、驗證授權、版本演進和可觀測欄位。
最後看證據。好的回答會用具代表性的資料內容、併發、壓縮、長連線和故障模式做基準測試,再用 canary 驗證尾端延遲與錯誤率。只憑「二進位一定比較快」推動平台級遷移,缺少可重現依據。
回答前需要釐清的問題
- 誰控制用戶端和升級節奏? 同一組織內可協調升級的服務適合產生用戶端;無法強制更新的合作夥伴需要更穩定、更容易獨立使用的邊界。
- 通訊是 unary、串流還是非同步通知? 一般 CRUD 不會因改用 gRPC 自動受益;長連線上的有序資料流可能讓原生 gRPC 更自然。第三方 webhook 由對方主動發起,通常必須遵循其公開 HTTP 契約。
- 瀏覽器是否必須直接呼叫? 瀏覽器無法直接使用原生 gRPC 所需的完整 HTTP/2 控制能力,需要 gRPC-Web、JSON 轉碼或 BFF。這層轉接會改變除錯、串流能力和維運成本。
- 效能問題到底在哪裡? 若瓶頸在資料庫、下游 fan-out 或超大查詢,更換序列化協定不會消除它。需要提供資料大小、QPS、併發、p95/p99、CPU 與網路預算。
- 既有 Gateway 與可觀測工具支援什麼? Proxy 是否理解 gRPC status、stream 與健康檢查,Log 和 Trace 能否關聯單次 RPC,會直接影響上線風險。
- 介面如何演進? 公開 API 需要相容政策;內部 Protobuf 需要欄位編號和混合版本規則。沒有跨版本測試,強型別契約仍可能在滾動部署時失敗。
30 秒回答架構
「我會依消費者邊界選擇,不做全域二選一。公開瀏覽器、行動 App 和合作夥伴 API 先用 REST 風格的 HTTP+JSON,並以 OpenAPI、HTTP 語意和相容政策約束;第三方 webhook 也沿用公開 HTTP 契約。內部每秒 20,000 次的可控服務呼叫,如果基準測試證明序列化或連線成本重要,而且確實需要伺服器串流,我會選原生 gRPC。它以 .proto 產生用戶端,但仍要明確設定 deadline、傳遞取消、只重試安全操作並維護欄位相容。邊緣可用 JSON 轉碼或薄 Gateway 共用一份領域實作。上線前用真實資料比較端到端 p99、CPU、頻寬和錯誤復原,再逐步遷移;協定本身不會取代驗證、冪等和可觀測性。」
逐步深入解析
第一步:分別決定三個邊界
公開邊界選 REST 風格的 HTTP+JSON。資源 URI、方法、狀態碼、條件式請求與快取可被瀏覽器、CDN、命令列工具及合作夥伴廣泛理解。OpenAPI 作為契約真源,可產生文件和 SDK,所以不能把 REST 說成「沒有型別、全靠手寫」。代價是團隊必須主動治理錯誤模型、分頁、列舉開放性和文件漂移。
內部邊界先滿足兩個門檻再選 gRPC:呼叫方和伺服器能統一產生程式碼與執行元件;具代表性的基準測試證明收益足以負擔 Proxy、除錯和混合版本成本。本題也明確需要伺服器串流,gRPC 的串流方法、每次呼叫的 metadata、status 和 deadline 能提供一致模型。因此內部採用 gRPC 合理,但 20,000 QPS 本身不是決定依據。
第三方回呼使用 HTTP webhook。協定由外部發送方控制,入口需要公開 TLS、簽章驗證、快速確認和非同步處理。把接收端改成 gRPC,不能讓只會傳 HTTP POST 的合作夥伴呼叫。若內部處理鏈用 gRPC,webhook 接入層先驗證、持久化,再呼叫內部服務。
| 邊界 | 初始選擇 | 主因 | 主要代價 |
|---|---|---|---|
| 瀏覽器、行動 App、合作夥伴 | REST/HTTP+JSON | 廣泛相容、HTTP 語意、低串接成本 | 需治理契約與用戶端差異 |
| 可控內部服務 | gRPC | 產生契約、串流方法、緊湊訊息 | 工具鏈、Proxy 和滾動升級複雜度 |
| 第三方回呼入口 | HTTP webhook | 發送方契約和公網互通性 | 必須自行處理簽章、去重和非同步隔離 |
第二步:分別寫出可執行契約
公開讀取介面使用資源和 HTTP 語意:
GET /v1/orders/ord_123
If-None-Match: "order-v7"
200 OK
ETag: "order-v7"
Content-Type: application/json內部介面以動作和訊息定義:
service OrderService {
rpc GetOrder(GetOrderRequest) returns (Order);
rpc WatchOrder(WatchOrderRequest) returns (stream OrderEvent);
}
message GetOrderRequest {
string order_id = 1;
}兩份契約都要涵蓋身分驗證、授權、錯誤、分頁或串流結束條件、大小上限和稽核欄位。gRPC 的方法名稱可能隱藏網路成本,呼叫方仍要把它當成會延遲、逾時和部分失敗的遠端呼叫。REST 的 POST 也不會自動冪等;建立類操作要使用穩定冪等鍵或業務唯一限制。
第三步:設計失敗語意
gRPC 用戶端預設可能沒有 deadline,應依端到端預算明確設定。deadline 到期會讓呼叫方停止等待,但伺服器應用程式仍要停止已衍生的工作;跨服務傳遞也要依使用的語言和框架驗證。HTTP 用戶端同樣需要連線、回應和總預算,不能把 socket 預設值當成業務 SLO。
重試由業務語意決定。HTTP 的 GET、HEAD、PUT、DELETE 在規範中具有安全或冪等屬性,但實作仍須符合方法語意;POST 只有在冪等鍵等機制保證結果時才能安全重試。gRPC 方法名稱沒有自動冪等性,必須在契約中標示哪些狀態和操作可重試。兩者都要限制嘗試次數、遵守剩餘 deadline,並避免 Gateway、SDK 和業務層疊加重試。
串流 RPC 還要處理慢速消費者、背壓、單筆訊息上限、斷線後的恢復位置和重複事件。若消費者必須從游標恢復,事件需要穩定 ID 或序號;只重新建立 stream 不能證明不漏不重。
第四步:讓契約能滾動演進
REST/JSON 的相容要同時檢查結構和語意。新增回應欄位只有在用戶端允許未知欄位時才安全;改變預設分頁、排序或列舉含義,可能維持 JSON 可解析卻破壞業務。用 OpenAPI diff、最後一版公開 SDK、錄製請求回放和端到端斷言做門禁。
Protobuf 新增欄位通常維持二進位線路相容,舊讀取方會忽略未知欄位,但應用程式碼仍可能因新列舉或預設值出錯。不得更改既有欄位編號;刪除欄位後保留編號和名稱,防止未來重複使用。滾動部署時要同時測試舊用戶端—新伺服器和新用戶端—舊伺服器,不能只讓同版本互測。
若邊緣和內部都需要同一能力,優先共用領域邏輯和一份明確的契約真源,再以 JSON 轉碼或薄 Adapter 暴露 HTTP。Adapter 要明確對應 HTTP 狀態、gRPC status、headers/metadata、串流限制和欄位名稱。維護兩份人工同步的業務實作會產生行為漂移。
第五步:用資料決定是否遷移
基準測試使用真實分布的資料內容和方法比例,不只序列化一個小物件。對相同業務邏輯測端到端吞吐、p50/p95/p99、用戶端與伺服器 CPU、傳輸位元組、連線數和記憶體;分別涵蓋 unary、小訊息、大訊息、壓縮、伺服器串流、慢速消費者和跨可用區網路。資料庫與下游呼叫保持相同,才能隔離協定影響。
先讓一個低風險內部方法雙棧運行,依呼叫方逐步開放。比較業務結果、錯誤分類、deadline exceeded、取消後仍持續的工作、重試放大和 Trace 完整率。達到預先寫下的收益與可靠性門檻才擴大;沒有明顯收益時保留 HTTP API 也是有效結論。
高品質示範回答
「我不會替整個平台統一貼上 REST 或 gRPC 標籤。先看誰呼叫、誰控制升級,以及通訊模式。
公開瀏覽器、行動 App 和合作夥伴介面我會用 REST 風格的 HTTP+JSON。它能直接利用瀏覽器和通用 HTTP 生態,也能用 OpenAPI 做機器可讀契約、SDK 和相容檢查。第三方 webhook 繼續接收 HTTP POST,因為發送方協定是外部限制;入口驗章、以事件 ID 去重、先持久化再非同步處理。
內部介面中,本題有每秒 20,000 次呼叫和伺服器串流。我會用真實資料做基準測試。如果呼叫方都能使用產生的用戶端,Proxy、負載平衡和監控也支援 gRPC,而且 p99、CPU 或頻寬收益達到門檻,我會在這個邊界採用 gRPC。unary 和 stream 都在 .proto 定義,但每次呼叫仍明確設定 deadline,取消後伺服器停止衍生工作,重試只用於契約宣告為安全的操作。
演進方面,HTTP API 用 OpenAPI diff、舊 SDK 和語意回放防止分頁或列舉破壞;Protobuf 不修改欄位編號,刪除欄位後 reserve,並進行新舊用戶端交叉測試。若公開與內部要共用能力,我會讓同一領域實作分別由 HTTP Adapter 和 gRPC 服務呼叫,或在確認對應完整時使用 JSON 轉碼。
最後依呼叫方逐步開放,觀察端到端 p99、CPU、位元組數、狀態對應、重試放大和 Trace 完整率。若收益只存在微基準,正式環境瓶頸仍在資料庫,我不會為了協定一致而擴大遷移。」
常見錯誤
- 把 REST 等同於 HTTP/1.1 → 混淆 HTTP 語意與傳輸版本 → 說明 REST API 也能執行於 HTTP/2 或 HTTP/3。
- 宣稱 gRPC 永遠比較快 → 真正瓶頸可能在儲存、業務邏輯或 Proxy → 以同一業務邏輯和真實資料測端到端尾端延遲、CPU 與頻寬。
- 認為 REST 沒有強契約 → 忽略 OpenAPI 的描述、程式碼產生和測試生態 → 比較實際契約流程,不比較團隊疏於維護後的結果。
- 讓瀏覽器直接呼叫原生 gRPC → 瀏覽器缺少原生 gRPC 所需控制能力 → 選 gRPC-Web、JSON 轉碼或 BFF,並計入功能限制。
- 未設定 gRPC deadline → 用戶端可能無限等待並占用資源 → 從端到端預算推導 deadline,並驗證取消傳遞。
- 把 Protobuf 線路相容當業務相容 → 新列舉、預設值和欄位含義仍會破壞舊程式碼 → 做新舊版本交叉測試並保留欄位編號。
- 所有層都自動重試 → 故障時請求成倍放大,寫入還可能重複 → 集中重試,限制安全操作、剩餘預算和嘗試次數。
- 為了統一只保留一種協定 → 犧牲公開串接成本或內部串流需求 → 允許邊緣 HTTP、內部 gRPC 的明確邊界。
追問與應對
追問一:內部介面只有 500 QPS,還該用 gRPC 嗎?
QPS 不能單獨決定。若已有成熟 gRPC 平台、跨語言產生契約與串流需求,500 QPS 仍可能適合;若團隊只有單純 CRUD 服務、HTTP 工具成熟且效能足夠,REST/HTTP+JSON 的維運成本較低。先寫出要改善的指標,無法證明收益就不遷移。
追問二:公開行動 App 可以使用產生的 gRPC 用戶端,能直接對外提供 gRPC 嗎?
可作為受控用戶端的候選,但仍要檢查 Proxy、企業網路、除錯工具、版本相容、憑證和發布節奏。合作夥伴與瀏覽器可能仍需 HTTP API,所以公開 gRPC 不會自動消除雙協定成本。應依呼叫方分組,而非只看「公網」標籤。
追問三:如何從一份 .proto 同時提供 JSON API?
可以替方法加入 HTTP 對應並使用 JSON 轉碼或 Gateway。上線前逐項驗證欄位命名、空值與預設值、HTTP 狀態和 gRPC status、metadata 與 headers、驗證、快取及 streaming 限制。用 .proto 當契約真源能減少重複,但轉接語意仍需測試。
追問四:gRPC stream 斷線後如何確保不漏事件?
協定只提供串流和單次 RPC 內的訊息順序,不會自動提供業務級持久訂閱。事件應有穩定序號,伺服器保留可重播 Log,用戶端提交或攜帶已消費游標,重連時從游標繼續;再定義保留期、過期處理和去重。若需求更像訊息系統,應比較 Queue 或 Log,而非勉強延長 RPC。
追問五:基準測試顯示 gRPC p99 較低,但上線後沒有改善,如何排查?
先拆解用戶端排隊、DNS/連線、Proxy、序列化、服務業務邏輯、資料庫和下游耗時。檢查線上資料分布、壓縮、連線複用、TLS、跨區路徑和隱藏重試是否與基準一致。若協定只占總延遲的一小部分,應優化主導瓶頸,不要繼續擴大遷移。