rpc的正確打開方式|讀懂Go原生net/rpc包
前言
最近在閱讀字節跳動開源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的核心組件源碼,共勉。
關注公眾號【程式設計師白澤】,我會同步分享部落格文章。