gRPC從源碼角度分析客戶端和服務端一次交互的全流程

  • 2019 年 11 月 13 日
  • 筆記

我們知道RPC框架是一個CS的架構,有服務的提供者,有服務的消費者,那麼一次RPC請求到底經歷也什麼了?這篇文章一起從源碼揭秘gRPC的一次請求生命周期,從其中我們探尋RPC框架設計時一些必要的模組,進行抽象總結。

文章較長,希望大家有耐心。

客戶端發送一次請求的過程分析

在看客戶端如何發起一次請求時,我們先看看pb文件,和生成的pb.go文件,事實上常規的rpc請求和流式的rpc請求是不一樣的,這裡我們主要分析常規的rpc請求(也就是一次請求,一次響應)

通過protobuffer工具生成pb.go文件,這個文件中包含的資訊比較多,這裡我們先主要看對HelloService服務的描述資訊

我們從HelloWorld的RPC請求看起,看看這個一次請求,一次響應是怎麼執行的,首先在pb.go文件中,我們看到客戶端使用的api的定義,如下程式碼

這個HelloWorld方法接受三個參數:

  1. content 上下文參數,
  2. HelloRequest 請求入參,
  3. grpc.callOption

這裡著重說一下第三個參數,前兩個參數相信大家都知道是什麼意思,看gRPC中對這個參數的定義和描述,很清楚的知道這是一個介面,介面中定義了before方法和after方法,看如下注釋很容易明白的,我們可以自定義結構體實現自己想要處理的一些邏輯。

接下來我們看看客戶端api的實現,也是在pb.go文件中,核心是Invoke方法,

當我們在程式碼中發起調用時,像如下程式碼一樣傳入參數,第三個參數我們可以傳入一個空的 CallOption,這是grpc提供的默認實現,這個實現在rpc_util.go文件中。事實上,grpc提供了很多默認實現,都在這個文件中,這不是本次的重點,就不展開說了

最後我們深入invoke方法中做了什麼,invoke方法在call.go文件中

在invoke方法中主要做了如下如下事情

  1. 創建客戶端流對象(在這個方法中發送前執行befor方法),這個方法中主要初始化一些流對象參數,比如超時時間,發送最大消息大小,接受最大消息大小,
  2. 發送請求
  3. 接受服務端響應 (在接受響應後執行after方法)

我們進入到SendMsg中看看消息是如何發送出去的

我們再進入RecvMsg中看看客戶端是如何接受消息的

服務端處理一次請求的過程分析

在之前的文章gRPC-Server啟動做了哪些事,詳細分析了gRPCServer的啟動流程,這篇文章我們接著看看服務端監聽到一個客戶端連接之後,是如何處理這個請求的。

grpc.Server(listener)中有如下片段程式碼

我們主要分析的是在handleRwConn方法中做了哪些事

  1. 設置連接建立的超時時間
  2. 許可權認證
  3. 創建基於http2的連接
  4. 處理請求

繼續深入ServerStreams()方法看看是如何處理客戶端請求的

HandleStream方法中主要是循環讀取http2協議發送的各種幀,然後交給不同的方法去處理,其中MetaHeadersFrame幀會觸發調用服務端的服務實現,traceCtx主要負責跟蹤執行過程。這裡省略很多程式碼,感興趣的去閱讀源碼,文章里就不粘貼了。

最後我們看看這個真正調用我們自己業務服務程式碼的方法是做了什麼,省略很多非核心的程式碼,這樣流程比較清晰 s.handleStream(st,stream,s.traceInfo(st,stream))

總結

深入閱讀進去,你會發現源碼並不是特別難懂,關鍵在於踏出第一步,上面分析了grpc從客戶端發起請求到服務端接受處理的全流程,中間也有很多細節並沒有說,比如鑒權,比如創建http2服務,攔截器執行,trace跟蹤等,尤其是錯誤處理,但本篇文章重點是帶領大家貫穿整個流程,把從客戶端發起請求到服務端處理銜接起來,並不是把所有細節說明白,一篇文章也說不明白,最後我用一張圖表述整個流程,讓大家更加清晰的理解。