.NET Core Ocelot+Consul+JWT 身份验证与服务治理,网关配置(四):集成 gRPC 通信
在微服务架构中,随着服务数量增加,不仅外部请求可通过网关(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调用远程服务; - 结合负载均衡策略实现高可用。