1. 程式人生 > >NET Core微服務之路:自己動手實現Rpc服務框架,基於DotEasy.Rpc服務框架的介紹和整合...

NET Core微服務之路:自己動手實現Rpc服務框架,基於DotEasy.Rpc服務框架的介紹和整合...

本篇內容屬於非實用性(拿來即用)介紹,如對框架設計沒興趣的朋友,請略過。 

快一個月沒有寫博文了,最近忙著兩件事;

   一:閱讀劉墉先生的《說話的魅力》,以一種微妙的,你我大家都會經常遇見的事物,來建議說話的“藝術和魅力”,對於我們從事軟體開發、不太善於溝通和表達的朋友來說,也算是一項軟技能了,推薦喜歡閱讀的朋友有時間閱讀,給你不一樣的閱讀體驗。

640?wx_fmt=png

二:編寫基於Net Core的Rpc框架。之前有朋友說如何將Rpc等整個體系整合到dotnet框架中,我想這篇博文會給你一個答案。

哦,對了,我不建議直接將程式碼直接複製下來然後去執行的朋友,因為這樣你達不到學習的目的,也違背了筆者的初衷。謝謝理解。

 

一:簡單回顧一下之前的介紹

繼續貼上之前的一張圖片

 

640?wx_fmt=png

 

根據上面圖,服務化原理可以分為3步:

  1. 服務端啟動並且向註冊中心傳送服務資訊,註冊中心收到後會定時監控服務狀態(常見心跳檢測);

  2. 客戶端需要開始呼叫服務的時候,首先去註冊中心獲取服務資訊;

  3. 客戶端建立遠端呼叫連線,連線後服務端返回處理資訊;

 

  第3步又可以細分,下面說說遠端過程呼叫的原理:

目標:客戶端怎麼呼叫遠端機器上的公開方法

  1. 服務發現,向註冊中心獲取服務(這裡需要做的有很多:拿到多個服務時需要做負載均衡,同機房過濾、版本過濾、服務路由過濾、統一閘道器等);

  2. 客戶端發起呼叫,將需要呼叫的服務、方法、引數進行組裝;

  3. 序列化編碼組裝的訊息,這裡可以使用json,也可以使用xml,也可以使用protobuf,也可以使用hessian,幾種方案的序列化速度還有序列化後佔用位元組大小都是選擇的重要指標,對內筆者建議使用高效的protobuf,它基於TCP/IP二進位制進行序列化,體積小,速度快。

  4. 傳輸協議,可以使用傳統的IO阻塞傳輸,也可以使用高效的nio傳輸(Netty);

  5. 服務端收到後進行反序列化,然後進行相應的處理;

  6. 服務端序列化response資訊並且返回;

  7. 客戶端收到response資訊並且反序列化;

 

  至於C類和S類之間的通訊方式,是採用RPC還是採用RESTful,讀者可以參考之前的介紹,根據實際業務進行決定https://www.cnblogs.com/SteveLee/p/service_discovery_and_service_governance.html

 

二:DotEasy.Rpc框架介紹

  單論Rpc框架市場,且不論Java上的Spring Boot和Spring Cloud這樣大名鼎鼎的開源框架,目前Net上的Rpc整合性框架確實並不多,我們Net程式設計師也要混口飯吃,不能總被Java甩掉好幾條街吧。

  言歸正傳,一個遠端過程呼叫,會涉及到如下幾個方面的技術點(功能):

  1. 路由轉發:當服務部署在多個節點上時,呼叫方需要知道自己的目標服務在什麼地方。

  2. 通訊協議:當管道存在,還需要在管道的兩端建立處理程式(宿主),以處理管道中的資料包。DotEasy.Rpc基於DotNetty進行通訊處理和協議實現。

  3. 動態生成:我們知道,基於二進位制的RPC傳輸,每當新增介面,或修改介面,都需要生成相關協議的protobuf檔案(或 thrift 檔案),本框架基於protobuf-net的傳輸框架和Rosyln的預生成,動態生成相關的CS檔案。

  4. 執行時代理:本框架採用一次請求建立一個客戶端的模式,進行S端的服務請求,本框架基於Rosyln執行時建立客戶端代理。

  5. 介面掃描:需要實現多個介面的新增和註冊,本框架採用Attribute特性來掃描介面,並新增到相關的註冊中心,比如consul或etcd。

 

2.1 解決方案介紹:

640?wx_fmt=jpeg

  整體解決方案不做過多介紹,相信單詞的詞義已經表達了這個專案的作用。

  小插曲:筆者曾用Easy.Rpc做為專案的主庫名稱,但上傳到nuget後才發現,原來也有名為easy.rpc的包,不過筆者並沒找到這個開源作者的網站,無賴之下,想到“點”這個詞,也是dotnet的dot開頭部分,索性乾脆也叫DotEasy了吧。

  筆者的目的,是想通過這個框架(或類庫集),來簡化微服務的部署和開發,讓希望從事微服務NET開發的朋友不再找不到從何入手的窘境。

在Nuget.org上能直接下載編譯過的包,地址:https://www.nuget.org/packages/DotEasy.Rpc/,推薦使用這種方式進行安裝。

  如果喜歡研究原始碼,筆者同樣也貼上地址:https://github.com/steveleeCN87/doteasy.rpc,不過原始碼不包含任何依賴,如果編譯中出現版本問題,可聯絡筆者。目前Github和Nuget上就放上了DotEasy.Rpc核心庫和DotEasy.Rpc.Consul擴充套件包,etcd和entry還在測試階段,就不方便拿出來獻醜了。o(∩_∩)o 哈哈

  當然,也歡迎廣大愛好開源的朋友加入,共同為NET開源專案做貢獻。

 

2.2 主專案介紹:

640?wx_fmt=png

Attributes:用於標註該介面為某一特性的方法體,當系統通過Microsoft.Extensions.DependencyInjection自動構建成功後,框架將自動掃描介面上標註過[RpcTagBundle]的介面,形成目標介面列表,以供下面的方法呼叫。

Core:該核心分為Server和Client以及核心通用三個部分組成:

640?wx_fmt=png

  Client:主要用於通過Ip地址遠端呼叫的方法Invoke實現,以及遠端服務端的檢查檢查實現。

  Communally:通用方法庫,包括唯一ID生成器(用於標識每次請求所產生的唯一客戶端)、通用物件轉換器(將複雜物件轉換為喜歡預設物件)、異常處理器、序列化生成器。

  Server:提供服務宿主,服務執行者、服務入口處理、服務管理、服務定位、服務管理工廠的介面及預設實現。

Proxy:執行時預生成及客戶端動態代理模組的介面和方法實現。

640?wx_fmt=png

Routing:提供地址定位、服務描述、服務路由的介面和方法實現。

640?wx_fmt=png

Transport:基於protobuf-net和dotnettey構建的二進位制序列化、和通訊管道和宿主的介面和實現。

640?wx_fmt=png

所有主要類均已介面的形式提供介面名稱,和已預設實現的具體方法體(虛方法),方便在這個基礎上進行擴充套件和重寫。

 

2.3 主專案介面

本節簡單介紹一下DotEasy.Rpc主框架的介面的作用。

640?wx_fmt=png

RpcTagBundleAttribute.cs:所有標記過[RpcTagBundle]特性的介面均會被掃描至doteasy.rpc框架中;

IAddressResolver.cs:地址解析器,提供IPv4地址解析作用,用於IServiceRouteFactory和IRemoteInvokeService定位操作;

IRemoteInvokeService.cs:遠端呼叫服務介面,提供遠端服務呼叫的關鍵介面,通過IServiceProxyFactory介面代理呼叫;

IServiceEntryFactory.cs:服務入口工廠介面,對全域性服務入口的統一的工廠操作,例如新增,監聽,移除,修改服務入口等;

IServiceEntryLocate.cs:服務入口定位介面,通過IAddressResolver過濾和解析,實現服務入口的定位;

IServiceEntryManager.cs:服務入口管理全域性管理介面,功能同IServiceEntryFactory相似,但提供更多的服務管理操作,比如負載均衡(採用輪詢實現);

IServiceEntryProvider.cs:服務入口提供者介面,一個簡單的服務入口提供者程式;

IServiceExecutor.cs:執行服務方法介面,執行遠端服務的IRemoteInvokeService;

IServiceHost.cs:服務宿主介面,DotNetty的服務宿主,類似own框架的自宿主程式,提供請求和響應操作;

IServiceProxyFactory.cs:服務代理工廠介面,客戶端代理(預編譯)的所有操作實現;

IServiceProxyGenerater.cs:服務代理生成介面,客戶端代理(預編譯)生成器;

IServiceRouteFactory.cs:服務路由工廠介面;

IServiceRouteManager.cs:服務路由全域性管理介面;

ITransportMessageCodecFactory.cs:管道訊息傳輸工廠介面;

ITransportMessageDecoder.cs:管道訊息解碼器介面;

ITransportMessageEncoder.cs:管道訊息編碼器介面;

IMessageListener.cs:管道訊息監聽介面,可實現一個訊息的接受者和處理程式;

IMessageSender.cs:管道訊息傳送介面,可實現一個訊息的傳送者;

 

  通過上面的框架和㢟就能實現客戶端到服務端的RPC通訊了嗎?當然不是,還需要服務註冊中心(例如Consul,etcd,zookeeper)來實現。上面只是提供了路由轉發,服務定位,客戶端預編譯的實現等等功能而已,而服務的註冊並沒提供,因為它不屬於基礎框架的範疇,筆者對zookeeper的笨重太反感(當然不是說它不好),而consul和etcd十分輕量級,特此又專門新增了兩個專案:DotEasy.Rpc.Consul和DotEasy.Rpc.Etcd,用於實現不同註冊中心的註冊(獲取)方法,和健康檢測機制。

 

  當然,介紹Consul和Etcd如何實現不是本節的重點,DotEasy.Rpc這個框架的完全剖析也將在日後新開篇章中專門介紹如何去實現一個框架,想必大部分朋友關心的是這個框架能做什麼,有什麼樣的功能,那麼,接下來開始吧。

 

三:如何使用

本系列一直重複的那張圖片,噼裡啪啦噼裡啪啦......此處省略三百字。繞來繞去,難以入手,正如上一篇有朋友推薦如何在Asp.net core中整合、等等。

功能和特性如下:

  1. Apache許可證2協議開放原始碼;

  2. 統一元件裝配和構造;

  3. 基於protobuf-net實現位元組流序列化;

  4. 基於Rosyln的執行時客戶端代理生成;

  5. 基於Rosyln的預生成的客戶端代理;

  6. 基於DotNetty的傳輸通道;

  7. 支援客戶端以輪詢的方式實現負載平衡;

  8. Net Core結構及跨平臺;

就這麼點,不囉嗦,讓我們開始做DEMO。

 

3.1 建立服務介面和服務實現

既然是服務,那麼肯定需要以介面interface的方式實現對外暴露,並且,介面的實現不能和介面封裝在同一個DLL中,不然還叫什麼遠端過程呼叫呢(RPC)呢,如果不瞭解什麼叫介面,去翻一翻C#語言規範。

先定義一個介面,介面方法簽名如下:

640?wx_fmt=png

很簡單,不解釋。

其中介面上有個重要特性叫[RpcTagBundle],該特性只允許標記在interface介面上,用於啟動時方便框架掃描所有標有該特性的介面。 

在看實現類

640?wx_fmt=png

再次重申:注意兩篇程式碼中的名稱空間,就是兩個不同的程式集(兩個專案),千萬不能以為筆者僅僅是為了區分而已。

 

3.2 建立asp.net core mvc應用程式

新建一個asp.net core mvc應用程式,模版預設webapi,新增一個控制器HeathController,當然,控制器名稱你也可以自由發揮,鍵入如下程式碼:

640?wx_fmt=png

也十分簡單,對外暴露一個路由地址為"api/Health"的API介面,提供GET方法,返回OK資訊。

這個介面的作用是用於Consul對服務的健康狀態檢查回撥地址,Consul可以基於HTTP做健康檢查,也可以通過gRPC驗證服務健康狀態。

至此,一個WebApi就建立完成,對外不在通過HTTP做任何介面暴露。

接下來我們新增一個IApplicationBuilder的擴充套件,用於啟動Rpc服務端,程式碼如下:

640?wx_fmt=png

(1):例項化一個BaseServer的服務型別,並使用IConfiguration作為引數。該BaseServer類是封裝太DotEasy.Rpc.Entry中的一個實現,主要是簡化呼叫者的程式碼構建能力,不然,截圖一個部分原始碼瞧瞧

640?wx_fmt=png

框架的目的是簡化編碼工作,構建RPC例項也不例外,100多行的構建程式碼只用3行實現,偷懶者必備。

(2):呼叫一次RegisterEvent委託事件,用於將介面和實現註冊到ServiceCollection容器中。

(3):啟動這個RPC服務,其實方法內還有構建ServiceCollection容器等等一大堆方法,你可以自己實現,也可以問筆者要原始碼。

 

配置檔案在哪兒,難道這樣就可以了?

當然不是,我們還需要一個appsettings.json的預設配置檔案,程式碼如下:

640?wx_fmt=png

這篇配置檔案很容易理解,這裡不再重複囉嗦的解釋。

 

至此,一個寄宿於Asp.net core的rpc服務就這樣搭建完成,可以啟動隨Consul啟動看看。

友情提示:建議將Asp.net core的預設日誌功能關閉,否則Consul會5秒傳送一個健康檢查請求過來,日誌會慢慢的變得十分臃腫,ConfigureLogging((context, logging) => { logging.ClearProviders(); })即可移除Logging日誌功能。當然,你也可以做其他修改,畢竟日誌在專案中非常重要。

 

3.3 測試啟動Consul和Asp.net core

consul如何啟動這個,筆者就不再複述了吧,想必看過之前的文章,應該都會啟動和使用consul了,截個圖,喜悅一下

640?wx_fmt=png

3.1中的六個介面全被註冊到consul服務中,不信,我們訪問一個具體介面,看看meta資訊。

640?wx_fmt=png

路由和服務均通過這個資訊進行定位,我們可以知道,在127.0.0.1的9881埠上,可以訪問名為“doteasy.rpc.interfaces.IUserService.Exists_id”的介面。當然,目前是沒有任何驗證機制的,下一篇會介紹RPC中的統一驗證機制。

 

3.4 建立一個客戶端

廢話不多說,直接上程式碼:

640?wx_fmt=png

如你所見,我們就像在呼叫介面一樣的去呼叫了RPC遠端服務,中間的所有操作都是透明的,不需要關心的,唯一多了一句是Proxy<IUserService>();使用代理模式動態生成了RPC遠端客戶端,該操作又被筆者封裝在了BaseClient中,一切都為了使用者簡單。

 

3.5 跑跑客戶端看看結果

640?wx_fmt=png

640?wx_fmt=png

注意加粗的文字,是否跟呼叫介面一樣的結果呢,其他的除錯日誌目前請忽略。 

四:總結

這樣,通過asp.net core + consul + doteasy.rpc便實現了一個簡單的遠端服務呼叫,你可以嘗試部署到外網,看看是否是遠端呼叫,當然,目前並沒有任何的統一閘道器驗證,所以,任何人的機器都可以呼叫。


  


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


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

640?wx_fmt=jpeg