NET Core微服务之路:自己动手实现Rpc服务框架,基于DotEasy.Rpc服务框架的介绍和集成...
本篇内容属于非实用性(拿来即用)介绍,如对框架设计没兴趣的朋友,请略过。
一:简单回顾一下之前的介绍
继续贴上之前的一张图片
- 服务端启动并且向注册中心发送服务信息,注册中心收到后会定时监控服务状态(常见心跳检测);
- 客户端需要开始调用服务的时候,首先去注册中心获取服务信息;
- 客户端创建远程调用连接,连接后服务端返回处理信息;
- 服务发现,向注册中心获取服务(这里需要做的有很多:拿到多个服务时需要做负载均衡,同机房过滤、版本过滤、服务路由过滤、统一网关等);
- 客户端发起调用,将需要调用的服务、方法、参数进行组装;
- 序列化编码组装的消息,这里可以使用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
四:总结
NET Core微服务之路:自己动手实现Rpc服务框架,基于DotEasy.Rpc服务框架的介绍和集成...相关推荐
- .NET Core微服务之路:不断更新中的目录 (v0.42)
原文:.NET Core微服务之路:不断更新中的目录 (v0.42) 微服务架构,对于从事JAVA架构的童鞋来说,早已不是什么新鲜的事儿,他们有鼎鼎大名的Spring Cloud这样的全家桶框架支撑, ...
- .NET Core微服务之路:文章系列和内容索引汇总 (v0.52)
原文:.NET Core微服务之路:文章系列和内容索引汇总 (v0.52) 微服务架构,对于从事JAVA架构的童鞋来说,早已不是什么新鲜的事儿,他们有鼎鼎大名的Spring Cloud这样的全家桶框架 ...
- .NET Core微服务之路:文章系列和内容索引汇总 (v0.53)
微服务架构,对于从事JAVA架构的童鞋来说,早已不是什么新鲜的事儿,他们有鼎鼎大名的Spring Cloud这样的全家桶框架支撑,包含微服务核心组件如 1. Eureka:实现服务注册与发现. 2. ...
- .NET Core微服务之路:基于Ocelot的API网关实现--http/https协议篇
前言 最近一直在忙公司和私下的兼职,白天十个小时,晚上四个小时,感觉每天都是打了鸡血似的,精神满满的,连自己那已经学打酱油的娃都很少关心,也有很长一段时间没有更新博客了,特别抱歉,小伙伴们都等得想取关 ...
- NET Core微服务之路:基于Ocelot的API网关实现--http/https协议篇
前言 最近一直在忙公司和私下的兼职,白天十个小时,晚上四个小时,感觉每天都是打了鸡血似的,精神满满的,连自己那已经学打酱油的娃都很少关心,也有很长一段时间没有更新博客了,特别抱歉,小伙伴们都等得想取关 ...
- NET Core微服务之路:基于Ocelot的API网关Relay实现--RPC篇
前言 我们都知道,API网关是工作在应用层上网关程序,为何要这样设计呢,而不是将网关程序直接工作在传输层.或者网络层等等更底层的环境呢?让我们先来简单的了解一下TCP/IP的五层模型. (图片出自ht ...
- PaaS服务之路漫谈(二):Monolithic架构分析
网易杭州研究院·尧飘海 本文作于2015年2月 天下大势,分久必合,合久必分,社会历史的发展方向总有着惊人的相似. 把这种规律应用到软件应用架构的发展方向上,当生产力和生产关系到了不可调和的矛盾时,也 ...
- MATLAB代码:基于储能电站服务的冷热电多微网系统双层优化配置
MATLAB代码:基于储能电站服务的冷热电多微网系统双层优化配置 关键词:储能电站 共享储能电站 冷热电多微网 双层优化配置 参考文档:<基于储能电站服务的冷热电多微网系统双层优化配置>完 ...
- .NET Core微服务之路:基于Consul最少集群实现服务的注册与发现(一)
原文:.NET Core微服务之路:基于Consul最少集群实现服务的注册与发现(一) Consul介绍 Consul是HashiCorp公司推出的开源工具[开源地址:https://github.c ...
最新文章
- Coinbase内部调查未发现比特币现金内幕交易证据
- Oracle日期函数集锦
- java.lang.ClassNotFoundException: com.sap.exception.GlobalDefaultExceptionHandler
- 无连接可靠传输_FPC连接器的特点以及弹片微针模组的作用
- 收到手机第二天就自燃?S10机主怒告三星 要求道歉并索赔1元
- Vue3(setup函数介绍)
- [工具类]DataTable与泛型集合List互转
- 简单好用一键恢复丢失办公文档
- UltraEdit配置代码格式化工具astyle
- Docker 安装 linux版
- 查看计算机屏幕颜色软件是什么,显示器颜色校正软件,详细教您显示器颜色校正软件...
- 农村配电设施存在的安全隐患及应采取的措施130637
- 【电脑讲解】无线鼠标没反应的两种常见原因,其实很好解决
- 操盘手“本来生活”,这样把“褚橙”卖成“励志橙”
- IDEA运行卡顿,网页刷新不及时,网页报404错误以及Ajax收不到servlet返回的数据(已解决)
- 惨遭腾讯面试官吊打高并发系统设计,回来学习2400小时后成功复仇!
- 2018百度云ABC智能物联大会发布世界顶级智能边缘产品BIE
- Java窗体实现飞机躲子弹游戏
- Ubuntu 快速显示桌面快捷键
- 嵌入式Linux驱动开发基础