前言
最近在閱讀位元組跳動開源RPC框架Kitex的原始碼,分析了如何藉助命令列,由一個IDL檔案,生成client
和server
的腳手架程式碼,也分析了Kitex的日誌元件klog。當然Kitex還有許多其他元件:服務註冊、發現、負載均衡、熔斷、限流等等,後續我也會繼續分析。
我希望藉助這篇文章,用盡可能少的語言,配合分析Go原生net/rpc
包的部分核心程式碼,幫助你貫通RPC的知識,梳理RPC的運作流程,讓你對RPC有一個比較全面的認識。
以此為基礎,將有助於你在閱讀其他開源RPC框架原始碼時,對比發掘開源RPC框架具體做了哪些提高。
RPC的流程
遠端過程呼叫 (Remote Procedure Call,RPC) 是一種計算機通訊協議。允許執行在一臺計算機的程式呼叫另一個地址空間的子程式(一般是開放網路中的一臺計算機),而程式設計師就像呼叫呼叫本地程式一樣,無需額外做互動程式設計。
假設你要呼叫一個Add(a int, b int) int
方法,實現求和功能,但是這個方法部署在另一臺機器上,該如何呼叫?
這就是一次RPC的流程,甚至和HTTP請求/響應流程很像,眼下我先側重於介紹RPC的概念,以後會介紹其與HTTP的區別。
並且這裡暫時沒有涉及所謂的服務註冊、發現、負載均衡、熔斷、限流等字眼,這些都是一個成熟的RPC框架應該具備的功能元件,用於確保一個RPC框架的高可用,但是卻不是一個RPC框架所必需的。
RPC協議本質上定義了一種通訊的流程,而具體的實現技術是沒有約束的,每一種RPC框架都有自己的實現方式,比如你可以規定自己的RPC請求/響應包含訊息頭和訊息體,使用gob/json/pb/thrift
來序列化/反序列化訊息內容,使用socket/http2
進行網路通訊,只要client
和server
訊息的傳送和解析能對應即可。希望讀者仔細體會——“約定”這個概念,這將貫穿始終。
分析net/rpc
先講解一下流程圖中的序列化和網路傳輸部分,這是RPC的核心。
訊息編碼/解碼(序列化)
上面的RPC通訊流程圖,其中很重要的一環就是訊息的編解碼,訊息只有序列化之後,才能高效地參與網路傳輸。通過實現上圖net/rpc
包定義的介面,可以指定使用的編解碼方式,比如net/rpc
包預設使用了gob
二進位制編碼:
服務端負責序列化的結構gobServerCodec
的實現了ServerCodec
介面,服務端需要編解碼訊息的地方,都會呼叫gobServerCodec
的對應方法(客戶端也是類似的實現,也是一樣使用gob
編解碼)。
訊息的網路傳輸
訊息序列化之後,是需要用於網路傳輸的,涉及到客戶端與服務端的通訊方式。
這是服務端的接受連結的邏輯,和大部分網路應用相同,server
監聽了一個ip:port
,然後accept
一個連線之後,會開啟一個go
協程處理請求與響應。
這是客戶端發起請求的方式,也印證了socket
網路程式設計的通訊模型。
理解了RPC的各個流程之後,就能梳理清楚RPC框架的各種元件是作用在哪個層面的,例如Kitex的網路庫netpoll
,雖然我未曾看過其原始碼實現,但是有理由猜測其是在網路通訊/傳輸部分做了提高。
Server端的設計
這是service
的結構,可以看到一個服務通過Map
可以繫結多個名稱的方法,提供呼叫,且對應service
需要提前註冊到服務端,這樣在客戶端請求達到時才能準確呼叫。
服務註冊主要引數是serviceName
和service
實體。
reflect.xxx()
:主要的工作就是通過反射的機制,解析所繫結的服務的名稱、型別等。
suitableMethods()
:解析一個service
繫結的所有method
。
serviceMap.LoadOrStore()
:將service
註冊到服務端server
的Map,如下是Server
的結構:
Client端的設計
這是Client
的結構:
codec
:編解碼的具體實現。seq
:RPC的序列號,每發起一個就計數增加,加入Map,且完成或失敗後從Map中移除。pending
:配合seq
工作的Map。
這是客戶端具體發起一次RPC請求的過程,當然一次具體的RPC請求可以是同步的,也可以是非同步的:
client.Go()
是非同步的。client.Call()
是同步的,且其內部就是呼叫了client.Go()
,但是因為其呼叫之後,在呼叫完成之前,會被阻塞在chan
上,因此後續的RPC請求必須等待傳送。
小結
到此為止我們粗淺的分析了net/rpc
的一些核心原始碼,藉此梳理了RPC的工作流程,主要包括:
- RPC的編解碼(序列化)協議選擇
- RPC的網路通訊/傳輸模型(Socket程式設計)
- RPC的請求發起/響應接受(同步/非同步)
RPC的功能元件
一個成熟的RPC框架只實現基本的通訊功能是不夠的,否則它將十分的脆弱,沒有任何應對服務當機的能力,在高併發場景下也難堪重任,因此需要增加很多的功能元件來提高服務的可靠性:
- 超時控制|請求重試|負載均衡|熔斷器|限流器|日誌|監控|鏈路追蹤|...
(Go原生net/rpc
包也有很多提高可靠性的設計,本文沒有過多展開)
結束語
這篇文章,我藉助Go原生net/rpc
包的部分核心原始碼,梳理了RPC的工作流程,試圖幫助你建立RPC的全域性觀念,希望你明白,RPC框架是對RPC通訊流程的具體實現,每一個框架為提高自身的可靠性,又延伸出了多種功能元件。
後續的文章我也將繼續分析位元組跳動開源RPC框架Kitex的核心元件原始碼,共勉。
關注公眾號【程式設計師白澤】,我會同步分享部落格文章。