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. [洛谷P3366] [模板] 最小生成树

    存个模板,顺便复习一下kruskal和prim. 题目传送门 kruskal 稀疏图上表现更优. 设点数为n,边数为m. 复杂度:O(mlogm). 先对所有边按照边权排序,初始化并查集的信息. 然后 ...

  2. Django学习之路03

    django项目生命周期 路由层 路由匹配 #urls中的urlpatterns #url()方法 urlpatterns = [ url(r'^admin/', admin.site.urls), ...

  3. TabActivity中的Tab标签详细设置

    参考链接: http://www.iteye.com/topic/602737 这个写的很不错,我是跟着一步步写下来的,不过到最后也遇到了麻烦,就是不能将Tab标签的文字和图片分开,始终是重合的,而且 ...

  4. 用Express 4和 MongoDB打造Todo List

    本篇文章主要參考自DreamersLab - 用Express和MongoDB寫一個todo list.原文的教學內容是使用Express 3.x版,在這邊做簡單Express 4.12.1的todo ...

  5. 从846家初创倒下 看A轮融资后的悬崖

    看A轮融资后的悬崖" title="从846家初创倒下 看A轮融资后的悬崖"> 相比往年,今年的寒冷冬天来得更早.在互联网行业,今年的"大雪"更 ...

  6. JAVA中对list map根据map某个key值进行排序

    package test; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; ...

  7. PHP manual-mysqli-connections-翻译

    PHP manual-mysqli-connections MySQL服务器支持使用不同的传输层进行连接. 连接可以使用TCP / IP,Unix域套接字或Windows命名管道. 主机名localh ...

  8. openpyxl(python操作Excel)

    一.安装 >>> pip install openpyxl import openpyxl 二.常用操作 1.创建与保存一个工作簿 wb = openpyxl.Workbook() ...

  9. linux入门系列16--文件共享之Samba和NFS

    前一篇文章"linux入门系列15--文件传输之vsftp服务"讲解了文件传输,本篇继续讲解文件共享相关知识. 文件共享在生活和工作中非常常见,比如同一团队中不同成员需要共同维护同 ...

  10. Javascript学习笔记-基本概念-语句

    1.if语句 if (condition) statement1 else statement2 也可以像下面这样把整个if 语句写在一行代码中: if (condition1) statement1 ...