手动造轮子——基于.NetCore的RPC框架DotNetCoreRpc
前言
一直以来对内部服务间使用RPC的方式调用都比较赞同,因为内部间没有这么多限制,最简单明了的方式就是最合适的方式。个人比较喜欢类似Dubbo的那种使用方式,采用和本地方法相同的方式,把接口层独立出来作为服务契约,为服务端提供服务,客户端也通过此契约调用服务。.Net平台上类似Dubbo这种相对比较完善的RPC框架还是比较少的,GRPC确实是一款非常优秀的RPC框架,能跨语言调用,但是每次还得编写proto文件,个人感觉还是比较麻烦的。如今服务拆分,微服务架构比较盛行的潮流下,一个简单实用的RPC框架确实可以提升很多开发效率。
简介
随着.Net Core逐渐成熟稳定,为我一直以来想实现的这个目标提供了便利的方式。于是利用闲暇时间本人手写了一套基于Asp.Net Core的RPC框架,算是实现了一个自己的小目标。大致的实现方式,Server端依赖Asp.Net Core,采用的是中间件的方式拦截处理请求比较方便。Client端可以是任何可承载.Net Core的宿主程序。通信方式是HTTP协议,使用的是HttpClientFactory。至于为什么使用HttpClientFactory,因为HttpClientFactory可以更轻松的实现服务发现,而且可以很好的集成Polly,很方便的实现,超时重试,熔断降级这些,给开发过程中提供了很多便利。由于本人能力有限,基于这些便利,站在巨人的肩膀上,简单的实现了一个RPC框架,项目托管在GitHub上https://github.com/softlgl/DotNetCoreRpc有兴趣的可以自行查阅。
开发环境
- Visual Studio 2019
- .Net Standard 2.1
- Asp.Net Core 3.1.x
使用方式
打开Visual Studio先新建一个RPC契约接口层,这里我起的名字叫IRpcService。然后新建一个Client层(可以是任何可承载.Net Core的宿主程序)叫ClientDemo,然后建立一个Server层(必须是Asp.Net Core项目)叫WebDemo,文末附本文Demo连接,建完这些之后项目结构如下:
Client端配置
Client端引入DotNetCoreRpc.Client包,并引入自定义的契约接口层
<PackageReference Include="DotNetCoreRpc.Client" Version="1.0.2" />
然后可以愉快的编码了,大致编码如下
class Program { static void Main(string[] args) { IServiceCollection services = new ServiceCollection(); //*注册DotNetCoreRpcClient核心服务 services.AddDotNetCoreRpcClient() //*通信是基于HTTP的,内部使用的HttpClientFactory,自行注册即可 .AddHttpClient("WebDemo", client => { client.BaseAddress = new Uri("http://localhost:13285/"); }); IServiceProvider serviceProvider = services.BuildServiceProvider(); //*获取RpcClient使用这个类创建具体服务代理对象 RpcClient rpcClient = serviceProvider.GetRequiredService<RpcClient>(); //IPersonService是我引入的服务包interface,需要提供ServiceName,即AddHttpClient的名称 IPersonService personService = rpcClient.CreateClient<IPersonService>("WebDemo"); PersonDto personDto = new PersonDto { Id = 1, Name = "yi念之间", Address = "中国", BirthDay = new DateTime(2000,12,12), IsMarried = true, Tel = 88888888888 }; bool addFlag = personService.Add(personDto); Console.WriteLine($"添加结果=[{addFlag}]"); var person = personService.Get(personDto.Id); Console.WriteLine($"获取person结果=[{person.ToJson()}]"); var persons = personService.GetAll(); Console.WriteLine($"获取persons结果=[{persons.ToList().ToJson()}]"); personService.Delete(person.Id); Console.WriteLine($"删除完成"); Console.ReadLine(); } }
到这里Client端的代码就编写完成了
Server端配置
Client端引入DotNetCoreRpc.Client包,并引入自定义的契约接口层
<PackageReference Include="DotNetCoreRpc.Server" Version="1.0.2" />
然后编写契约接口实现类,比如我的叫PersonService
//实现契约接口IPersonService public class PersonService:IPersonService { private readonly ConcurrentDictionary<int, PersonDto> persons = new ConcurrentDictionary<int, PersonDto>(); public bool Add(PersonDto person) { return persons.TryAdd(person.Id, person); } public void Delete(int id) { persons.Remove(id,out PersonDto person); } //自定义Filter [CacheFilter(CacheTime = 500)] public PersonDto Get(int id) { return persons.GetValueOrDefault(id); } //自定义Filter [CacheFilter(CacheTime = 300)] public IEnumerable<PersonDto> GetAll() { foreach (var item in persons) { yield return item.Value; } } }
通过上面的代码可以看出,我自定义了Filter,这里的Filter并非Asp.Net Core框架定义的Filter,而是DotNetCoreRpc框架定义的Filter,自定义Filter的方式如下
//*要继承自抽象类RpcFilterAttribute public class CacheFilterAttribute: RpcFilterAttribute { public int CacheTime { get; set; } //*支持属性注入,可以是public或者private //*这里的FromServices并非Asp.Net Core命名空间下的,而是来自DotNetCoreRpc.Core命名空间 [FromServices] private RedisConfigOptions RedisConfig { get; set; } [FromServices] public ILogger<CacheFilterAttribute> Logger { get; set; } public override async Task InvokeAsync(RpcContext context, RpcRequestDelegate next) { Logger.LogInformation($"CacheFilterAttribute Begin,CacheTime=[{CacheTime}],Class=[{context.TargetType.FullName}],Method=[{context.Method.Name}],Params=[{JsonConvert.SerializeObject(context.Parameters)}]"); await next(context); Logger.LogInformation($"CacheFilterAttribute End,ReturnValue=[{JsonConvert.SerializeObject(context.ReturnValue)}]"); } }
以上代码基本上完成了对服务端业务代码的操作,接下来我们来看如何在Asp.Net Core中配置使用DotNetCoreRpc。打开Startup,配置如下代码既可
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IPersonService, PersonService>() .AddSingleton(new RedisConfigOptions { Address = "127.0.0.1:6379", Db = 10 }) //*注册DotNetCoreRpcServer .AddDotNetCoreRpcServer(options => { //*确保添加的契约服务接口事先已经被注册到DI容器中 //添加契约接口 //options.AddService<IPersonService>(); //或添加契约接口名称以xxx为结尾的 //options.AddService("*Service"); //或添加具体名称为xxx的契约接口 //options.AddService("IPersonService"); //或扫描具体命名空间下的契约接口 options.AddNameSpace("IRpcService"); //可以添加全局过滤器,实现方式和CacheFilterAttribute一致 options.AddFilter<LoggerFilterAttribute>(); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { //这一堆可以不要+1 if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //添加DotNetCoreRpc中间件既可 app.UseDotNetCoreRpc(); //这一堆可以不要+2 app.UseRouting(); //这一堆可以不要+3 app.UseEndpoints(endpoints => { endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Server Start!"); }); }); } }
以上就是Server端简单的使用和配置,是不是感觉非常的Easy。附上可运行的Demo地址,具体编码可查看Demo.
总结
能自己实现一套RPC框架是我近期以来的一个愿望,现在可以说实现了。虽然看起来没这么高大上,但是整体还是符合RPC的思想。主要还是想自身实地的实践一下,顺便也希望能给大家提供一些简单的思路。不是说我说得一定是对的,我讲得可能很多是不对的,但是我说的东西都是我自身的体验和思考,也许能给你带来一秒钟、半秒钟的思考,亦或是哪怕你觉得我哪一句话说的有点道理,能引发你内心的感触,这就是我做这件事的意义。最后,欢迎大家评论区或本项目GitHub下批评指导。
- 基于DotNet Core的RPC框架(一) DotBPE.RPC快速开始
- 基于DotNet Core的RPC框架(一) DotBPE.RPC快速开始
- 基于DotNet Core的RPC框架(一) DotBPE.RPC快速开始
- 【基于EF Core的Code First模式的DotNetCore快速开发框架】完成对DB First代码生成的支持
- asp.net core 3.0 gRPC框架小试
- 基于ASP.Net Core开发的一套通用后台框架
- ABP CORE 框架入门视频教程《电话薄》基于 Asp.NET Core2.0 EF Core
- 造个轮子之基于 Netty 实现自己的 RPC 框架
- 基于Asp.Net Core打造轻量级内部服务治理RPC(一)
- dotnet core 出现Can not find runtime target for framework '.NETCoreApp,Version=v1.6' 的解决办法
- 基于.netcore 开发的轻量Rpc框架
- 造个轮子之基于 Netty 实现自己的 RPC 框架
- DotnetCore asp.net Core 获取客户端的IP地址以及端口号
- 基于EF Core的Code First模式的DotNetCore快速开发框架
- DotNetNuke_Core.fxcop检查代码程序集
- asp.net core 集成JWT(二)token的强制失效,基于策略模式细化api权限
- 基于.Net 2.0 (C# 2.0, ASP.NET 2.0)的快速开发框架设计NBear V3.3.9 Stable [开源]
- 基于微软ASP.NET AJAX框架开发幻灯片播放网页
- 谷歌发布的首款基于HTTP/2和protobuf的RPC框架:GRPC
- Asp.Net Core 2.0 项目实战(10) 基于cookie登录授权认证并实现前台会员、后台管理员同时登录