zoukankan      html  css  js  c++  java
  • 手动造轮子——基于.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包,并引入自定义的契约接口层

    Copy
    <PackageReference Include="DotNetCoreRpc.Client" Version="1.0.2" />
    

    然后可以愉快的编码了,大致编码如下

    Copy
    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包,并引入自定义的契约接口层

    Copy
    <PackageReference Include="DotNetCoreRpc.Server" Version="1.0.2" />
    

    然后编写契约接口实现类,比如我的叫PersonService

    Copy
    //实现契约接口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的方式如下

    Copy
    //*要继承自抽象类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,配置如下代码既可

    Copy
    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下批评指导。

    出处:https://www.cnblogs.com/wucy/p/13096515.html

  • 相关阅读:
    《Microsoft Sql server 2008 Internals》读书笔记第十一章DBCC Internals(2)
    《Microsoft Sql server 2008 Internals》读书笔记第十一章DBCC Internals(9)
    《Microsoft Sql server 2008 Internals》读书笔记第九章Plan Caching and Recompilation(10)
    CKEditor在asp.net环境下的使用一例
    《Microsoft Sql server 2008 Internals》读书笔记第五章Table(7)
    《Microsoft Sql server 2008 Internals》读书笔记第九章Plan Caching and Recompilation(11)
    千万数据的连续ID表,快速读取其中指定的某1000条数据?
    javascript中的float运算精度
    .Net与Java的互操作(.NET StockTrader微软官方示例应用程序)
    《Microsoft Sql server 2008 Internals》读书笔记第十一章DBCC Internals(6)
  • 原文地址:https://www.cnblogs.com/mq0036/p/13517092.html
Copyright © 2011-2022 走看看