扩展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,把目录添加到 ...
随机推荐
- 吴裕雄--天生自然HTML学习笔记:HTML 脚本
JavaScript 使 HTML 页面具有更强的动态和交互性. <!DOCTYPE html> <html> <head> <meta charset=&q ...
- js 实现排序算法 -- 冒泡排序(Bubble Sort)
原文: 十大经典排序算法(动图演示) 冒泡排序(Bubble Sort) 冒泡排序是一种简单的排序算法.它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来.走访数列的工作 ...
- struts2和springmvc比较1
其实都是ssh只是一个是struts2+spring3.0+hibernate,而另外一个是 springmvc+spring3.0+hibernate,纵向来看比较struts2和springmvc ...
- 为什么就连iPhone、三星手机的电池都能出问题?
近年来关于三星.苹果.华为等知名手机厂商电池爆炸的消息一直不断在媒体上报道.这在一定程度上引发了消费者的重度忧虑,也给这些知名手机厂商从一定程度上造成了信任危机.为何连这些知名品牌都无法避免手机电池的 ...
- WWW 2015:一个神奇的会议
2015:一个神奇的会议" title="WWW 2015:一个神奇的会议"> 作者:微软亚洲研究院研究员 袁进辉 WWW 2015(24th Internatio ...
- Design Patterns 25
尽管将一个系统分割成许多对象通常可以增加其可服用性, 但是对象间相互连接的激增又会降低其可复用性了. 大量的连接使得一个对象不可能在没有改变其他对象的支持下工作, 系统表现为一个不可分割的整体, 所以 ...
- SIP 协议详解
SIP 协议详解 2013年参与过一个"视频通讯的App"项目,使用Sip协议通信.当时通信协议这块不是自己负责,加上时间紧.任务重等方面的原因,一直未对Sip协议进行过深入的了解 ...
- echarts实现饼图及横向柱状图的绘制
项目中需要绘制饼图,因此简单学习了下echarts的基本使用.head中引入js文件: <script src="/static/frame/echarts/echarts.min.j ...
- step(iter)、epoch、batch size之间的关系
转自:https://blog.csdn.net/wcy23580/article/details/90082221
- 数据挖掘入门系列教程(三)之scikit-learn框架基本使用(以K近邻算法为例)
数据挖掘入门系列教程(三)之scikit-learn框架基本使用(以K近邻算法为例) 简介 scikit-learn 估计器 加载数据集 进行fit训练 设置参数 预处理 流水线 结尾 数据挖掘入门系 ...