🎯 什麼情境該想到我

當你「想讓一個系統像呼叫本地方法一樣,同步地請另一個系統做事並立刻拿回結果」的時候(RPC、遠端服務呼叫、Web API)。

⚙️ 怎麼用(步驟 / 公式)

意圖:讓每個應用把自己的部分功能開放成可被遠端呼叫的介面,其他應用透過同步呼叫來使用其功能與資料。

做法 / 結構要點:

  • 應用各自封裝資料,只透過介面(方法/服務)對外提供行為,隱藏內部實作。
  • 呼叫端同步等待被呼叫端回傳結果,語意上接近呼叫本地函式。
  • 相較共享資料庫,耦合的是「行為介面」而非資料模型,各系統可獨立維護資料。

★ 今天的 RPC:gRPC/REST/GraphQL

EIP 書裡今天
CORBA/DCOM/RMI/SOAP★ gRPC(HTTP/2 + protobuf)、REST/JSON、GraphQL、Connect
WSDL★ .proto(有 codegen、有向後相容規則)、OpenAPI
「像呼叫本地函式一樣」★ 這個理想已被放棄——現代 RPC 明確要求你處理 ctx、timeout、retry、error code
// ★ Go 的 gRPC 呼叫:所有「遠端」的現實都在簽章上
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)   // ★ 必須有超時
defer cancel()
resp, err := client.GetUser(ctx, &pb.GetUserRequest{Id: id})
if err != nil {
    if status.Code(err) == codes.DeadlineExceeded { … }   // ★ 錯誤要分類:能不能重試?
    return fmt.Errorf("get user %s: %w", id, err)
}

★★ 最重要的一段:RPC 最大的謊言是「它看起來像本地呼叫」

分散式運算的八個謬誤(Deutsch/Gosling,1994;至今仍然全中):

① 網路是可靠的        ② 延遲是零        ③ 頻寬是無限的      ④ 網路是安全的
⑤ 拓撲不會改變        ⑥ 只有一個管理者   ⑦ 傳輸成本是零      ⑧ 網路是同質的

具體後果:

// ★★ ① 部分失敗(partial failure)—— 本地呼叫沒有這個狀態
resp, err := client.Charge(ctx, req)
if err != nil {
    // ★ 扣款到底成功了沒?你不知道。
    //   可能:請求沒送到 / 送到了但回應丟了 / 對方處理中還沒回
    // → ★ 所以 RPC 也需要冪等(帶 idempotency key),和訊息一樣
}
 
// ★★ ② 可用性是相乘的
//    三個同步依賴,每個 99.9% → 0.999³ = 99.7%
//    → 一年多出約 16 小時的不可用。★ 而且這是你「什麼都沒做錯」的情況下

★ 這兩點是「同步 RPC vs 非同步訊息」真正的分水嶺:
訊息把「對方現在活著嗎」這個問題變成「對方遲早會處理」。

★ 同步 RPC 必須配的四件事(少一件就是事故等著發生)

// ① 超時 —— ★ 沒有超時的呼叫會讓你的連線池被慢速依賴吃光
// ② 重試 + 指數退避 + jitter,且只重試「安全的」錯誤
//    ★ 只對 DeadlineExceeded / Unavailable 重試;InvalidArgument 重試一萬次也一樣
// ③ ★ 斷路器 —— 對方掛了就快速失敗,不要繼續堆積請求
// ④ ★ 重試預算(retry budget)—— 三層各重試 3 次 = 最底層收到 27 倍流量

→ 見 工具-服務容錯設計、工具-限流器設計。

★ 什麼時候 RPC 是對的(不要一律改成訊息)

選 RPC選訊息
★ 呼叫端需要答案才能繼續(查餘額、驗證權限)呼叫端不需要立即結果
★ 請求-回應語義天然(讀取、查詢)通知、廣播
只有兩個服務、呼叫量小★ 一對多、需要削峰
★ 需要立即的錯誤回饋給使用者可以稍後補償
延遲預算很緊(< 100ms)★ 延遲容忍高

★ 判準:呼叫端在不在乎「現在」的結果?
在乎 → RPC;不在乎 → 訊息。
⚠️ 「為了解耦」而把所有讀取都改成訊息,會得到一個沒人能推理的系統——
查詢本來就該是同步的。

🧪 我實際套用的紀錄

  • (待填)

⚠️ 注意 / 什麼時候不適用

  • 同步等待造成時間耦合:被呼叫端變慢或當機,呼叫端也被拖住。

  • 受網路可靠性影響,遠端呼叫可能失敗、逾時、延遲,錯誤處理比本地呼叫複雜。

  • 一連串跨系統同步呼叫容易堆疊延遲並形成緊密相依;需要削峰或高可用時不理想。

  • ★★ 「像本地呼叫一樣」是 RPC 最大的謊言。部分失敗(不知道成功沒)是本地呼叫不存在的狀態。

  • ★ RPC 也需要冪等。帶 idempotency key,和訊息一樣——因為回應可能丟失而請求已生效。

  • ★★ 可用性是相乘的。三個 99.9% 的同步依賴 = 0.999³ ≈ 99.7%:一年不可用 ≈ (1−0.999³)×8760 ≈ 26.3 小時,單一服務是 0.001×8760 ≈ 8.8 小時,等於一年多出約 17.5 小時。

  • ★ 沒有超時的呼叫會吃光連線池,把對方的慢變成自己的掛。

  • ★ 只重試安全的錯誤(Unavailable、DeadlineExceeded),不要重試 InvalidArgument。

  • ★ 重試會放大:三層各 3 次 = 27 倍流量。要有重試預算與斷路器。

  • 不要為了解耦把查詢也改成訊息。 需要答案才能繼續的呼叫,本來就該是同步的。

  • gRPC 的 .proto 有相容性規則(欄號不可重用、不可改型別),違反了是靜默的資料損壞(見 工具-資料編碼與演進)。

🔗 相關工具