.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;