遠端程序呼叫(remote procedure call)
/ RPC /
在自己的機器上呼叫一個函式既簡單又熟悉:你寫 result = add(2, 3),答案就回來了。遠端程序呼叫是一個巧妙的把戲,讓你能寫出幾乎一模一樣的東西——result = add(2, 3)——即使 add 其實是在網路另一頭的另一台電腦上執行。RPC 的整個目標,就是讓一個網路呼叫看起來、感覺起來都像一個普通的本機函式呼叫,好讓程式設計師大致上可以忽略網路的存在。
用平白的步驟說明它的機制。在你這一側,坐著一個叫做客戶端樁(client stub)的替身函式。當你呼叫 add(2, 3) 時,樁並不做加法;它反而把參數 2 和 3 打包成一個訊息(這個打包動作叫做編組/marshalling),再把訊息送過網路。在遠端機器上,一個對應的伺服器樁收到訊息、把參數拆包、呼叫真正的 add、得到 5、再把 5 編組成一個回覆訊息送回來。你的客戶端樁把 5 拆包,當作什麼怪事都沒發生似地回傳給你。這些樁就是那層偽裝;它們把底下所有的 socket 與訊息傳遞全都藏了起來。
為什麼重要,以及這層抽象的破綻:RPC 是各種服務跨機器互相對話的骨幹。但這層偽裝並不完美,而假裝它完美會讓人惹上麻煩。本機呼叫絕不會就這麼憑空消失;遠端呼叫卻可能,因為有部分失效。如果你呼叫了一次、卻沒收到回覆,它到底跑了沒有?這逼你必須選擇一種失效語意。至少一次(at-least-once)是指系統會重試到聽見成功為止——但這個操作可能因此跑了兩次。至多一次(at-most-once)是指它絕不會跑兩次——但它可能跑了零次。恰好一次(exactly-once)是人人都想要、卻真的很難保證的。所以一個 RPC,永遠不像它所模仿的那個本機呼叫那麼天真無邪。
一個網頁應用呼叫 getUser(42)。它看起來像本機呼叫,但客戶端樁會把「42」編組、送到另一台機器上的使用者服務,那邊查出使用者 42、再把他的姓名與電子郵件編組回來。若回覆遺失,「至少一次」策略會重送 getUser(42)——這沒關係,因為讀取是可重複的;但你絕不會想對 chargeCard(42)(刷卡扣款)用「至少一次」。
一個打扮成本機呼叫的遠端呼叫。偽裝在失效時會露餡,所以語意很重要。
最危險的假設,就是以為 RPC 的行為和本機呼叫一樣。它並不一樣:它可能很慢、可能失敗,而且「沒回覆」不等於「沒執行」。請設計成冪等(idempotent,可安全重複)的操作,這樣「至少一次」的重試才不會造成傷害。