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策略的更多相关文章

  1. 微服务(入门三):netcore ocelot api网关结合consul服务发现

    简介 api网关是提供给外部调用的统一入口,类似于dns,所有的请求统一先到api网关,由api网关进行指定内网链接. ocelot是基于netcore开发的开源API网关项目,功能强大,使用方便,它 ...

  2. 学习搭建 Consul 服务发现与服务网格-有丰富的示例和图片

    目录 第一部分:Consul 基础 1,Consul 介绍 2,安装 Consul Ubuntu/Debian 系统 Centos/RHEL 系统 检查安装 3,运行 Consul Agent 启动 ...

  3. Redola.Rpc 集成 Consul 服务发现

    Redola.Rpc 解决了什么问题? Redola.Rpc 是一个使用 C# 开发的 RPC 框架,代码开源在 GitHub 上.目前版本仅支持 .NET Framework 4.6 以上版本,未来 ...

  4. 基于Docker的Consul服务发现集群搭建

    在去年的.NET Core微服务系列文章中,初步学习了一下Consul服务发现,总结了两篇文章.本次基于Docker部署的方式,以一个Demo示例来搭建一个Consul的示例集群,最后给出一个HA的架 ...

  5. .NET Core微服务实施之Consul服务发现与治理

    .NET Core微服务实施之Consul服务发现与治理   Consul官网:https://www.consul.io Consul下载地址:https://www.consul.io/downl ...

  6. .NetCore Cap 注册 Consul 服务发现

    注册服务发现 需要使用Cap中的UseDiscovery方法 具体用法如下 var capConsulConfig = Configuration.GetSection("CapConsul ...

  7. ASP.NET Core gRPC 使用 Consul 服务注册发现

    一. 前言 gRPC 在当前最常见的应用就是在微服务场景中,所以不可避免的会有服务注册与发现问题,我们使用gRPC实现的服务可以使用 Consul 或者 etcd 作为服务注册与发现中心,本文主要介绍 ...

  8. Consul 服务发现与配置

    Consule 是什么 Consul包含多个组件,但是作为一个整体,为你的基础设施提供服务发现和服务配置的工具.他提供以下关键特性: 服务发现 Consul 的客户端可用提供一个服务,比如 api 或 ...

  9. Ocelot 网关 和 consul 服务发现

    服务发现 Consul 一.安装和启动 下载 [Consul](https://www.consul.io/downloads.html) 下载完成后,解压,只有一个consul.exe,把目录添加到 ...

随机推荐

  1. 【转载】python3安装scrapy之windows32位爬坑

    python3安装scrapy之windows32位爬坑 原创 2016年11月06日 01:38:08 标签: scrapy / windows / python / 开源框架 / 网络爬虫   早 ...

  2. curator配置及使用

    1.action.yml --- actions: 1: action: index_settings options: index_settings: index: routing.allocati ...

  3. angular知识点(2)

    angular知识点(2) 1.为了代码规范,对于需要自动加载的依赖,需要在前面加上注释,注释为://@ngInject 或者是/*@ngInject*/ 2.ngSwitch的应用 在需要用到选择出 ...

  4. 正则表达式之RegExp对象

    1.定义 RegExp对象是原生JavaScript中表示正则表达式的对象:是正则表达式(regular expression)的简写.有一点需要明确知道,正则表达式只能对字符串进行操作. 2.语法 ...

  5. 从2019-nCoV趋势预测问题,联想到关于网络安全态势预测问题的讨论

    0. 引言 在这篇文章中,笔者希望和大家讨论一个话题,即未来趋势是否可以被精确或概率性地预测. 对笔者所在的网络安全领域来说,由于网络攻击和网络入侵常常变现出随机性.非线性性的特征,因此纯粹的未来预测 ...

  6. Manjaro 19.01 kde下Tim sogou软件安装问题及解决

    我的系统配置 首先第一个问题是,在manjaro下Tim Thunderspeed这种deepin-wine的软件.今天我在装这些软件的时候,安装之后不能打开,闪退.苦恼了我好一会儿.终于找到了解决的 ...

  7. 基于netty的群聊

    基于netty的群聊 学了一段时间的netty知识,现在通过这个基于console的程序来对netty的相关接口做个简单的应用. 准备 依赖 <dependency> <groupI ...

  8. MVC04

    1. 从页面到action 讲述controller与View之间的具体运作关系 在上次添加的名为Movie的Model内添加 下面我们尝试为该model内的属性添加attribute 具体修改如下: ...

  9. Canvas 使用及应用

    Canvas canvas 是 HTML5 当中我最喜欢的所有新特性中我最喜欢的一个标签了.因为它太强大了,各种有意思的特效都可以实现. 1. canvas 的基本使用方法 - 它是一个行内块元素 - ...

  10. 前端javascript知识(三)

    函数记忆,判断是不是质数. 方法一: function isPrime1(n){ if(n<=3){return true} else{ for(var i=2;i<Math.sqrt(n ...