最近決心開始學習go語言,可是苦於沒有實際的應用場景,學習始終停留在hello world層面,看過的教程和資料印象也不深入。因而決定從go自帶的rpc實現開始切入,瞭解一下go語言在實際場景下是如何使用的,包括異常處理、代理和過濾、go routine的用法等等,同時也簡單瞭解了一下其餘rpc的go語言實現,好比thrift和grpc等等。一陣蜻蜓點水,稍微加深了印象,也開始慢慢體會到go語言和java語言的種種差別和共性。接下來,爲了進一步鞏固學習效果,也算是爲了對本身目前爲止的職業生涯作一次複習和彙報,決定使用go語言從零開始構建一個比較完整的RPC(或者說是微服務)框架。java
微服務框架和RPC框架git
本文中提到RPC框架,指的是提供基礎的RPC調用支持的框架;而本文中提到的微服務框架,指的是包含一些服務治理相關的功能(好比服務註冊發現、負載均衡、鏈路追蹤等)的RPC框架。github
在動手開始作以前,須要先了解學習一下其餘現有的產品,能夠從中學習一下優秀的經驗和方法,這裏列舉一下初步瞭解到的幾個框架:apache
以上就是目前瞭解過的幾個已有的框架,比較慚愧的是瞭解得都不夠深刻,後續還要持續學習。設計模式
Pluggable Interfacesbash
值得一提的是除了thrift,其餘三個稱得上微服務框架的產品,其特性都包含Pluggable Interfaces,也就是能夠經過插件替換部分功能。經過插件實現可替換的功能,實際上在一個微服務框架中基本是最低要求了,不然後續的功能擴展將會變得十分困難,相信我,這裏是飽含血淚的經驗之談。網絡
在開始着手設計甚至是編寫代碼之前,咱們首先分析一下咱們的需求(來自學習軟件工程中的成果)。同時對於一部分可能不太熟悉RPC相關細節的同窗來講,對咱們後面要作的事情心中也可以有一個大體的概念。這裏就直接列舉幾個功能性需求:負載均衡
有了大體的需求,接下來就能夠開始着手設計了。首先咱們將框架劃分爲若干層,層與層之間約定經過接口交互。這裏就不要問爲何須要分層了,非要問就是經驗。分層做爲一種經典到不能在經典的設計模式,幾乎在軟件開發過程當中無處不在,在RPC框架當中也十分適用,下面畫出大體的層次圖:框架
上面提到的各個層,除了service,實際上能夠提供多種實現,因此應該都以plugin的方式實現。異步
這樣一來按照咱們劃分的層次,一個客戶端從發出請求到收到響應的流程大概就是這樣:
服務端的邏輯比較相似,這裏就不畫圖了。經過上面的層次劃分能夠看到,一個請求或者響應實際上會依次穿過各個層而後經過網絡發送或者到達用戶邏輯,因此咱們採用相似過濾器鏈同樣的方式處理請求和響應,以此來達到對擴展開放,對修改關閉的效果。這樣一來對於一些附加功能好比熔斷降級和限流、身份認證等功能均可以在過濾器中實現。
接下來設計具體的消息協議,所謂消息協議大概就是兩臺計算機爲了互相通訊而作的約定。舉個例子,TCP協議約定了一個TCP數據包的具體格式,好比前2個byte表示源端口,第3和第4個byte表示目標端口,接下來是序號和確認序號等等。而在咱們的RPC框架中,也須要定義本身的協議。通常來講,網絡協議都分爲head和body部分,head是一些元數據,是協議自身須要的數據,body則是上一層傳遞來的數據,只須要原封不動的接着傳遞下去就是了。
接下來咱們就試着定義本身的協議:
-------------------------------------------------------------------------------------------------
|2byte|1byte |4byte |4byte | header length |(total length - header length - 4byte)|
-------------------------------------------------------------------------------------------------
|magic|version|total length|header length| header | body |
-------------------------------------------------------------------------------------------------
複製代碼
根據上面的協議,一個消息體由如下幾個部分嚴格按照順序組成:
協議中消息頭的數據主要是RPC調用過程當中的元數據,元數據跟方法參數和響應無關,主要記錄額外的信息以及實現附屬功能好比鏈路追蹤、身份認證等等;消息體的數據則是由實際的請求參數或者響應編碼而來。 在實際的處理中,消息頭在發送端一般是一個結構體,在發送時會被編碼成二進制添加在消息頭的前面,在接收端接收時又解碼成一個結構體,交給程序進行處理。這裏試着列舉消息頭包含的各個信息:
type Header struct {
Seq uint64 //序號, 用來惟一標識請求或響應
MessageType byte //消息類型,用來標識一個消息是請求仍是響應
CompressType byte //壓縮類型,用來標識一個消息的壓縮方式
SerializeType byte //序列化類型,用來標識消息體採用的編碼方式
StatusCode byte //狀態類型,用來標識一個請求是正常仍是異常
ServiceName string //服務名
MethodName string //方法名
Error string //方法調用發生的異常
MetaData map[string]string //其餘元數據
}
複製代碼
第一篇文章就到此爲止了,主要先作一下準備,整理一下思路,若是有不正確或者不合理的部分還請你們多多指教。