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

 

 

 

 

 

posted on 2023-05-18 23:47  是水饺不是水饺  阅读(145)  评论(0)    收藏  举报

导航