GRPC 是谷歌發佈的一個開源、高性能、通用RPC服務,儘管大部分 RPC 框架都使用 TCP 協議,但其實 UDP 也可以,而 gRPC 乾脆就用了 HTTP2。還有就是它具有跨平臺、跨語言 等特性,這裡就不再說明RPC是啥。 在寫項目當中,grp服務過多會非常頭疼,那麼我們分析一下如果解決這個問 ...
GRPC 是谷歌發佈的一個開源、高性能、通用RPC服務,儘管大部分 RPC 框架都使用 TCP 協議,但其實 UDP 也可以,而 gRPC 乾脆就用了 HTTP2。還有就是它具有跨平臺、跨語言 等特性,這裡就不再說明RPC是啥。
在寫項目當中,grp服務過多會非常頭疼,那麼我們分析一下如果解決這個問題。我們都知道在grpc註入到.NET Core 中使用的方法是 MapGrpcService 方法,是一個泛型方法。
[NullableAttribute(0)] [NullableContextAttribute(1)] public static class GrpcEndpointRouteBuilderExtensions { public static GrpcServiceEndpointConventionBuilder MapGrpcService<TService>(this IEndpointRouteBuilder builder) where TService : class; }
那我們就可以通過反射調用這個方法來進行服務批量註冊,看方法的樣子我們只需要將我們的服務對應 TService 以及將我們的 endpointBuilder 傳入即可,我們看下源碼是不是就像我所說的那樣?
public static class GrpcEndpointRouteBuilderExtensions { public static GrpcServiceEndpointConventionBuilder MapGrpcService<TService>(this IEndpointRouteBuilder builder) where TService : class { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } ValidateServicesRegistered(builder.ServiceProvider); var serviceRouteBuilder = builder.ServiceProvider.GetRequiredService<ServiceRouteBuilder<TService>>(); var endpointConventionBuilders = serviceRouteBuilder.Build(builder); return new GrpcServiceEndpointConventionBuilder(endpointConventionBuilders); } private static void ValidateServicesRegistered(IServiceProvider serviceProvider) { var marker = serviceProvider.GetService(typeof(GrpcMarkerService)); if (marker == null) { throw new InvalidOperationException("Unable to find the required services. Please add all the required services by calling " + "'IServiceCollection.AddGrpc' inside the call to 'ConfigureServices(...)' in the application startup code."); } } }
ok,看樣子沒什麼問題就像我剛纔所說的那樣做。現在我們準備一個proto以及一個Service.這個就在網上找個吧..首先定義一個proto,它是grpc中的協議,也就是每個消費者遵循的。
syntax = "proto3"; option csharp_namespace = "Grpc.Server"; package Greet; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); } message HelloRequest { string name = 1; enum Laguage{ en_us =0 ; zh_cn =1 ; } Laguage LaguageEnum = 2; } message HelloReply { string message = 1; int32 num = 2; int32 adsa =3; }
隨後定義Service,當然非常簡單, Greeter.GreeterBase 是重新生成項目根據proto來生成的。
public class GreeterService : Greeter.GreeterBase { public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context) { var greeting = string.Empty; switch (request.LaguageEnum) { case HelloRequest.Types.Laguage.EnUs: greeting = "Hello"; break; case HelloRequest.Types.Laguage.ZhCn: greeting = "你好"; break; } return Task.FromResult(new HelloReply { Message = $"{greeting} {request.Name}", Num = new Random().Next() }); } }
此時我們需要自定義一個中間件,來批量註入grpc服務,其中我們獲取了類型為 GrpcEndpointRouteBuilderExtensions ,並獲取了它的方法,隨後傳入了他的TService,最後通過Invoke轉入了我們的終點對象。
public static class GrpcServiceExtension { public static void Add_Grpc_Services(IEndpointRouteBuilder builder) { Assembly assembly = Assembly.GetExecutingAssembly(); foreach (var item in ServicesHelper.GetGrpcServices("Grpc.Server")) { Type mytype = assembly.GetType(item.Value + "."+item.Key); var method = typeof(GrpcEndpointRouteBuilderExtensions).GetMethod("MapGrpcService").MakeGenericMethod(mytype); method.Invoke(null, new[] { builder }); }; } public static void useMyGrpcServices(this IApplicationBuilder app) { app.UseEndpoints(endpoints => { Add_Grpc_Services(endpoints); }); } }
在 ServicesHelper 中通過反射找到程式集當中的所有文件然後判斷並返回。
public static class ServicesHelper { public static Dictionary<string,string> GetGrpcServices(string assemblyName) { if (!string.IsNullOrEmpty(assemblyName)) { Assembly assembly = Assembly.Load(assemblyName); List<Type> ts = assembly.GetTypes().ToList(); var result = new Dictionary<string, string>(); foreach (var item in ts.Where(u=>u.Namespace == "Grpc.Server.Services")) { result.Add(item.Name,item.Namespace); } return result; } return new Dictionary<string, string>(); } }
這樣子我們就註入了所有命名空間為Grpc.Server.Services的服務,但這樣好像無法達到某些控制,我們應當如何處理呢,我建議攜程Attribute的形式,創建一個Flag.
public class GrpcServiceAttribute : Attribute { public bool IsStart { get; set; } }
將要在註入的服務商添加該標識,例如這樣。
[GrpcService] public class GreeterService : Greeter.GreeterBase {...}
隨後根據反射出來的值找到 AttributeType 的名稱進行判斷即可。
public static Dictionary<string,string> GetGrpcServices(string assemblyName) { if (!string.IsNullOrEmpty(assemblyName)) { Assembly assembly = Assembly.Load(assemblyName); List<Type> ts = assembly.GetTypes().ToList(); var result = new Dictionary<string, string>(); foreach (var item in ts.Where(u=>u.CustomAttributes.Any(a=>a.AttributeType.Name == "GrpcServiceAttribute"))) { result.Add(item.Name,item.Namespace); } return result; } return new Dictionary<string, string>(); }
隨後我們的批量註入在Starup.cs中添加一行代碼即可。
app.useMyGrpcServices();
啟動項目試一試效果:
示例代碼:傳送門