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. 关于angular跳转路由之后不能自动回到顶部的解决方法

    Question: angular2 scroll top on router change 当我们在第一个路由滑动到底部当我们点击导航跳转到另一个路由时页面没有回到顶部而是保持上一个路由的滚动位置, ...

  2. 添砖加瓦:几种常见的数据摘要算法(MD5、CRC32、SHA1和SHA256)

    1.算法概述 数据摘要算法是密码学算法中非常重要的一个分支,它通过对所有数据提取指纹信息以实现数据签名.数据完整性校验等功能,由于其不可逆性,有时候会被用做敏感信息的加密.数据摘要算法也被称为哈希(H ...

  3. 那些被刻意“阉割”的名人名言

    "天才是百分之一的灵感,百分之九十九的汗水",这句名言大家都知道的吧!不过还有好多人不知道的是这句名言还有后半句:"但百分之一的灵感甚至比百分之九十九的汗水更重要.&qu ...

  4. java中的URLEncoder和URLDecoder类;中文在地址栏中的处理

    [IT168 技术文档] /* 网页中的表单使用POST方法提交时,数据内容的类型是 application/x-www-form-urlencoded,这种类型会: 1.字符"a" ...

  5. Python学习笔记(四)函数式编程

    高阶函数(Higher-order function) Input: 1 abs Output: 1 <function abs> Input: 1 abs(-10) Output: 1 ...

  6. 基于SR-IOV的IO虚拟化技术

    服务器配置要求 x86服务器内存不能低于32GB 服务器CPU需要支持虚拟化和设备虚拟化 VT-x VT-d,SR-IOV 功能,并且在BIOS中能启用了SR-IOV 网卡配置最起码为千兆配置 支持 ...

  7. python设置检查点简单实现

    说检查点,其实就是对过去历史的记录,可以认为是log.不过这里进行了简化.举例来说,我现在又一段文本.文本里放有一堆堆的链接地址.我现在的任务是下载那些地址中的内容.另外因为网络的问题或者网站的问题, ...

  8. Android中Intent的各种常见作用。

    Android开发之Intent.Action  1 Intent.ACTION_MAIN String: android.intent.action.MAIN 标识Activity为一个程序的开始. ...

  9. 下一个风口?迷你KTV能变成“绿巨人”吗

    近段时间,在全国各地多个商场.大学城等繁华地点,一种全新娱乐方式--迷你KTV变得火爆起来.这种仅能容纳两三人,以单首.时段等进行计费,且价格不低的点唱新模式,正成为投资者眼中的"新宠&qu ...

  10. android使用giflib加载gif

    转载请标明出处:https:////www.cnblogs.com/tangZH/p/12356915.html 背景不多说,反正ndk加载gif比java上加载gif好很多很多,主要体现在内存占用与 ...