本篇内容属于非实用性(拿来即用)介绍,如对框架设计没兴趣的朋友,请略过。

一:简单回顾一下之前的介绍
继续贴上之前的一张图片
-
服务端启动并且向注册中心发送服务信息,注册中心收到后会定时监控服务状态(常见心跳检测);
-
客户端需要开始调用服务的时候,首先去注册中心获取服务信息;
-
客户端创建远程调用连接,连接后服务端返回处理信息;
-
服务发现,向注册中心获取服务(这里需要做的有很多:拿到多个服务时需要做负载均衡,同机房过滤、版本过滤、服务路由过滤、统一网关等);
-
客户端发起调用,将需要调用的服务、方法、参数进行组装;
-
序列化编码组装的消息,这里可以使用json,也可以使用xml,也可以使用protobuf,也可以使用hessian,几种方案的序列化速度还有序列化后占用字节大小都是选择的重要指标,对内笔者建议使用高效的protobuf,它基于TCP/IP二进制进行序列化,体积小,速度快。
-
传输协议,可以使用传统的IO阻塞传输,也可以使用高效的nio传输(Netty);
-
服务端收到后进行反序列化,然后进行相应的处理;
-
服务端序列化response信息并且返回;
-
客户端收到response信息并且反序列化;
二:DotEasy.Rpc框架介绍
-
路由转发:当服务部署在多个节点上时,调用方需要知道自己的目标服务在什么地方。
-
通信协议:当管道存在,还需要在管道的两端建立处理程序(宿主),以处理管道中的数据包。DotEasy.Rpc基于DotNetty进行通信处理和协议实现。
-
动态生成:我们知道,基于二进制的RPC传输,每当新增接口,或修改接口,都需要生成相关协议的protobuf文件(或 thrift 文件),本框架基于protobuf-net的传输框架和Rosyln的预生成,动态生成相关的CS文件。
-
运行时代理:本框架采用一次请求创建一个客户端的模式,进行S端的服务请求,本框架基于Rosyln运行时创建客户端代理。
-
接口扫描:需要实现多个接口的添加和注册,本框架采用Attribute特性来扫描接口,并添加到相关的注册中心,比如consul或etcd。
2.1 解决方案介绍:
2.2 主项目介绍:
Attributes:用于标注该接口为某一特性的方法体,当系统通过Microsoft.Extensions.DependencyInjection自动构建成功后,框架将自动扫描接口上标注过[RpcTagBundle]的接口,形成目标接口列表,以供下面的方法调用。
Core:该核心分为Server和Client以及核心通用三个部分组成:
Client:主要用于通过Ip地址远程调用的方法Invoke实现,以及远程服务端的检查检查实现。
Communally:通用方法库,包括唯一ID生成器(用于标识每次请求所产生的唯一客户端)、通用对象转换器(将复杂对象转换为喜欢默认对象)、异常处理器、序列化生成器。
Server:提供服务宿主,服务执行者、服务入口处理、服务管理、服务定位、服务管理工厂的接口及默认实现。
Proxy:运行时预生成及客户端动态代理模组的接口和方法实现。
Routing:提供地址定位、服务描述、服务路由的接口和方法实现。
Transport:基于protobuf-net和dotnettey构建的二进制序列化、和通信管道和宿主的接口和实现。
所有主要类均已接口的形式提供接口名称,和已默认实现的具体方法体(虚方法),方便在这个基础上进行扩展和重写。
2.3 主项目接口
本节简单介绍一下DotEasy.Rpc主框架的接口的作用。
Attribute: |---RpcTagBundleAttribute.cs Core: |---Client: |-------Address: |-----------IAddressResolver.cs |-------IRemoteInvokeService.cs |---Server: |-------IServiceEntryFactory.cs |-------IServiceEntryLocate.cs |-------IServiceEntryManager.cs |-------IServiceEntryProvider.cs |-------IServiceExecutor.cs |-------IServiceHost.cs Proxy: |---IServiceProxyFactory.cs |---IServiceProxyGenerater.cs Routing: |---IServiceRouteFactory.cs |---IServiceRouteManager.cs Transport: |-------Codec: |-----------ITransportMessageCodecFactory.cs |-----------ITransportMessageDecoder.cs |-----------ITransportMessageEncoder.cs |---IMessageListener.cs |---IMessageSender.cs
三:如何使用
-
Apache许可证2协议开放源代码;
-
统一组件装配和构造;
-
基于protobuf-net实现字节流序列化;
-
基于Rosyln的运行时客户端代理生成;
-
基于Rosyln的预生成的客户端代理;
-
基于DotNetty的传输信道;
-
支持客户端以轮询的方式实现负载平衡;
-
Net Core结构及跨平台;
3.1 建立服务接口和服务实现
namespace doteasy.rpc.interfaces { [RpcTagBundle] public interface IUserService { Task<string> GetUserName(int id); Task<bool> Exists(int id); Task<int> GetUserId(string userName); Task<DateTime> GetUserLastSignInTime(int id); Task<IDictionary<string, string>> GetDictionary(); Task TryThrowException(); } }
namespace doteasy.rpc.implement { public class UserService : IUserService { public Task<string> GetUserName(int id) { return Task.FromResult($"我传了一个int数字{id}."); } public Task<bool> Exists(int id) { return Task.FromResult(true); } public Task<int> GetUserId(string userName) { return Task.FromResult(1); } public Task<DateTime> GetUserLastSignInTime(int id) { return Task.FromResult(DateTime.Now); } public Task<IDictionary<string, string>> GetDictionary() { return Task.FromResult<IDictionary<string, string>>(new Dictionary<string, string> { { "key", "value" } }); } public Task TryThrowException() { throw new Exception("尝试抛出异常!"); } } }
3.2 建立asp.net core mvc应用程序
namespace doteasy.rpc.webserver.Controllers { [Produces("application/json")] [Route("api/Health")] public class HealthController : Controller { [HttpGet] public IActionResult Get() => Ok("ok"); } }
using System; using doteasy.rpc.implement; using doteasy.rpc.interfaces; using DotEasy.Rpc.Entry; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace doteasy.rpc.webserver { public static class ConsulServerExtensions { public static IApplicationBuilder UseConsulServerExtensions(this IApplicationBuilder app, IConfiguration configuration) { if (app == null) throw new ArgumentNullException(nameof(app)); BaseServer baseServer = new BaseServer(configuration); //(1) baseServer.RegisterEvent += collection => collection.AddTransient<IUserService, UserService>(); //(2) baseServer.Start(); //(3) return app; } } }

{ "Hosting.urls": "http://127.0.0.1:5000", "Hosting.And.Rpc.Health.Check": "http://127.0.0.1:5000/api/health", "Rpc": { "IP": "127.0.0.1", "Port": 9881 }, "ServiceDescriptor": { "Name": "LZZ.DEV.ServerService" }, "ConsulRegister": { "IP": "127.0.0.1", "Port": 8500, "Timeout": 5 } }
3.3 测试启动Consul和Asp.net core


3.4 建立一个客户端
using System; using System.Threading.Tasks; using doteasy.rpc.interfaces; using DotEasy.Rpc.Entry; namespace DotEasy.Client { class Program : BaseClient { static void Main() { new TestClient(); } } public class TestClient : BaseClient { public TestClient() { Task.Run(async () => { var userService = Proxy<IUserService>(); Console.WriteLine($"UserService.GetUserName:{await userService.GetUserName(1)}"); Console.WriteLine($"UserService.GetUserId:{await userService.GetUserId("rabbit")}"); Console.WriteLine($"UserService.GetUserLastSignInTime:{await userService.GetUserLastSignInTime(1)}"); Console.WriteLine($"UserService.Exists:{await userService.Exists(1)}"); Console.WriteLine($"UserService.GetDictionary:{(await userService.GetDictionary())["key"]}"); }).Wait(); } } }
3.5 跑跑客户端看看结果
info: DotEasy.Rpc.Core.Communally.Convertibles.Impl.DefaultTypeConvertibleService[0] 发现了以下类型转换提供程序:DotEasy.Rpc.Core.Communally.Convertibles.Impl.DefaultTypeConvertibleProvider info: DotEasy.Rpc.Core.Communally.IdGenerator.Impl.DefaultServiceIdGenerator[0] 方法:System.Threading.Tasks.Task`1[System.String] GetUserName(Int32) 生成服务Id:doteasy.rpc.interfaces.IUserService.GetUserName_id info: DotEasy.Rpc.Core.Communally.IdGenerator.Impl.DefaultServiceIdGenerator[0] 方法:System.Threading.Tasks.Task`1[System.Boolean] Exists(Int32) 生成服务Id:doteasy.rpc.interfaces.IUserService.Exists_id info: DotEasy.Rpc.Core.Communally.IdGenerator.Impl.DefaultServiceIdGenerator[0] 方法:System.Threading.Tasks.Task`1[System.Int32] GetUserId(System.String) 生成服务Id:doteasy.rpc.interfaces.IUserService.GetUserId_userName info: DotEasy.Rpc.Core.Communally.IdGenerator.Impl.DefaultServiceIdGenerator[0] 方法:System.Threading.Tasks.Task`1[System.DateTime] GetUserLastSignInTime(Int32) 生成服务Id:doteasy.rpc.interfaces.IUserService.GetUserLastSignInTime_id info: DotEasy.Rpc.Core.Communally.IdGenerator.Impl.DefaultServiceIdGenerator[0] 方法:System.Threading.Tasks.Task`1[System.Collections.Generic.IDictionary`2[System.String,System.String]] GetDictionary() 生成服务Id:doteasy.rpc.interfaces.IUserService.GetDictionary info: DotEasy.Rpc.Core.Communally.IdGenerator.Impl.DefaultServiceIdGenerator[0] 方法:System.Threading.Tasks.Task TryThrowException() 生成服务Id:doteasy.rpc.interfaces.IUserService.TryThrowException info: DotEasy.Rpc.Core.Client.Address.Resolvers.Implementation.DefaultAddressResolver[0] 准备为服务id:doteasy.rpc.interfaces.IUserService.GetUserName_id,解析可用地址 info: DotEasy.Rpc.Consul.ConsulServiceRouteManager[0] 准备获取所有路由配置。 info: DotEasy.Rpc.Core.Client.Address.Resolvers.Implementation.DefaultAddressResolver[0] 根据服务id:doteasy.rpc.interfaces.IUserService.GetUserName_id,找到以下可用地址:127.0.0.1:9881 info: DotEasy.Rpc.Core.Client.Implementation.RemoteInvokeService[0] 使用地址:'127.0.0.1:9881'进行调用 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 准备为服务端地址:127.0.0.1:9881创建客户端。 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 准备发送消息。 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 准备获取Id为:0b720018feda4e4192937dfbb76eeb66的响应内容。 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 消息发送成功。 UserService.GetUserName:我传了一个int数字1. info: DotEasy.Rpc.Core.Client.Address.Resolvers.Implementation.DefaultAddressResolver[0] 准备为服务id:doteasy.rpc.interfaces.IUserService.GetUserId_userName,解析可用地址 info: DotEasy.Rpc.Consul.ConsulServiceRouteManager[0] 准备获取所有路由配置。 info: DotEasy.Rpc.Core.Client.Address.Resolvers.Implementation.DefaultAddressResolver[0] 根据服务id:doteasy.rpc.interfaces.IUserService.GetUserId_userName,找到以下可用地址:127.0.0.1:9881 info: DotEasy.Rpc.Core.Client.Implementation.RemoteInvokeService[0] 使用地址:'127.0.0.1:9881'进行调用 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 准备为服务端地址:127.0.0.1:9881创建客户端。 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 准备发送消息。 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 准备获取Id为:e14b7606b4d54a66af81bfe3c7df46d4的响应内容。 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 消息发送成功。 info: DotEasy.Rpc.Core.Communally.Convertibles.Impl.DefaultTypeConvertibleService[0] 准备将 System.Int64 转换为:System.Int32 UserService.GetUserId:1 info: DotEasy.Rpc.Core.Client.Address.Resolvers.Implementation.DefaultAddressResolver[0] 准备为服务id:doteasy.rpc.interfaces.IUserService.GetUserLastSignInTime_id,解析可用地址 info: DotEasy.Rpc.Consul.ConsulServiceRouteManager[0] 准备获取所有路由配置。 info: DotEasy.Rpc.Core.Client.Address.Resolvers.Implementation.DefaultAddressResolver[0] 根据服务id:doteasy.rpc.interfaces.IUserService.GetUserLastSignInTime_id,找到以下可用地址:127.0.0.1:9881 info: DotEasy.Rpc.Core.Client.Implementation.RemoteInvokeService[0] 使用地址:'127.0.0.1:9881'进行调用 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 准备为服务端地址:127.0.0.1:9881创建客户端。 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 准备发送消息。 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 准备获取Id为:d0452b16caeb48ba877da5f69a31b2f8的响应内容。 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 消息发送成功。 UserService.GetUserLastSignInTime:2018/12/11 22:31:41 info: DotEasy.Rpc.Core.Client.Address.Resolvers.Implementation.DefaultAddressResolver[0] 准备为服务id:doteasy.rpc.interfaces.IUserService.Exists_id,解析可用地址 info: DotEasy.Rpc.Consul.ConsulServiceRouteManager[0] 准备获取所有路由配置。 info: DotEasy.Rpc.Core.Client.Address.Resolvers.Implementation.DefaultAddressResolver[0] 根据服务id:doteasy.rpc.interfaces.IUserService.Exists_id,找到以下可用地址:127.0.0.1:9881 info: DotEasy.Rpc.Core.Client.Implementation.RemoteInvokeService[0] 使用地址:'127.0.0.1:9881'进行调用 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 准备为服务端地址:127.0.0.1:9881创建客户端。 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 准备发送消息。 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 准备获取Id为:4e9a218c4abd4551845008d9bc23c31f的响应内容。 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 消息发送成功。 UserService.Exists:True info: DotEasy.Rpc.Core.Client.Address.Resolvers.Implementation.DefaultAddressResolver[0] 准备为服务id:doteasy.rpc.interfaces.IUserService.GetDictionary,解析可用地址 info: DotEasy.Rpc.Consul.ConsulServiceRouteManager[0] 准备获取所有路由配置。 info: DotEasy.Rpc.Core.Client.Address.Resolvers.Implementation.DefaultAddressResolver[0] 根据服务id:doteasy.rpc.interfaces.IUserService.GetDictionary,找到以下可用地址:127.0.0.1:9881 info: DotEasy.Rpc.Core.Client.Implementation.RemoteInvokeService[0] 使用地址:'127.0.0.1:9881'进行调用 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 准备为服务端地址:127.0.0.1:9881创建客户端。 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 准备发送消息。 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 准备获取Id为:a625fd4a6bd24e6b82983272b8894562的响应内容。 info: DotEasy.Rpc.Transport.Impl.DefaultDotNettyTransportClientFactory[0] 消息发送成功。 info: DotEasy.Rpc.Core.Communally.Convertibles.Impl.DefaultTypeConvertibleService[0] 准备将 Newtonsoft.Json.Linq.JObject 转换为:System.Collections.Generic.IDictionary`2[System.String,System.String] UserService.GetDictionary:value