1. 程式人生 > >.NET Core微服務之路:讓我們對上一個Demo通訊進行修改,完成RPC通訊

.NET Core微服務之路:讓我們對上一個Demo通訊進行修改,完成RPC通訊

 最近一段時間有些事情耽擱了更新,抱歉各位了。

  上一篇我們簡單的介紹了DotNetty通訊框架,並簡單的介紹了基於DotNetty實現了迴路(Echo)通訊過程。

  我們來回憶一下上一個專案的整個流程:

  1. 當服務端啟動後,繫結並監聽(READ)設定的埠,比如1889。

  2. 當客戶端啟動後,繫結指定埠,等待使用者輸入。

  3. 當用戶輸入任意字串資料後,客戶端將這組資料進行轉碼為byte格式進行傳輸到服務端。

  4. 當服務端收到客戶端傳來的資料,進行轉碼後輸出控制檯,並將這組資料再次回傳到客戶端。

  5. 客戶端收到資料,也打印出來。

640?wx_fmt=png

 

  很簡單的實現了一個點對點的通訊例子。接下來我們將對這個DEMO進行簡單的修改,模擬最簡單的gRPC通訊的一個構造過程。

 

  本篇很簡單,只要實現了上一個demo,稍作修改,就能實現gRPC了(當然實際構建gRPC根本不會這麼簡單),本篇也是順帶一下這幾天搞出來的一個輕量級RPC框架,先接上一個例子。

 

服務端

增加兩個靜態方法SayHello和SayByebye,用於提供遠端呼叫,超級簡單,不解釋。

640?wx_fmt=png

在我們原來的ChannelRead函式中,將原有的Echo迴路傳輸,直接替換成如下內容。

640?wx_fmt=png

(1):有這樣一句話Replace(")", ""),筆者不知為何每次傳送過來從buffer裡轉義出來的字串,始終會有一個左括號在裡面,也許是訊息頭,也許是protobuf-net的標記頭,因為都是byte格式,在服務端偷懶就沒有再進行一次protobuf的反序列化了。

為何要用Dictionary來作為中間物件轉換,因為序列化需要實體物件作為型別,為了簡單的介紹RPC,目前也就這麼幹了,例如上面程式碼所示。

(2):通過判斷“func”欄位中的內容進行方法呼叫,並將呼叫過程的返回結果轉為BYTE格式。

(3):設定本次傳輸中的Buffer大小。

(4):將訊息(資料)寫入到DotNetty的Buffer。

(5):最終將Buffer寫入到當前上下文(包含通道,傳輸物件,連線物件等等)。 

客戶端

我們將上一個demo中的EchoClientHandler做如下修改,以完成一個簡單的請求

640?wx_fmt=png

(1):建立與服務端相關的通訊資料。

(2):將資料序列化為二進位制流。

(3):將資料寫入到ByteBuffer中。

 

啟動一下

由於在客戶端明文標註了使用sayHello這個方法,客戶端會收到服務端返回的"hello stevelee"。

640?wx_fmt=png

  這樣一個最簡單的RPC遠端呼叫就完成了(其實上一篇就也屬於RPC,只是這裡用方法和過濾來指定呼叫)。

 

 問題

  1. 服務端不可能都通過這樣笨拙的過濾方式來呼叫方法吧?是的,這只是DEMO,為了演示和理解基礎概念而已,而是要動過動態代理來實現方法Invoke。

  2. 這個DEMO只是一個點對點的遠端呼叫,不會涉及到任何服務路由和轉發等高階特性。

  3. 有新的介面的時候時候,需要重新編譯和暴露,如果有上萬個新的介面,這樣的重複工作豈不是瘋了。

  4. ...etc

  這裡推薦一下最近構建的一個小框架:Easy.Rpc(連線點我),實現了路由,轉發,代理,動態編譯的特性。這裡也幫朋友們推薦一個同樣基於DotNetty的RPC框架(連線點我)張隊推薦我加入他們,可我不知道怎麼加入他們的團隊,悲催啊...

 

  簡單介紹一下使用方法,本篇不詳細介紹這個框架是如何實現的,估計會好幾十萬字,單獨擰出來做個系列會更好,框架設計需要哪些原則,需要考慮到的問題,包含設計模式、依賴注入、動態代理、動態編譯、路由轉發等等特性。

 

Esay.Rpc

  正如上面提到問題,需要解決這些問題,就需要修改諸多內容,

 

  例如把函式改為介面,把介面的定義放置服務端並對外開放相應埠,把介面的實現同樣放置服務端,提供介面的呼叫,客戶端通過類似API的方式進行遠端介面呼叫,因此這個介面的定義必須單列的一個專案;

如何將介面自動部署(暴露)出來,可以通過中間協調器(也叫服務註冊中心,如ETCD,consul,zookeeper),如何將這些介面自動註冊到服務中心呢,需要實現反射自動掃描並新增到註冊中心。

 

  我們新增一個Rpc.Common的中間通用庫,當然Easy.Rpc的框架原始碼也在這個裡面(框架目前不探討),新增IUserService介面,UserModel實體類,UserServiceImpl實現類。其實通用類庫只需要介面和實體就行,介面實現完全放置服務端,這樣這個庫也能完全分離出來。(不過筆者偷懶都寫到Rpc.Common庫中去了,實際生產決不能這麼膜,分離,分離,分離,這也是微服務的主要概念之一)

 

  DEMO結構如下(Easy.Rpc原始碼目前也包含在這個裡面,過兩天單獨拎出來做成框架,方便呼叫)

 640?wx_fmt=png

 

先看看介面定義了些什麼:

640?wx_fmt=png

8個介面,幾乎囊括了目前RPC呼叫測試的所有方法場景。介面實現就不貼了,你完全可以自定義介面的任何實現,或者就一句Console.Write("哇涼哇涼完啦")都可以。

介面引數中有個UserModel的實體物件,這裡也貼上來。

640?wx_fmt=png

上面有兩個不一樣的標記,也是protobuf-net中獨有的特性。

ProtoContract標記:該類是參與序列化內容的資料類。

ProtoMember標題:該類需要序列化的欄位和順序。 

protobuf-net的坑

  1. 預設例子中該類沒有任何繼承,因此不會存在一個妖孽問題,但如果UserModel是一個子類,他繼承於一個父類,而這個父類也同樣擁有多個子類,直接ProtoContract參與序列化將會報錯,需要在特性上增加DataMemberOffset = x,此處的x不是字母,而是這個子類的一個序列化順序。比如有3個子類繼承同一個父類,前面兩個子類的偏移量分別是1和2,那麼這個類的偏移量將設定為3,以此類推。

  2. 預設的資料型別中,系統定義的標準型別沒問題,但有個妖孽的int[]這樣的陣列型別,那也將是個噩夢,官網團隊沒有解釋為何不支援陣列的序列化,我猜測估計是因為陣列的不規則性(比如多維陣列、甚至不規則的多維陣列)而放棄了這個型別的序列化,畢竟序列化是不能影響效能的。

接下來繼續服務端的程式碼

640?wx_fmt=png

640?wx_fmt=png

全程基於serviceCollection實現自動裝配和構造,相信用過Ioc容器都能明白這上面幾條依賴注入和自動構建服務的含義。

再新增客戶端程式碼:

640?wx_fmt=png

我想看到這裡,明白上面程式碼的作用,也就明白了這個框架的作用,客戶端能像呼叫本地方法一樣去呼叫遠端方法,並且中間過程是完全透明的,分離,分離,分離。

  微服務的作用不再介紹,呵呵。

原文地址:https://www.cnblogs.com/SteveLee/p/Simple_Rpc_Demo.html



  

.NET社群新聞,深度好文,歡迎訪問公眾號文章彙總 http://www.csharpkit.com

640?wx_fmt=jpeg