惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Security @ Cisco Blogs
P
Privacy & Cybersecurity Law Blog
P
Privacy International News Feed
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Threatpost
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Project Zero
Project Zero
C
CERT Recently Published Vulnerability Notes
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
C
Cisco Blogs
博客园 - 司徒正美
L
LINUX DO - 热门话题
D
Docker
M
MIT News - Artificial intelligence
GbyAI
GbyAI
Microsoft Azure Blog
Microsoft Azure Blog
Microsoft Security Blog
Microsoft Security Blog
Cisco Talos Blog
Cisco Talos Blog
Jina AI
Jina AI
Last Week in AI
Last Week in AI
Security Latest
Security Latest
The Hacker News
The Hacker News
L
Lohrmann on Cybersecurity
Y
Y Combinator Blog
A
Arctic Wolf
小众软件
小众软件
T
Threat Research - Cisco Blogs
Know Your Adversary
Know Your Adversary
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
I
InfoQ
腾讯CDC
Google DeepMind News
Google DeepMind News
B
Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
C
CXSECURITY Database RSS Feed - CXSecurity.com
Simon Willison's Weblog
Simon Willison's Weblog
博客园 - 叶小钗
I
Intezer
NISL@THU
NISL@THU
A
About on SuperTechFans
爱范儿
爱范儿
C
Cybersecurity and Infrastructure Security Agency CISA
D
Darknet – Hacking Tools, Hacker News & Cyber Security
G
Google Developers Blog
J
Java Code Geeks
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Cyberwarzone
Cyberwarzone
AWS News Blog
AWS News Blog
Engineering at Meta
Engineering at Meta
Latest news
Latest news

元视角

.NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 基于 SVG 的图形交互方案实践 - 元视角 前端视频播放技术概览 - 元视角 温故而知新,再话 Python 动态导入 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 gRPC 借助 Any 类型实现接口的泛化调用 - 元视角 分布式丛林探险系列之 Redis 集群模式 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角 源代码探案系列之 .NET Core 跨域中间件 CORS - 元视角 源代码探案系列之 .NET Core 限流中间件 AspNetCoreRateLimit - 元视角 源代码探案系列之 .NET Core 并发限制中间件 ConcurrencyLimiter - 元视角 通过 EmbededFileProvider 实现 Blazor 的静态文件访问 - 元视角 低代码,想说爱你不容易 - 元视角 记一次失败的 ThoughtWorks 面试经历 - 元视角 从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览 - 元视角 通过 Python 分析 2020 年全年微博热搜数据 - 元视角 基于 Python 和 Selenium 实现 CSDN 一键三连自动化 - 元视角 使用多线程为你的 Python 爬虫提速的 N 种姿势,你会几种? - 元视角 实现网页长截图的常见思路总结 - 元视角 温故而知新,由 ADO.NET 与 Dapper 所联想到的 - 元视角 视频是不能 P 的系列:OpenCV 人脸检测 - 元视角 作为技术宅的我,是这样追鬼滅の刃的 - 元视角 使用 Python 抽取《半泽直树》原著小说人物关系 - 元视角 厉害了!打工人用 Python 分析西安市职位信息 - 元视角 使用 dotTrace 对 .NET 应用进行性能分析与优化 - 元视角 一道 HashSet 面试题引发的蝴蝶效应 - 元视角 基于选项模式实现.NET Core 的配置热更新 - 元视角 Dapper.Contrib 在 Oracle 环境下引发 ORA-00928 异常问题的解决 - 元视角 .NET Core 中对象池(Object Pool)的使用 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(下):EventBus 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(中):RabbitMQ 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(上):基础篇 - 元视角 记一次从已损坏的 Git 仓库中找回代码的经历 - 元视角 .NET Core 原生 DI 扩展之属性注入实现 - 元视角 .NET Core 原生 DI 扩展之基于名称的注入实现 - 元视角
ASP.NET Core gRPC 健康检查的探索与实现 - 元视角
飞鸿踏雪 · 2021-06-01 · via 元视角

各位朋友,大家好,欢迎大家关注我的博客。在上一篇 博客 中,博主和大家分享了gRPC的拦截器在日志记录方面的简单应用,今天我们继续来探索gRPC在构建微服务架构方面的可能性。其实,从博主个人的理解而言,不管我们的微服务架构是采用RPC方式还是采用RESTful方式,我们最终要面对的问题本质上都是一样的,博主这里将其归纳为:服务划分、服务编写 和 服务治理。首先,服务划分决定了每一个服务的上下文边界以及服务颗粒度大小,如果按照领域驱动设计(DDD)的思想来描述微服务,我认为它更接近于限界上下文(BoundedContext)的概念。其次,服务编写决定了每一个服务的具体实现方式,譬如是采用无状态的RESTful风格的API,还是采用强类型的、基于代理的RPC风格的API。最后,服务治理是微服务架构中永远避不开的话题,服务注册、服务发现、健康检查、日志监控等等一切的话题,其实都是在围绕着服务治理而展开,尤其是当我们编写了一个又一个的服务以后,此时该如何管理这些浩如“”海的服务呢?所以,在今天这篇博客中,博主想和大家一起探索下gRPC的健康检查,希望能给大家带来一点启发。

健康检查-服务注册-服务发现示意图 健康检查-服务注册-服务发现示意图

关于“健康检查”,大家都知道的一点是,它起到一种“防微杜渐”的作用。不知道大家还记不记得,语文课本里的经典故事《扁鹊见蔡桓公》,扁鹊一直在告知蔡桓公其病情如何,而蔡桓公讳疾忌医,直至病入骨髓、不治而亡。其实,对应到我们的领域知识,后端依赖的各种服务譬如数据库、消息队列、Redis、API 等等,都需要这样一个“扁鹊”来实时地“望闻问切”,当发现问题的时候及时地采取相应措施,不要像“蔡桓公”一样病入骨髓,等到整个系统都瘫痪了,这时候火急火燎地去“救火”,难免会和蔡桓公一样,发出“悔之晚矣”的喟叹。当我们决定使用gRPC来构建微服务架构的时候,我们如何确保这些服务一直是可用的呢?所以,提供一种针对gRPC服务的健康检查方案就会显得非常迫切。这里,博主主要为大家介绍两种实现方式,它们分别是:基于IHostedService的实现方式 以及 基于Consul的实现方式。

基于 IHostedService 的实现方式

第一种方式,主要是利用IHostedService可以在程序后台执行的特点,搭配Timer就可以实现定时轮询。在 gRPC官方规范 中,提供了一份Protocol Buffers的声明文件,它规定了一个健康检查服务必须实现Check()Watch()两个方法。既然是官方定义好的规范,建议大家不要修改这份声明文件,我们直接沿用即可:

syntax = "proto3";

package grpc.health.v1;

message HealthCheckRequest {
  string service = 1;
}

message HealthCheckResponse {
  enum ServingStatus {
    UNKNOWN = 0;
    SERVING = 1;
    NOT_SERVING = 2;
  }
  ServingStatus status = 1;
}

service Health {
  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
  rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}

接下来,我们需要实现对应的HealthCheckService:

public class HealthCheckService : Health.HealthBase
{
    public override Task<HealthCheckResponse> Check(
      HealthCheckRequest request, 
      ServerCallContext context
    )
    {
        // TODO: 在这里添加更多的细节
        return Task.FromResult(new HealthCheckResponse() { 
            Status = HealthCheckResponse.Types.ServingStatus.Serving 
        });
    }

    public override async Task Watch(
      HealthCheckRequest request, 
      IServerStreamWriter<HealthCheckResponse> responseStream, 
      ServerCallContext context
    )
    {
        // TODO: 在这里添加更多的细节
        await responseStream.WriteAsync(new HealthCheckResponse(){
            Status = HealthCheckResponse.Types.ServingStatus.Serving 
        });
    }
}

接下来,我们需要实现HostedHealthCheckService,它实现了IHostedService接口,并在其中调用HealthCheckService:

public class HostedHealthCheckService : IHostedService
{
    private Timer _timer = null;
    private readonly ILogger<HostedHealthCheckService> _logger;

    public HostedHealthCheckService(ILogger<HostedHealthCheckService> logger)
    {
        _logger = logger;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation($"{nameof(HostedHealthCheckService)} start running....");
        _timer = new Timer(DoCheck, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation($"{nameof(HostedHealthCheckService)} stop running....");
        _timer?.Change(Timeout.Infinite, 0);
        return Task.CompletedTask;
    }

    private void DoCheck(object state)
    {
        using var channel = GrpcChannel.ForAddress("https://localhost:5001"); ;
        var client = new Health.HealthClient(channel);
        client.Check(new HealthCheckRequest() { Service = "https://localhost:5001" });
    }
}

接下来,是大家非常熟悉的依赖注入环节:

// ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc(options => options.Interceptors.Add<GrpcServerLoggingInterceptor>());
    services.AddHostedService<HostedHealthCheckService>();
}

// Configure
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGrpcService<HealthCheckService>();
    });
}

如果大家对上一篇博客中的拦截器还有印象,对于下面的结果应该会感到非常亲切:

基于 IHostedService 的 gRPC 健康检查 基于 IHostedService 的 gRPC 健康检查

除此以外,我们还可以直接安装第三方库:Grpc.HealthCheck。此时,我们需要继承HealthServiceImpl类并重写其中的Check()Watch()方法:

public class HealthCheckService : HealthServiceImpl
{
    public override Task<HealthCheckResponse> Check(
      HealthCheckRequest request, 
      ServerCallContext context
    )
    {
        // TODO: 在这里添加更多的细节
        return Task.FromResult(new HealthCheckResponse()
        {
            Status = HealthCheckResponse.Types.ServingStatus.Serving
        });
    }

    public override async Task Watch(
      HealthCheckRequest request, 
      IServerStreamWriter<HealthCheckResponse> responseStream, 
      ServerCallContext context
    )
    {
        // TODO: 在这里添加更多的细节
        await responseStream.WriteAsync(new HealthCheckResponse()
        {
            Status = HealthCheckResponse.Types.ServingStatus.Serving
        });
    }
}

接下来,我们只需要在HostedHealthCheckService调用它即可,这个非常简单。

故,无需博主多言,相信屏幕前的你都能写得出来,如果写不出来,参考博主给出得实现即可(逃!

基于 Consul 的实现方式

Consul 是一个由 HashiCorp 提供的产品,它提供了服务注册、服务发现、健康检查、键值存储等等的特性。这里,我们通过集成它的SDK来实现gRPC服务的服务注册、服务发现、健康检查,从某种程度上来讲,它无形中帮助我们实现了客户端的负载均衡,因为我们可以将每一个服务的终结点都注册到Consul中,而Consul的健康检查则可以定时移除那些不可用的服务。所以,客户端获得的终结点实际上都是可用的终结点。

首先,我们需要安装第三方库:Consul。接下来,我们可需要通过Docker安装一下Consul:

docker pull consul
docker run --name consul -d -p 8500:8500 consul

默认情况下,Consul的端口号为:8500,我们可以直接访问:http://localhost:8500

Consul 界面效果展示 Consul 界面效果展示

接下来,为了让Startup类看起来清爽一点,首先,我们先来写一点扩展方法:

// 为指定的gRPC服务添加健康检查
public static void AddGrpcHealthCheck<TService>(this IServiceCollection services)
{
    var configuration = services.BuildServiceProvider().GetService<IConfiguration>();

    // 注册ConsulClient
    services.AddSingleton<IConsulClient, ConsulClient>(_ => new ConsulClient(consulConfig =>
    {
        var baseUrl = configuration.GetValue<string>("Consul:BaseUrl");
        consulConfig.Address = new Uri(baseUrl);
    }));

    // 注册gRPC服务
    RegisterConsul<TService>(services).Wait();
}

其中,RegisterConsul()方法负责告诉Consul,某个服务对应的 IP 和端口号分别是多少,采用什么样的方式进行健康检查。

不过,由于Consul默认不支持gRPC的健康检查,所以,我们使用了更为常见的基于TCP方式的健康检查。你可以认为,只要服务器连接畅通,gRPC服务就是健康的。

// 注册指定服务到Consul
private static async Task RegisterConsul<TService>(IServiceCollection services)
{
    var serverHost = GetLocalIP();
    var serverPort = services.BuildServiceProvider().GetService<IConfiguration>().GetValue<int>("gRPC:Port");
    await RegisterConsul<TService>(services, serverHost, serverPort);
}

// 注册指定服务到Consul
private static async Task RegisterConsul<TService>(
  IServiceCollection services, 
  string serverHost, 
  int serverPort
)
{
    var client = services.BuildServiceProvider().GetService<IConsulClient>();
    var registerID = $"{typeof(TService).Name}-{serverHost}:{serverPort}";
    await client.Agent.ServiceDeregister(registerID);
    var result = await client.Agent.ServiceRegister(new AgentServiceRegistration()
    {
        ID = registerID,
        Name = typeof(TService).Name,
        Address = serverHost,
        Port = serverPort,
        Check = new AgentServiceCheck
        {
            TCP = $"{serverHost}:{serverPort}",
            Status = HealthStatus.Passing,
            DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(10),
            Interval = TimeSpan.FromSeconds(10),
            Timeout = TimeSpan.FromSeconds(5)
        },
        Tags = new string[] { "gRpc" }
    }) ;
}

对于Consul中的健康检查,更常用的是基于HTTP的健康检查,简单来说,就是我们提供一个接口,供Consul来调用,我们可以去设置请求的头(Header)、消息体(Body)、方法(Method)等等。所以,对于这里的实现,你还可以替换为更一般的实现,即提供一个 API 接口,然后在这个接口中调用gRPC的客户端。除此以外,如果你擅长写脚本,Consul同样支持脚本级别的健康检查。

在这里,博主水平扩展(复制)了两套服务,它们分别被部署在50016001两个端口上,通过Consul能达到什么效果呢?我们一起来看一下:

// ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc(options => options.Interceptors.Add<GrpcServerLoggingInterceptor>());
    services.AddGrpcHealthCheck<GreeterService>();
    services.AddGrpcHealthCheck<CalculatorService>();
}

// Configure
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGrpcService<GreeterService>();
        endpoints.MapGrpcService<CalculatorService>();
    });
}

OK,此时,我们注意到Consul中有两个服务注册进去,它们分别是:GreeterServiceCalculatorService

gRPC 服务成功注册到 Consul 中 gRPC 服务成功注册到 Consul 中

以其中一个CalculatorService为例,我们可以注意到,它的确注册了50016001两个实例:

CalculatorService 的两个实例 CalculatorService 的两个实例

至此,我们就完成了基于Consul的健康检查,在这里,图中的绿色标记表示服务可用。

关于 gRPC 的引申话题

其实,写到这里的时候,这篇博客就该接近尾声啦,因为对于 gRPC 健康检查的探索基本都已找到答案,可我还是想聊一聊关于 gRPC 的引申话题。理由特别简单,就是在我看来,接下来要讲的这点内容,完全撑不起一篇博客的篇幅,索性就在这篇博客里顺带一提。我打算分享两个话题,其一,是 gRPC 客户端的负载均衡;其二,是 gRPC 接口的测试工具。

gRPC 客户端的负载均衡

截止到目前为止,结合Consul我们已经实现了服务注册和服务发现两个功能。通过调研我们可以发现,针对服务器端的gRPC的负载均衡,目前主要有NginxEnvoy两种方案,这两种相方案对要更复杂一点,博主目前所在的公司,在gRPC的负载均衡上感觉是个空白,这算是博主想要研究gRPC的一个主要原因。而在这里,由于Consul里注册了所有gRPC服务的终结点信息,所以,我们更容易想到的,其实是客户端的负载均衡,具体怎么实现呢?我们一起看一下:

// 从Consul中获取服务终结点信息
var consulClient = serviceProvider.GetService<IConsulClient>();
var serviceName = typeof(TGrpcClient).Name.Replace("Client", "Service");
var services = await consulClient.Health.Service(serviceName, string.Empty, true);
var serviceUrls = services.Response.Select(s => $"{s.Service.Address}:{s.Service.Port}").ToList();
if (serviceUrls == null || !serviceUrls.Any())
    throw new Exception($"Please make sure service {serviceName} is registered in consul");

// 构造Channel和Client
var serviceUrl = serviceUrls[new Random().Next(0, serviceUrls.Count - 1)];
var channel = GrpcChannel.ForAddress($"https://{serviceUrl}");
var client = new var client = new Calculator.CalculatorClient(channel);
await client.CalcAsync(new CalculatorRequest() { Num1 = 10, Op = "+", Num2 = 12 });

可以看出,基本思路就是从Consul里拿到对应服务的终结点信息,然后构造出GrpcChannel,再通过GrpcChannel构造出 Client 即可。

不过,博主觉得这个过程有一点繁琐,我们有没有办法让这些细节隐藏起来呢?于是,我们有了下面的改进方案:

public static async Task<TGrpcClient> GetGrpcClientAsync<TGrpcClient>(
  this IServiceProvider serviceProvider
)
{
    var consulClient = serviceProvider.GetService<IConsulClient>();
    var serviceName = typeof(TGrpcClient).Name.Replace("Client", "Service");
    var services = await consulClient.Health.Service(serviceName, string.Empty, true);
    var serviceUrls = services.Response.Select(s => $"{s.Service.Address}:{s.Service.Port}").ToList();
    if (serviceUrls == null || !serviceUrls.Any())
        throw new Exception($"Please make sure service {serviceName} is registered in consul");

    var serviceUrl = serviceUrls[new Random().Next(0, serviceUrls.Count - 1)];
    var channel = GrpcChannel.ForAddress($"https://{serviceUrl}");
    var constructorInfo = typeof(TGrpcClient).GetConstructor(new Type[] { typeof(GrpcChannel) });
    if (constructorInfo == null)
        throw new Exception($"Please make sure {typeof(TGrpcClient).Name} is a gRpc client");

    var clientInstance = (TGrpcClient)constructorInfo.Invoke(new object[] { channel });
    return clientInstance;
}

现在,有没有觉得简单一点?完美!

var client = await serviceProvider.GetGrpcClientAsync<CalculatorClient>();
await client.CalcAsync(new CalculatorRequest() { Num1 = 1, Num2 = 2, Op = "+" });

gRPC 接口的测试工具

我猜,大多数看到这个标题会一脸鄙夷,心里大概会想,就测试工具这种东西值得特地写出来吗?诚然,以前写 API 接口的时候,大家都是用 Postman 或者 Apifox 这样的工具来进行测试的,可是突然有一天你要调试一个gRPC的接口,你总不能每次都调用客户端啊,所以,这里要给大家推荐两个gRPC接口的测试工具,它们分别是: grpcurlgrpcui,它们都出自同一个人 FullStory 之手,基于 Go 语言开发,简单介绍下使用方法:

// 建议使用国内源
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

// grpcurl
brew install grpcurl

// grpcui
go get github.com/fullstorydev/grpcui/...
go install github.com/fullstorydev/grpcui/cmd/grpcui

// 安装后的路径为:C:\Users\<User>\go\bin\grpcui.exe
grpcui -bind <Your-IP> -plaintext <Your-gRPC-Service>

虽然这个说明简单而直白,可我还是没能装好,我不得不祭出 Docker 这个神器,果然它不会令我失望:

docker pull wongnai/grpcui
docker run -e GRPCUI_SERVER=localhost:5001 -p 8080:8080 wongnai/grpcui

这里有两个重要的参数,其中,8080grpcui的服务地址,可以按个人喜好进行修改,GRPCUI_SERVERgRPC服务地址,该工具运行效果如下:

gRPCUI 接口测试工具 gRPCUI 接口测试工具

对于使用者来说,我们只需要选择服务(service)、方法(rpc)、然后填入参数即可,个人感觉非常方便。

本文小结

本文探索并实现了gRPC服务健康检查,主要提供了两种思路:基于IHostedService + Timer的轮询的方案 以及 基于Consul的集服务注册、服务发现、健康检查于一身的方案。特别地,对于后者而言,我们可以顺理成章地联想到客户端的负载均衡,其原理是:Consul中注册了所有gRPC服务的终结点信息,通过IConsulClient可以拿到所有可用的终结点信息,只要以此为基础来构建GrpcChannel即可。根据这个原理,我们引申出了gRPC客户端负载均衡的相关话题,这里我们采用的是随机选择一个终结点信息的做法,事实上,按照一般负载均衡的理论,我们还可以采取轮询、加权、Hash 等等的算法,大家可以按照自己的业务场景来选择合适的方法。最后,我们简单介绍了下gRPC接口测试方面的内容,它可以帮助我们更高效地编写、验证gRPC接口。好了,以上就是这篇博客的全部内容啦,欢迎大家在评论区留言、参与讨论,谢谢大家!