Redola.Rpc 解決了什么問題?
Redola.Rpc 是一個使用 C# 開發的 RPC 框架,代碼開源在 GitHub 上。目前版本僅支持 .NET Framework 4.6 以上版本,未來待系統穩健后再考慮移植 .NET Standard 和 .NET Core。
Redola.Rpc 在 0.3.2 版本中,嘗試解決幾個 RPC 設計問題:
- 我是誰?(Local Actor)
- 如何告訴別人我是誰?(Actor Directory)
- 我提供什么服務?(Service Catalog Provider)
- 如何告訴別人我提供什么服務?(Service Directory)
- 我需要的服務在哪里?(Service Discovery)
- 如何調用該服務?(Service Dynamic Proxy)
- 如何找到該服務?(Actor Directory)
- 如何發消息給該服務?(Remote Actor)
Actor 是什么?
Redola 定義的 Actor 模型代表着一個通信節點,使用 ActorIdentity 描述,包括節點類型 Type、節點名稱 Name、節點地址 Address、節點端口 Port。
Actor 與 Actor 之間是基於 TCP Socket 通信的,Actor 並不區分 TCP 的 Server/Client 端,它將 Server 和 Client 封裝在底層,為上層應用提供更便捷的傳輸定義和調用接口。Actor 模型提供了面向通道 Channel 的雙工通道,可以接收來自對端的消息,也可以發送消息給對端。
Actor 收發的消息是面向二進制數組的,它不關心具體發送的是什么消息,也不關心序列化格式。Actor 使用 ActorFrameHeader 定義傳輸消息頭,Header 攜帶消息體長度。
Actor 一旦建立連接,生成的 Channel 通道會自動進行 KeepAlive 雙向保活機制。通過 Actor 服務發現,可以與任意的 Actor 進行通信,無需再配置對端節點地址和端口。並且,針對相同 Type 的 Actor,還可以實現消息分發的負載均衡功能。
RPC 契約定義
Redola.Rpc 是基於契約模型通信的,使用 Protobuf 2 格式定義 IDL,並通過自動生成工具生成 Contract 契約定義。
例如,下面是定義 ICalcService 服務的 IDL 定義。
package Redola.Rpc.TestContracts; message AddRequest { required int32 X = 10; required int32 Y = 20; } message AddResponse { required int32 Result = 10; } service CalcService { rpc Add (AddRequest) returns (AddResponse); }
上述 IDL 生成的 ICalcService 接口定義為:
public interface ICalcService { AddResponse Add(AddRequest request); }
RPC 消息序列化
Redola.Rpc 選擇使用 Protobuf 2 進行消息序列化,默認集成 protobuf-net 類庫,穩定使用 protobuf-net v2.0.0.668 版本。
RPC 消息信封
使用 ActorMessageEnvelope 封裝消息信封,攜帶如下信息:
RPC 消息定義
RPC 消息分為 2 類:
- InvokeMethodRequest / InvokeMethodResponse 用於定義請求回復模型的方法調用;
- InvokeMethodMessage 用於定義請求無回復模型的方法調用;
通常 RPC 消息會包含如下屬性信息:
例如,對於 ICalcService 中的 Add 方法:
- MethodLocator = "Rodola.Rpc.TestContracts.ICalcService/Add_AddRequest";
- MethodArguments = new object[] { new AddRequest(1, 2)};
鑒於 protobuf 本身是面向契約設計的,而 object[] 中的 object 是有不確定性的,並不能具體描述一個契約,則要求每一個 Argument 都需要支持 protobuf 的序列化,傳輸時系統會攜帶該 Argument 類型的 AssemblyQualifiedName,在對端通過反射進行反序列化。
Actor Directory 節點目錄
Actor Directory 負責注冊本地 Local Actor 到注冊中心,Local Actor 也可以在 Shutdown 時將自己從注冊中心移除掉。
通過 Actor Directory,Local Actor 可以使用 Type 和 Name 進行 Remote Actor 的檢索,進而進行 Channel 的建立和通信。
Actor Directory 通過 IActorDirectory 的抽象定義,可以與不同的目錄方案進行集成。例如,自實現基於 Actor 的 CenterActorDirectory,使用 XML 配置文件的 LocalXmlFileActorDirectory,使用 Consul 進行中心注冊的 ConsulActorDirectory。
使用 Consul 時,實際上是調用了 Consul HTTP API 中的 Agent Register Service 接口 '/v1/agent/service/register',通過指定 ServiceID 和 ServiceName 進行注冊。
通過如下 cmd 啟動 Consul Server 和 Consul Agent。
consul.exe agent -config-dir "C:\Consul\config\server-01" -bootstrap -ui consul.exe agent -config-dir "C:\Consul\config\client-01" -join 192.168.1.133:7774 -ui
下面為啟動本地 Consul 進行測試的配置文件。
server-01.json
{ "datacenter": "dc1", "data_dir": "C:\\Consul\\data\\server-01", "log_level": "INFO", "node_name": "server-01", "server": true, "ports": { "http": 7771, "rpc": 7772, "dns": 7773, "serf_lan": 7774, "serf_wan": 7775, "server": 7776 } }
client-01.json
{ "datacenter": "dc1", "data_dir": "C:\\Consul\\data\\client-01", "log_level": "INFO", "node_name": "client-01", "ports": { "http": 8881, "rpc": 8882, "dns": 8883, "serf_lan": 8884, "serf_wan": 8885, "server": 8886 } }
Service Catalog Provider 服務提供者
作為 RPC Service 的 Provider 提供方,需要顯式定義指定 Contract 的服務實例。例如,下面將不同的服務契約與服務實例進行了注冊。
var serviceCatalog = new ServiceCatalogProvider(); serviceCatalog.RegisterService<IHelloService>(new HelloService()); serviceCatalog.RegisterService<ICalcService>(new CalcService()); serviceCatalog.RegisterService<IOrderService>(new OrderService());
實際上,可以通過對於 IServiceCatalogProvider 接口的不同實現,進行不同方式的本地服務發現和注冊。例如,可以使用 Attribute 標記服務,通過對 Assembly 進行反射進行服務的實例化。
Service Directory 服務目錄
本地服務聚集到 Catalog 中后,系統會將服務逐個注冊到 Service Directory 服務目錄中,使得其他節點可以檢索服務進行使用。
通過 IServiceDirectory 的抽象定義,可以與不同的目錄方案進行集成。例如,使用 XML 配置文件的 LocalXmlFileServiceDirectory,使用 Consul 進行中心注冊的 ConsulServiceDirectory。
使用 Consul 時,注冊服務的 log 如下所示。
當 Redola 將服務注冊至 Consul 中后,可通過 Consul 內置的 UI 進行查看。
http://localhost:8881/ui/#/dc1/services
Service Discovery 服務發現
通過 ConsulServiceDiscovery 實現 IServiceDiscovery 服務發現接口,從 Consul 檢索指定服務類型的服務。
通過 Postman 測試 GET /v1/catalog/services,得到如下 JSON 數據。
http://localhost:8881/v1/catalog/services
{ "Redola.Rpc.TestContracts.ICalcService": [], "Redola.Rpc.TestContracts.IHelloService": [], "Redola.Rpc.TestContracts.IOrderService": [], "consul": [], "server": [] }
通過 Postman 測試 GET /v1/catalog/service,得到如下 JSON 數據。
http://localhost:8881/v1/catalog/service/Redola.Rpc.TestContracts.ICalcService
[ { "ID": "359e8dfe-262d-6eb7-260c-e6e3ad208a14", "Node": "client-01", "Address": "192.168.1.133", "Datacenter": "dc1", "TaggedAddresses": { "lan": "192.168.1.133", "wan": "192.168.1.133" }, "NodeMeta": {}, "ServiceID": "redola/server/server-33333/Redola.Rpc.TestContracts.ICalcService", "ServiceName": "Redola.Rpc.TestContracts.ICalcService", "ServiceTags": [], "ServiceAddress": "localhost", "ServicePort": 33333, "ServiceEnableTagOverride": true, "CreateIndex": 2147, "ModifyIndex": 2151 } ]
服務檢索方,可通過指定 IServiceLoadBalancingStrategy 的具體實現實施不同的負載均衡策略,默認指定的是 IServiceLoadBalancingStrategy 隨機選擇。
Service Dynamic Proxy 動態代理
為簡化 RPC 調用發起方的封裝,通常會使用 Dynamic Proxy 動態代理技術來動態生成給定契約的服務實例,將整體 RPC 的過程透明化。
例如,通過下面的代碼來動態生成 ICalcService 的動態代理。
var calcClient = rpcNode.Resolve<ICalcService>();
目前 Redola.Rpc 默認集成了 Castle.Core 中的 Dynamic Proxy 模塊,通過對實例方法的 Intercept 攔截進行 RPC 消息的收發處理。
當然,如需集成其他 Dynamic Proxy 類庫,可通過 ISeviceProxyGenerator 接口進行方案實現。
Redola.Rpc 類庫依賴
Redola.Rpc 當前實現依賴了如下開源類庫。
<?xml version="1.0" encoding="utf-8"?> <packages> <package id="Consul" version="0.7.2.3" targetFramework="net46" /> <package id="Cowboy.Sockets" version="1.3.14.0" targetFramework="net46" /> <package id="protobuf-net" version="2.0.0.668" targetFramework="net46" /> <package id="Castle.Core" version="4.1.0" targetFramework="net46" /> <package id="Logrila.Logging" version="1.0.3.0" targetFramework="net46" /> <package id="Logrila.Logging.NLogIntegration" version="1.0.3.0" targetFramework="net46" /> <package id="NLog" version="4.2.3" targetFramework="net46" /> </packages>
版權聲明:本篇文章《Redola 集成 Consul 服務發現》由作者 Dennis Gao 發表自博客園個人技術博客,未經作者本人同意禁止以任何的形式轉載,任何自動的或人為的爬蟲轉載行為均為耍流氓。