rpc的正確打開方式|讀懂Go原生net/rpc包

前言

最近在閱讀字節跳動開源RPC框架Kitex的源碼,分析了如何藉助命令行,由一個IDL文件,生成clientserver的腳手架程式碼,也分析了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進行網路通訊,只要clientserver消息的發送和解析能對應即可。希望讀者仔細體會——「約定」這個概念,這將貫穿始終。

分析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需要提前註冊到服務端,這樣在客戶端請求達到時才能準確調用。

服務註冊主要參數是serviceNameservice實體。

  • reflect.xxx():主要的工作就是通過反射的機制,解析所綁定的服務的名稱、類型等。
  • suitableMethods():解析一個service綁定的所有method
  • serviceMap.LoadOrStore():將service註冊到服務端serverMap,如下是Server的結構:

Client端的設計

這是Client的結構:

  • codec:編解碼的具體實現。
  • seqRPC的序列號,每發起一個就計數增加,加入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的核心組件源碼,共勉。

關注公眾號【程式設計師白澤】,我會同步分享部落格文章。

Tags: