当前位置:首页 > 技术 > 正文内容

.NET Core Ocelot+Consul+JWT 身份验证与服务治理,网关配置(四):集成 gRPC 通信

访客 技术 2026年6月19日 1

在微服务架构中,随着服务数量增加,不仅外部请求可通过网关(Ocelot)统一接入,服务间的内部调用也需高效可靠的通信机制。本文将演示如何在现有系统基础上,通过 gRPC 实现服务间高性能通信,并结合 Consul 完成服务注册与发现。

创建 gRPC 服务

新建一个 gRPC 项目,定义服务契约文件 user.proto

syntax = "proto3";
option csharp_namespace = "GrpcUserService";
package user;

service UserGrpc {
    rpc GetUser (GetUserRequest) returns (StringData);
}

message GetUserRequest {
    int64 id = 1;
}

message StringData {
    string data = 1;
}

该接口用于根据用户 ID 查询用户信息并返回序列化后的 JSON 字符串。

实现 gRPC 服务逻辑

实现 UserGrpc 接口,使用 Entity Framework Core 查询数据库:

using Domain.Entities;
using Domain.Respositorys;
using Grpc.Core;
using GrpcUserService;
using Newtonsoft.Json;
using System.Text.Json.Serialization;

namespace GrpcService.Services
{
    public class UserService : UserGrpc.UserGrpcBase
    {
        private readonly ILogger<UserService> logger;
        private readonly IRespository repository;

        public UserService(ILogger<UserService> logger, IRespository repository)
        {
            this.logger = logger;
            this.repository = repository;
        }

        public override async Task<StringData> GetUser(GetUserRequest request, ServerCallContext context)
        {
            long userId = request.Id;
            var user = await repository.FindUserById(userId);
            string result = JsonConvert.SerializeObject(user);
            return new StringData { Data = result };
        }
    }
}

Program.cs 中注册服务:

app.MapGrpcService<UserService>();

此时客户端已可调用该服务,但为实现服务治理,还需将其注册至 Consul。

添加健康检查支持

gRPC 服务需提供健康状态检查接口。定义 health.proto

syntax = "proto3";
option csharp_namespace = "GrpcHealth";
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);
}

实现健康检查服务:

using Grpc.Core;
using GrpcHealth;

namespace GrpcService.Services
{
    public class HealthCheckService : Health.HealthBase
    {
        public override Task<HealthCheckResponse> Check(HealthCheckRequest request, ServerCallContext context)
        {
            return Task.FromResult(new HealthCheckResponse
            {
                Status = HealthCheckResponse.Types.ServingStatus.Serving
            });
        }

        public override async Task Watch(HealthCheckRequest request, IServerStreamWriter<HealthCheckResponse> responseStream, ServerCallContext context)
        {
            await responseStream.WriteAsync(new HealthCheckResponse
            {
                Status = HealthCheckResponse.Types.ServingStatus.Serving
            });
        }
    }
}

注册健康检查服务:

app.MapGrpcService<HealthCheckService>();

注册到 Consul 服务发现

安装 NuGet 包 NConsul.AspNetCore

简单注册方式:

builder.Services.AddConsul("http://localhost:8500")
    .AddGRPCHealthCheck("localhost:5285")
    .RegisterService("GrpcService", "localhost", 5285, null);

更优雅的配置封装

创建配置类:

public class GrpcOptions
{
    public string ConsulUrl { get; set; }
    public string CheckHealthUrl { get; set; }
    public GrpcService GrpcService { get; set; }
}

public class GrpcService
{
    public string Name { get; set; }
    public string Host { get; set; }
    public int Port { get; set; }
    public string[] Tags { get; set; }
}

扩展方法封装注册逻辑:

public static class ConsulExtend
{
    public static IServiceCollection AddCustomGrpcConsul(this IServiceCollection services, Action<GrpcOptions> action)
    {
        var options = new GrpcOptions();
        action.Invoke(options);
        services.AddConsul(options.ConsulUrl)
                .AddGRPCHealthCheck(options.CheckHealthUrl)
                .RegisterService(options.GrpcService.Name, options.GrpcService.Host, options.GrpcService.Port, options.GrpcService.Tags);
        return services;
    }

    public static IServiceCollection AddCustomGrpcConsul(this IServiceCollection services, IConfiguration configuration)
    {
        var options = new GrpcOptions();
        configuration.Bind("GrpcOptions", options);
        services.AddConsul(options.ConsulUrl)
                .AddGRPCHealthCheck(options.CheckHealthUrl)
                .RegisterService(options.GrpcService.Name, options.GrpcService.Host, options.GrpcService.Port, options.GrpcService.Tags);
        return services;
    }
}

Program.cs 中使用:

builder.Services.AddCustomGrpcConsul(builder.Configuration);

配置文件 appsettings.json

"GrpcOptions": {
  "ConsulUrl": "http://localhost:8500",
  "CheckHealthUrl": "localhost:5285",
  "GrpcService": {
    "Name": "GrpcService",
    "Host": "localhost",
    "Port": 5285,
    "Tags": ["grpc"]
  }
}

完成配置后,服务将成功注册至 Consul。

客户端调用 gRPC 服务

在调用方项目中复制 user.proto 文件,并修改生成模式为客户端:

<ItemGroup>
  <Protobuf Include="Protos\user.proto" GrpcServices="Client" />
</ItemGroup>

在 Web API 控制器中调用远程服务:

[HttpGet("{id}")]
public IActionResult Get([FromRoute] long id)
{
    string targetUrl = this.dispatcher.GetAddress("http://GrpcService");
    AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);

    using var channel = GrpcChannel.ForAddress(targetUrl);
    var client = new UserGrpc.UserGrpcClient(channel);

    var response = client.GetUser(new GetUserRequest { Id = id });
    var user = JsonConvert.DeserializeObject<User>(response.Data);

    return Ok(user);
}

其中 dispatcher 是基于 Consul 的服务发现组件,用于获取可用服务实例地址。

实现 Consul 服务发现与负载均衡

抽象基类实现服务发现核心逻辑:

public abstract class AbstractConsulDispatcher
{
    protected ConsulOptions options;
    protected KeyValuePair<string, AgentService>[] _currentServices = null;

    public AbstractConsulDispatcher(IOptionsMonitor<ConsulOptions> consulOptions)
    {
        options = consulOptions.CurrentValue;
    }

    public string GetAddress(string mappingUrl)
    {
        var uri = new Uri(mappingUrl);
        string serviceName = uri.Host;
        string addressPort = ChooseAddress(serviceName);
        return $"{uri.Scheme}://{addressPort}{uri.PathAndQuery}";
    }

    protected virtual string ChooseAddress(string serviceName)
    {
        var client = new ConsulClient(c => 
        {
            c.Address = new Uri(options.ConsulMain.Address);
            c.Datacenter = options.ConsulMain.Datacenter;
        });

        var response = client.Agent.Services().Result.Response;
        _currentServices = response.Where(s => s.Value.Service.Equals(serviceName, StringComparison.OrdinalIgnoreCase)).ToArray();

        int index = GetIndex();
        var service = _currentServices[index].Value;
        return $"{service.Address}:{service.Port}";
    }

    protected abstract int GetIndex();
}

轮询策略实现:

public class PollingDispatcher : AbstractConsulDispatcher
{
    private static int _index = 0;

    public PollingDispatcher(IOptionsMonitor<ConsulOptions> consulOptions) : base(consulOptions) { }

    protected override int GetIndex()
    {
        return _index++ % _currentServices.Length;
    }
}

注册服务:

builder.Services.AddScoped<AbstractConsulDispatcher, PollingDispatcher>();

测试调用成功,服务间通过 gRPC 高效通信,且具备自动发现与负载均衡能力。

总结

gRPC 提供了高效的二进制通信协议,配合 Protobuf 编码,显著提升性能。服务注册与发现通过 Consul 实现,只需简单配置即可完成服务动态注册、健康检查和客户端发现。整个流程清晰、可维护性强,适合构建大规模微服务架构。

关键点回顾:

  • gRPC 协议定义通过 .proto 文件描述;
  • 实现服务逻辑继承自 *Base 类并重写方法;
  • 使用 MapGrpcService 暴露服务;
  • 借助 Consul 进行服务注册与发现;
  • 客户端通过 GrpcChannel.ForAddress 调用远程服务;
  • 结合负载均衡策略实现高可用。
标签: gRPCConsul

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。