造輪子系列之grpc(一)

作為一個程序猿,對造輪子這事情可以說是情有獨鍾,幾乎程序猿內心都存在一個夢想是去將開源的技術都實現一遍,所有從本篇開始,我會開一個造輪子系列。



前言

首先,看看這個,想必大家對下面這種簡歷看得比較多了吧?

  • 精通JAVA,Python,熟練掌握C++
  • 精通Redis,Memcached,Mysql
  • 精通Nginx配置,模塊開發
  • 精通Kafka,ActiveMQ 等消息隊列
  • 精通常用數據結構和算法
  • 精通網絡編程,多線程編程技術,高性能服務器技術
  • 精通tcp/ip協議棧,熟悉內核網絡子系統代碼
  • 精通nginx代碼及模塊開發

上面每一條都涉及好多輪子,每一個都是精通,如果真能做到。那這個人可以說是碼農中的戰鬥機。

那我們現在目標就是去做這個戰鬥機。而這個方法,就是自己去造輪子,造的目的不是為了在項目中使用自己造的輪子,而是為了去了解輪子的構造,然後自己動手去體會造輪子的過程。


後端的輪子們

說起後端的輪子們,大家都可以說出一大串來,我們大致來數一數啊。

  • 抗在最前面的:LVS,F5,HAProxy這類負載均衡
  • 接下來有Nginx,Apache,Lighttpd這類Http服務
  • http服務後則是各種容器,部署著我們的業務邏輯
  • 存儲這邊有Redis,Memcached這一類KV存儲器和緩存系統
  • 如果是多機部署,肯定還有Kafka,ActiveMQ這種負責解耦的消息隊列
  • 為了實現集群通信,肯定少不了Thrift這種RPC框架和Protobuf這種序列化技術
  • 再高端點,到了分佈式領域了,就是更多的輪子了。。zookeeper、raft等等
  • 還有大數據系列hadoop。spark。。。。。

本文先開始我們的第一個輪子,服務器通信需要用的數據序列化反序列技術:protobuf。



基礎輪子:protobuf

講基礎前,先附上一張極客時間中的一個技術需要從哪些角度來講的圖片,本文也會盡可能從這些個方面來講。

造輪子系列之grpc(一)

  • 應用角度
  • 問題:”幹什麼用“
  • 技術規範:”怎麼用“
  • 最佳實踐:”怎麼能用好“
  • 市場應用趨勢:“誰用,用在哪”
  • 設計角度
  • 目標:“做到什麼”
  • 實現原理:“怎麼做到”
  • 優劣侷限:“做得怎麼樣”
  • 演進趨勢:“未來如何”

正文

我們先回顧下上一篇內容:protobuf。通過上一篇文章,我們知道了protobuf是一種序列化、反序列化協議,開發之初是為了解決接口的兼容性問題,目前是主要用在內部服務之間RPC調用和傳遞數據。

那我們現在就來看下,如果要用在rpc調用中傳遞數據,我們應該怎麼做?

首先我們要知道rpc調用是幹什麼用的?

rpc即遠程過程調用,主要是為了讓我們能夠像調用本地函數一樣調用遠程服務。

那既然是遠程調用,遠程服務就有可能跟我們在不同機器上,此時我們通信就需要走網絡;或者服務就是本地的進程,我們可以選擇unix socket方式,但是不管遠程服務在哪,為了能夠實現遠程調用,我們首先都需要解決通信問題。

現在假設我們使用網絡通信,傳輸層使用tcp協議,我們現在基於tcp來傳輸數據,我們首先要解決的就是:我們怎麼界定數據的開頭和結尾(tcp是基於流的協議)。

為了解決界定問題,我們回想下protobuf的編碼方式:TLV(Tag,Length,Value),protobuf是能夠根據讀取到的頭和length來知道後續還有多少長度的value,其中 Tag = (field_num << 3) | wire_type。field_num 就是在 proto 文件中,給每個字段指定唯一的數字標識,而 wire_type 用戶標識後面的數據類型。

TLV方式只是編碼了結構中的單個field,假設我們現在有一個結構:

造輪子系列之grpc(一)

此處我們定義了一個通用的message結構,裡面有request結構和response結構,這樣子拿到界定好開始和結束邊界的數據後,我們就能夠進行反序列化了。。但這還是回到了最開始的問題,因為tcp是字節流協議,我們無法界定一個message的開始和結束。

那怎麼辦呢?我們在序列化的message之後加上一個長度字段,這個時候,我麼先讀取長度,然後就知道了後續message的長度了,就能夠進行反序列化了。

下面我們來實現下我們第一版的rpc通信。

我們先來看發送消息的代碼:

造輪子系列之grpc(一)

先寫消息頭,再寫二進制的消息體。

讀消息的代碼也是這樣子,先讀消息頭,再讀消息體。

完整的代碼可以看GitHub https://github.com/zhuanxuhit/go-in-practice/tree/master/wheel/rpc/grpc/v1,歡迎各位關注,後續會不斷更新輪子系列。

在這次實現中,我們也發現了很多問題,我們對於message到底代表是哪個請求,必須不停通過if else來判斷,

造輪子系列之grpc(一)

擴展性非常之差,那有什麼好的方法呢?

我們來看下現有的輪子的做法:分析包net/rpc

服務端上關鍵語句如下

造輪子系列之grpc(一)

rpc.Register(arith) 註冊service名和方法

具體裡面是將service名和方法註冊到了一個map中,

造輪子系列之grpc(一)

然後我們重點看下服務端是如何讀取請求,並且找到對應的處理邏輯的,此處我們關注點是:

  1. 如何界定一個消息的開始、結束
  2. 如何能夠有擴展性,能夠方便的新增方法

Net/rpc的消息頭如下:

造輪子系列之grpc(一)

我們通過一個字符串 service.method 來指明請求的具體服務,同時response也是通過service.method來標明響應是哪個請求

造輪子系列之grpc(一)

同時request 和 response 中的 seq來將請求和響應聯繫起來。

消息頭解析完後,net/rpc通過反射能知道具體的請求和返回參數,然後具體消息體body的編碼採用的是glob,gob是Golang包自帶的一個數據結構序列化的編碼/解碼工具,就跟我們之前介紹的protobuf功能是一樣的。

界定消息開始、結束 可擴展性 我們的rpc 2字節消息頭,指定後續protobuf的字節數 通過在request中不斷新增XXXrequest net/rpc service.method 字符串來界定消息體字節 只要在服務端註冊上 service.method 對應的處理方法

現在我們來實現下我們第二版的rpc通信,目的是可擴展。

先來看proto定義,MessageHeader定義了消息類型和消息體長度

造輪子系列之grpc(一)

服務端在處理數據時,先讀取頭,然後再讀取body,完整的代碼可以看 https://github.com/zhuanxuhit/go-in-practice/tree/master/wheel/rpc/grpc/v3

歡迎start,關注。

總結

本文介紹了rpc的基本概念,你應該知道為了實現rpc,我們要解決

  1. 通信問題
  2. 消息體邊界界定

接著我們以網絡通信為例子,介紹瞭如果要使用tcp作為傳輸協議,我們要如何設計消息協議,從而能夠在字節流中界定出每個消息體,為此我們設計了兩個版本的協議:

  1. 通過在消息頭指定消息體長度,從而界定長度
  2. 在消息頭中不僅有消息體長度和消息類型,提高可擴展性

後續我們會繼續看gRPC的協議,看一個距離一個完整的rpc我們還差哪些,我們怎麼能夠造出我們自己的輪子來。。。

come on,關注我,大家一起造輪子。。。。。。。


分享到:


相關文章: