手動造輪子——基於.NetCore的RPC框架DotNetCoreRpc

前言

    一直以來對內部服務間使用RPC的方式調用都比較贊同,由於內部間沒有這麼多限制,最簡單明瞭的方式就是最合適的方式。我的比較喜歡相似Dubbo的那種使用方式,採用和本地方法相同的方式,把接口層獨立出來做爲服務契約,爲服務端提供服務,客戶端也經過此契約調用服務。.Net平臺上相似Dubbo這種相對比較完善的RPC框架仍是比較少的,GRPC確實是一款很是優秀的RPC框架,能誇語言調用,可是每次還得編寫proto文件,我的感受仍是比較麻煩的。現在服務拆分,微服務架構比較盛行的潮流下,一個簡單實用的RPC框架確實能夠提高不少開發效率。git

簡介

    隨着.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有興趣的能夠自行查閱。github

開發環境

  • 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包,並引入自定義的契約接口層app

<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端的代碼就編寫完成了async

Server端配置

Client端引入DotNetCoreRpc.Client包,並引入自定義的契約接口層ide

<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的方式以下ui

//*要繼承自抽象類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下批評指導。

相關文章
相關標籤/搜索