扩展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(".") + ).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 = ,
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 = ;
op.UseConsulDiscover("http://localhost:8500", "grpcServer");//使用consul服务发现
op.AddPolicy("Greeter.SayHello", new CRL.Core.Remoting.PollyAttribute() { RetryCount = });//定义方法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
扩展gRPC支持consul服务发现和Polly策略的更多相关文章
- 微服务(入门三):netcore ocelot api网关结合consul服务发现
简介 api网关是提供给外部调用的统一入口,类似于dns,所有的请求统一先到api网关,由api网关进行指定内网链接. ocelot是基于netcore开发的开源API网关项目,功能强大,使用方便,它 ...
- 学习搭建 Consul 服务发现与服务网格-有丰富的示例和图片
目录 第一部分:Consul 基础 1,Consul 介绍 2,安装 Consul Ubuntu/Debian 系统 Centos/RHEL 系统 检查安装 3,运行 Consul Agent 启动 ...
- Redola.Rpc 集成 Consul 服务发现
Redola.Rpc 解决了什么问题? Redola.Rpc 是一个使用 C# 开发的 RPC 框架,代码开源在 GitHub 上.目前版本仅支持 .NET Framework 4.6 以上版本,未来 ...
- 基于Docker的Consul服务发现集群搭建
在去年的.NET Core微服务系列文章中,初步学习了一下Consul服务发现,总结了两篇文章.本次基于Docker部署的方式,以一个Demo示例来搭建一个Consul的示例集群,最后给出一个HA的架 ...
- .NET Core微服务实施之Consul服务发现与治理
.NET Core微服务实施之Consul服务发现与治理 Consul官网:https://www.consul.io Consul下载地址:https://www.consul.io/downl ...
- .NetCore Cap 注册 Consul 服务发现
注册服务发现 需要使用Cap中的UseDiscovery方法 具体用法如下 var capConsulConfig = Configuration.GetSection("CapConsul ...
- ASP.NET Core gRPC 使用 Consul 服务注册发现
一. 前言 gRPC 在当前最常见的应用就是在微服务场景中,所以不可避免的会有服务注册与发现问题,我们使用gRPC实现的服务可以使用 Consul 或者 etcd 作为服务注册与发现中心,本文主要介绍 ...
- Consul 服务发现与配置
Consule 是什么 Consul包含多个组件,但是作为一个整体,为你的基础设施提供服务发现和服务配置的工具.他提供以下关键特性: 服务发现 Consul 的客户端可用提供一个服务,比如 api 或 ...
- Ocelot 网关 和 consul 服务发现
服务发现 Consul 一.安装和启动 下载 [Consul](https://www.consul.io/downloads.html) 下载完成后,解压,只有一个consul.exe,把目录添加到 ...
随机推荐
- OS Summary 1
内容概述 什么是操作系统 操作系统的演变 操作系统结果的分类 什么是操作系统 操作系统可以是: 一个控制程序 一个资源管理器 一套标准库 操作系统通常有内核.命令行和 GUI 组成.我们研究的主要是内 ...
- mac上svn: This client is too old to work with working copy 问题的解决
安装svn时,提示This client is too old to work with working copy........原因:svn的版本过旧,安装1.8的svn即可.下面简要说明一些步骤: ...
- 使用nodeJs安装Vue-cli并用它快速构建Vue项目
部分摘自:http://www.cnblogs.com/wisewrong/p/6255817.html(已在本地测试) 前提:nodeJs本地已安装. 一.安装 vue-cli 1.使用nodeJs ...
- 机器学习入门-逻辑(Logistic)回归(1)
原文地址:http://www.bugingcode.com/machine_learning/ex3.html 关于机器学习的教程确实是太多了,处于这种变革的时代,出去不说点机器学习的东西,都觉得自 ...
- SpringMVC之请求响应(上)
1.OutPutController package com.tz.controller; import java.util.Map; import org.springframework.stere ...
- B站实战第三天
B站实战第三天 用了两天多的时间才把B站页面的头部写完,今天来写头部下面的导航栏部分和轮播图一些模块. 因为还没学js,轮播图部分用swiper来实现. 今天首先复习的知识点是弹性盒模型. 弹性盒模型 ...
- js案例之使用正则表达式进行验证数据正确性
#js案例之使用正则表达式进行验证数据正确性 代码上传至 "GitHub" 样例: <tr> <td>密码:</td> <td> & ...
- Java 在PDF中添加表格
本文将介绍通过Java编程在PDF文档中添加表格的方法.添加表格时,可设置表格边框.单元格对齐方式.单元格背景色.单元格合并.插入图片.设置行高.列宽.字体.字号等. 使用工具:Free Spire. ...
- C#可空类型知多少
在项目中我们经常会遇到可为空类型,那么到底什么是可为空类型呢?下面我们将从4个方面为大家剖析. 1.可空类型基础知识 顾名思义,可空类型指的就是某个对象类型可以为空,同时也是System.Nullab ...
- RNN学习笔记(一):长短时记忆网络(LSTM)
一.前言 在图像处理领域,卷积神经网络(Convolution Nerual Network,CNN)凭借其强大的性能取得了广泛的应用.作为一种前馈网络,CNN中各输入之间是相互独立的,每层神经元的信 ...