zoukankan      html  css  js  c++  java
  • 扩展gRPC支持consul服务发现和Polly策略

    gRPC由于需要用工具生成代码实现,可开发性不是很高,在扩展这方面不是很友好

    最近研究了下,进行了扩展,不需要额外的工具生成,直接使用默认Grpc.Tools生成的代理类即可

    相关源码在文章底部

    客户端目标:

    • 能配置consul地址和服务名称,在调用client时能正确请求到真实的服务地址
    • 在调用方法时,能使用Polly策略重试,超时,和熔断

    查看gRPC生成的代码,可以看到Client实例化有有两个构造方法,以测试为例

          /// <summary>Creates a new client for Greeter</summary>
          /// <param name="channel">The channel to use to make remote calls.</param>
          public GreeterClient(grpc::ChannelBase channel) : base(channel)
          {
          }
          /// <summary>Creates a new client for Greeter that uses a custom <c>CallInvoker</c>.</summary>
          /// <param name="callInvoker">The callInvoker to use to make remote calls.</param>
          public GreeterClient(grpc::CallInvoker callInvoker) : base(callInvoker)
          {
          }

    1.可传入一个ChannelBase实例化

    2.可传入一个CallInvoker实例化

    Channel可实现为

    Channel CreateChannel(string address)
            {
                var channelOptions = new List<ChannelOption>()
                        {
                            new ChannelOption(ChannelOptions.MaxReceiveMessageLength, int.MaxValue),
                            new ChannelOption(ChannelOptions.MaxSendMessageLength, int.MaxValue),
                        };
                var channel = new Channel(address, ChannelCredentials.Insecure, channelOptions);
                return channel;
            }

    在这里,可以从consul地址按服务名获取真实的服务地址,生成Channel

    CallInvoker为一个抽象类,若要对方法执行过程干预,则需要重写这个方法,大致实现为

    public class GRpcCallInvoker : CallInvoker
        {
            public readonly Channel Channel;
            public GRpcCallInvoker(Channel channel)
            {
                Channel = GrpcPreconditions.CheckNotNull(channel); 
            }
    
            public override TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
            {
                return Calls.BlockingUnaryCall(CreateCall(method, host, options), request);
            }
    
            public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
            {
                return Calls.AsyncUnaryCall(CreateCall(method, host, options), request);
            }
    
            public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
            {
                return Calls.AsyncServerStreamingCall(CreateCall(method, host, options), request);
            }
    
            public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
            {
                return Calls.AsyncClientStreamingCall(CreateCall(method, host, options));
            }
    
            public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
            {
                return Calls.AsyncDuplexStreamingCall(CreateCall(method, host, options));
            }
    
            protected virtual CallInvocationDetails<TRequest, TResponse> CreateCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
                where TRequest : class 
                where TResponse : class
            {
                return new CallInvocationDetails<TRequest, TResponse>(Channel, method, host, options);
            }
        }

    这里可以传入上面创建的Channel,在CreateCall方法里,则可以对调用方法进行控制

    完整实现为

    public class GRpcCallInvoker : CallInvoker
        {
            GrpcClientOptions _options;
            IGrpcConnect _grpcConnect;
            public GRpcCallInvoker(IGrpcConnect grpcConnect)
            {
                _options = grpcConnect.GetOptions();
                _grpcConnect = grpcConnect;
            }
    
            public override TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
            {
                return Calls.BlockingUnaryCall(CreateCall(method, host, options), request);
            }
    
            public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
            {
                return Calls.AsyncUnaryCall(CreateCall(method, host, options), request);
            }
    
            public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
            {
                return Calls.AsyncServerStreamingCall(CreateCall(method, host, options), request);
            }
    
            public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
            {
                return Calls.AsyncClientStreamingCall(CreateCall(method, host, options));
            }
    
            public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
            {
                return Calls.AsyncDuplexStreamingCall(CreateCall(method, host, options));
            }
    
            protected virtual CallInvocationDetails<TRequest, TResponse> CreateCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
                where TRequest : class
                where TResponse : class
            {
                var methodName = $"{method.ServiceName}.{method.Name}";
                var key = methodName.Substring(methodName.IndexOf(".") + 1).ToLower();
                var a = _options.MethodPolicies.TryGetValue(key, out PollyAttribute methodPollyAttr);
                if (!a)
                {
                    _options.MethodPolicies.TryGetValue("", out methodPollyAttr);
                }
                CallOptions options2;
                //重写header
                if (options.Headers != null)
                {
                    options2 = options;
                }
                else
                {
                    options2 = new CallOptions(_grpcConnect.GetMetadata(), options.Deadline, options.CancellationToken);
                }
    
                var pollyData = PollyExtension.Invoke(methodPollyAttr, () =>
                {
                    var callRes = new CallInvocationDetails<TRequest, TResponse>(_grpcConnect.GetChannel(), method, host, options2);
                    return new PollyExtension.PollyData<CallInvocationDetails<TRequest, TResponse>>() { Data = callRes };
                }, $"{methodName}");
                var response = pollyData.Data;
                if (!string.IsNullOrEmpty(pollyData.Error))
                {
                    throw new Exception(pollyData.Error);
                }
                return response;
                //return new CallInvocationDetails<TRequest, TResponse>(Channel.Invoke(), method, host, options2);
            }
        }

    其中传入了PollyAttribute,由PollyExtension.Invoke来完成Polly策略的实现,具体代码可在源码里找到

    从上面代码可以看到,CallInvoker里可以传入了IGrpcConnect,由方法IGrpcConnect.GetChannel()获取Channel

    Client实例化

    .net FrameWork实现为

         public T GetClient<T>()
            {
                var a = instanceCache.TryGetValue(typeof(T), out object instance);
                if (!a)
                {
                    var grpcCallInvoker = new GRpcCallInvoker(this);
                    instance = System.Activator.CreateInstance(typeof(T), grpcCallInvoker);
                    instanceCache.TryAdd(typeof(T), instance);
                }
                return (T)instance;
            }

    core则简单点,直接注入实现

    var client = provider.GetService<Greeter.GreeterClient>();

    服务端注册

    和其它服务注册一样,填入正确的服务地址和名称就行了,但是在Check里得改改,gRPC的健康检查参数是不同的,并且在consul客户端里没有这个参数,得自已写

    以下代码是我封装过的,可查看源码

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
    
                app.UseRouting();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapGrpcService<GreeterService>();
                    endpoints.MapGrpcService<HealthCheckService>();
                    endpoints.MapGet("/", async context =>
                    {
                        await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
                    });
                });
    
                //注册服务
                var consulClient = new CRL.Core.ConsulClient.Consul("http://localhost:8500");
                var info = new CRL.Core.ConsulClient.ServiceRegistrationInfo
                {
                    Address = "127.0.0.1",
                    Name = "grpcServer",
                    ID = "grpcServer1",
                    Port = 50001,
                    Tags = new[] { "v1" },
                    Check = new CRL.Core.ConsulClient.CheckRegistrationInfo()
                    {
                        GRPC = "127.0.0.1:50001",
                        Interval = "10s",
                        GRPCUseTLS = false,
                        DeregisterCriticalServiceAfter = "90m"
                    }
                };
                consulClient.DeregisterService(info.ID);
                var a = consulClient.RegisterService(info);
    
            }

     客户端完整封装代码为

    core扩展方法,设置GrpcClientOptions来配置consul地址和Polly策略,直接注入了Client类型

    同时添加了统一header传递,使整个服务都能用一个头发送请求,不用再在方法后面跟参数

    public static class GrpcExtensions
        {
            public static void AddGrpcExtend(this IServiceCollection services, Action<GrpcClientOptions> setupAction, params Assembly[] assemblies)
            {
                services.Configure(setupAction);
                services.AddSingleton<IGrpcConnect, GrpcConnect>();
                services.AddScoped<CallInvoker, GRpcCallInvoker>();
                foreach (var assembyle in assemblies)
                {
                    var types = assembyle.GetTypes();
                    foreach (var type in types)
                    {
                        if(typeof(ClientBase).IsAssignableFrom(type))
                        {
                            services.AddSingleton(type);
                        }
                    }
                }
            }
        }
     class Program
        {
            static IServiceProvider provider;
            static Program()
            {
                var builder = new ConfigurationBuilder();
    
                var configuration = builder.Build();
    
                var services = new ServiceCollection();
                services.AddSingleton<IConfiguration>(configuration);
                services.AddOptions();
    
                services.AddGrpcExtend(op =>
                {
                    op.Host = "127.0.0.1";
                    op.Port = 50001;
                    op.UseConsulDiscover("http://localhost:8500", "grpcServer");//使用consul服务发现
                    op.AddPolicy("Greeter.SayHello", new CRL.Core.Remoting.PollyAttribute() { RetryCount = 3 });//定义方法polly策略
                }, System.Reflection.Assembly.GetExecutingAssembly());
    
                provider = services.BuildServiceProvider();
            }
    
    
            static void Main(string[] args)
            {            
                //设置允许不安全的HTTP2支持
                AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
    
                var grpcConnect = provider.GetService<IGrpcConnect>();
                //认证
                //https://www.cnblogs.com/stulzq/p/11897628.html
                var token = "";
                var headers = new Metadata { { "Authorization", $"Bearer {token}" } };
                grpcConnect.SetMetadata(headers);
    
    
            label1:
                var client = provider.GetService<Greeter.GreeterClient>();
                var reply = client.SayHello(
                    new HelloRequest { Name = "test" });
                Console.WriteLine("Greeter 服务返回数据: " + reply.Message);
    
                Console.ReadLine();
                goto label1;
            }
        }

    运行服务端,结果为

    可以看到服务注册成功,状态检查也成功

    运行客户端

    客户端正确调用并返回了结果

    项目源码:

    https://github.com/CRL2020/CRL.NetStandard/tree/master/Grpc

    除了gRPC实现了服务发现和Polly策略,本框架对API代理,动态API,RPC也一起实现了

    API代理测试
    https://github.com/CRL2020/CRL.NetStandard/tree/master/DynamicWebApi/ApiProxyTest

    动态API测试

    https://github.com/CRL2020/CRL.NetStandard/tree/master/DynamicWebApi/DynamicWebApiClient

    RCP测试

    https://github.com/CRL2020/CRL.NetStandard/tree/master/RPC/RPCClient

  • 相关阅读:
    Linux之网络基础
    Tomcat配置虚拟目录并发布web应用
    Linux之权限管理操作
    Linux之shell编程基础
    Python简介
    Python代码注释 Python零基础入门教程
    Python Pycharm Anacanda 区别
    Python Hello World入门
    Python2.x 和 Python3.x,如何选择?
    数据库课程设计心得【1】
  • 原文地址:https://www.cnblogs.com/hubro/p/12537409.html
Copyright © 2011-2022 走看看