.net core ocelot+consul+jwt 身份验证,服务治理与发现,网关配置(四)增加 gRPC
服务多了 外部访问 可以通过 网关(ocelot) 访问 但是服务与服务之间的通信 怎么通信呢 比如说订单服务 ,查询某个订单下的 所购买的商品,商品 在单独的商品服务中 ,可以使用 httpclient 请求
也可以使用gRPC的方式下面就是演示 gRPC的方式
新建 grpc服务

新建一个proto
syntax = "proto3"; option csharp_namespace = "GrpcUserService"; package user; // The greeting service definition. service UserGrpc { // Sends a greeting rpc GetUser (GetUserRequest) returns (StringData); } // The request message containing the user's name. message GetUserRequest { int64 id=1; } message StringData{ string data=1; }
返回调用端 是string

这个逻辑很简单 就是 通过EF查询 user 根据userid 查询出来 json序列化字符串 返回给调用端 继承 UserGrpc.UserGrpcBase 并重写 proto 定义的方法
using Domain.Entities; using Domain.Respositorys; using Grpc.Core; using GrpcUserService; using System.Text.Json.Serialization; using Newtonsoft.Json; namespace GrpcService.Services { public class UserService : UserGrpc.UserGrpcBase { private readonly ILogger<UserService> logger; private readonly IRespository respository; public UserService(ILogger<UserService> logger, IRespository respository) { this.logger = logger; this.respository = respository; } public override async Task<StringData> GetUser(GetUserRequest request, ServerCallContext context) { long id = request.Id; User user = await this.respository.FindUserById(id); string reuslt = JsonConvert.SerializeObject(user); return new StringData { Data = reuslt }; } } }
使用
app.MapGrpcService<UserService>();
其实到这步 客户端 就可以使用GRPC调用了 但是 我要注册到 Consul中去 所以以下是 搞到Consul中
先写健康检查的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) { //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 }); } } }
使用
app.MapGrpcService<HealthCheckService>();
首先 下包
NConsul.AspNetCore

注册服务 到consul中
builder.Services.AddConsul("http://localhost:8500")
.AddGRPCHealthCheck("localhost:5285")
.RegisterService("GRPCService", "localhost", 5285, null);
以上就可以注册到consul中去了 但是这种不够优雅 和灵活
单独搞了一个基础设施

配置项
public class GrpcOptions
{
/// <summary>
/// consul 地址 例如 http://localhost:8500
/// </summary>
public string ConsulUrl { get; set; }
/// <summary>
/// grpc 健康检查 地址 localhost:5285
/// </summary>
public string CheckHealthUrl { get; set; }
public GrpcService GrpcService { get; set; }
}
public class GrpcService
{
/// <summary>
/// grpc 服务名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 服务 host 例如 localhost
/// </summary>
public string Host { get; set; }
/// <summary>
/// 服务端口号 例如 5285
/// </summary>
public int Port { get; set; }
public string[] Tags { get; set; }
}
扩展
public static class ConsulExtend
{
/// <summary>
/// grpc consul注册扩展
/// </summary>
/// <param name="services"></param>
/// <param name="action"></param>
/// <returns></returns>
public static IServiceCollection AddCustomGrpcConsul(this IServiceCollection services, Action<GrpcOptions> action)
{
GrpcOptions grpcOptions = new GrpcOptions();
action.Invoke(grpcOptions);
services.AddConsul(grpcOptions.ConsulUrl)
.AddGRPCHealthCheck(grpcOptions.CheckHealthUrl)
.RegisterService(grpcOptions.GrpcService.Name, grpcOptions.GrpcService.Host,
grpcOptions.GrpcService.Port, grpcOptions.GrpcService.Tags);
return services;
}
/// <summary>
/// grpc consul注册扩展
/// </summary>
/// <param name="services"></param>
/// <param name="configuration"></param>
/// <returns></returns>
public static IServiceCollection AddCustomGrpcConsul(this IServiceCollection services, IConfiguration configuration)
{
GrpcOptions grpcOptions = new GrpcOptions();
configuration.Bind("GrpcOptions", grpcOptions);
services.AddConsul(grpcOptions.ConsulUrl)
.AddGRPCHealthCheck(grpcOptions.CheckHealthUrl)
.RegisterService(grpcOptions.GrpcService.Name, grpcOptions.GrpcService.Host,
grpcOptions.GrpcService.Port, grpcOptions.GrpcService.Tags);
return services;
}
}
使用
builder.Services.AddCustomGrpcConsul(builder.Configuration);
appsettings.json 配置
"GrpcOptions": { "ConsulUrl": "http://localhost:8500", "CheckHealthUrl": "localhost:5285", "GrpcService": { "Name": "GrpcService", "Host": "localhost", "Port": 5285 } },
好了 配置成功 后 测试consul 能不能注入
成功 注册到consul

以下 是调用端的代码
复制服务端proto 到调用端

复制过来 默认是 GrpcServices="Server"
修改调用端 GrpcServices="Client"
<ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
<Protobuf Include="Protos\user.proto" GrpcServices="Client" />
</ItemGroup>

调用端 wep 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); { StringData stringData = client.GetUser(new GetUserRequest { Id = id }); User user = JsonConvert.DeserializeObject<User>(stringData.Data); return Ok(user); } //var client1 = new Greeter.GreeterClient(channel); //{ // var result = client1.SayHello(new HelloRequest { Name = "小明" }); // return Ok(result.Message); //} } //StringData stringData = this.greeterClient.GetUser(new GetUserRequest { Id = id }); //User user = JsonConvert.DeserializeObject<User>(stringData.Data); //return Ok(user); }
this.dispatcher 这个代码 就是 consul 获取服务的地址
/// <summary>
/// Consul服务发现+负载均衡
/// </summary>
public abstract class AbstractConsulDispatcher
{
protected ConsulOptions options = null;
protected KeyValuePair<string, AgentService>[] _CurrentAgentServiceDictionary = null;
public AbstractConsulDispatcher(IOptionsMonitor<ConsulOptions> consulClientOption)
{
this.options = consulClientOption.CurrentValue;
}
/// <summary>
/// 负载均衡获取地址
/// </summary>
/// <param name="mappingUrl">Consul映射后的地址</param>
/// <returns></returns>
public string GetAddress(string mappingUrl)
{
Uri uri = new Uri(mappingUrl);
string serviceName = uri.Host;
string addressPort = this.ChooseAddress(serviceName);
return $"{uri.Scheme}://{addressPort}{uri.PathAndQuery}";
}
protected virtual string ChooseAddress(string serviceName)
{
ConsulClient client = new ConsulClient(c =>
{
c.Address = new Uri(options.ConsulMain.Address);
c.Datacenter = options.ConsulMain.Datacenter;
});
AgentService agentService = null;
var response = client.Agent.Services().Result.Response;
//foreach (var item in response)
//{
// Console.WriteLine("***************************************");
// Console.WriteLine(item.Key);
// var service = item.Value;
// Console.WriteLine($"{service.Address}--{service.Port}--{service.Service}");
// Console.WriteLine("***************************************");
//}
this._CurrentAgentServiceDictionary = response.Where(s => s.Value.Service.Equals(serviceName, StringComparison.OrdinalIgnoreCase)).ToArray();
int index = this.GetIndex();
agentService = this._CurrentAgentServiceDictionary[index].Value;
return $"{agentService.Address}:{agentService.Port}";
}
protected abstract int GetIndex();
}
轮询策略
/// <summary> /// 轮询 /// </summary> public class PollingDispatcher : AbstractConsulDispatcher { #region Identity private static int _iTotalCount = 0; private static int iTotalCount { get { return _iTotalCount; } set { _iTotalCount = value >= Int32.MaxValue ? 0 : value; } } public PollingDispatcher(IOptionsMonitor<ConsulOptions> consulClientOption) : base(consulClientOption) { } #endregion /// <summary> /// 轮询 /// </summary> /// <param name="serviceCount"></param> /// <returns></returns> protected override int GetIndex() { return iTotalCount++ % base._CurrentAgentServiceDictionary.Length; } }
使用
builder.Services.AddScoped<AbstractConsulDispatcher, PollingDispatcher>();
测试一下 成功调用

总结 其实 grpc proto 类似 接口 配置 完需要 实现接口的方法 没啥难的 一开始可能 有点懵 多写几遍就熟悉了 Consul 也不难 就是 注册 和发现
注册 就是 client.Agent.ServiceRegister 发现 就是 client.Agent.Services().Result.Response;


浙公网安备 33010602011771号