1. 程式人生 > >手動造輪子——基於.NetCore的RPC框架DotNetCoreRpc

手動造輪子——基於.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](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連線](https://files.cnblogs.com/files/wucy/DotNetCoreRpcDemo.zip),建完這些之後專案結構如下: ![](https://img2020.cnblogs.com/blog/2042116/202006/2042116-20200612112429418-567271796.png) #### Client端配置 Client端引入DotNetCoreRpc.Client包,並引入自定義的契約介面層 ```
``` 然後可以愉快的編碼了,大致編碼如下 ```cs 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(); //IPersonService是我引入的服務包interface,需要提供ServiceName,即AddHttpClient的名稱 IPersonService personService = rpcClient.CreateClient("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包,並引入自定義的契約介面層 ```
``` 然後編寫契約介面實現類,比如我的叫PersonService ```cs //實現契約介面IPersonService public class PersonService:IPersonService { private readonly ConcurrentDictionary persons = new ConcurrentDictionary(); 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 GetAll() { foreach (var item in persons) { yield return item.Value; } } } ``` 通過上面的程式碼可以看出,我自定義了Filter,這裡的Filter並非Asp.Net Core框架定義的Filter,而是DotNetCoreRpc框架定義的Filter,自定義Filter的方式如下 ```cs //*要繼承自抽象類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 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,配置如下程式碼既可 ```cs public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddSingleton() .AddSingleton(new RedisConfigOptions { Address = "127.0.0.1:6379", Db = 10 }) //*註冊DotNetCoreRpcServer .AddDotNetCoreRpcServer(options => { //*確保新增的契約服務介面事先已經被註冊到DI容器中 //新增契約介面 //options.AddService(); //或新增契約介面名稱以xxx為結尾的 //options.AddService("*Service"); //或新增具體名稱為xxx的契約介面 //options.AddService("IPersonService"); //或掃描具體名稱空間下的契約介面 options.AddNameSpace("IRpcService"); //可以新增全域性過濾器,實現方式和CacheFilterAttribute一致 options.AddFilter(); }); } 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地址](https://files.cnblogs.com/files/wucy/DotNetCoreRpcDemo.zip),具體編碼可檢視Demo. ### 總結     能自己實現一套RPC框架是我近期以來的一個願望,現在可以說實現了。雖然看起來沒這麼高大上,但是整體還是符合RPC的思想。主要還是想自身實地的實踐一下,順便也希望能給大家提供一些簡單的思路。不是說我說得一定是對的,我講得可能很多是不對的,但是我說的東西都是我自身的體驗和思考,也許能給你帶來一秒鐘、半秒鐘的思考,亦或是哪怕你覺得我哪一句話說的有點道理,能引發你內心的感觸,這就是我做這件事的意義。最後,歡迎大家評論區或[本專案GitHub](https://github.com/softlgl/DotNetCoreRpc)下批評指導。