Ocelot 网关 和 consul 服务发现

服务发现 Consul

一、安装和启动

下载 [Consul](https://www.consul.io/downloads.html)

下载完成后,解压,只有一个consul.exe,把目录添加到环境变量的PATH,注意添加到系统变量,仅仅加入用户变量不起作用。打开cmd,输入

consul agen -dev  // 启动Consul服务

二、在aspnetcore中注册Consul

1. 定义配置项
/// 
/// Consul配置
/// 
public class ConsulOptions
{
    /// 
    /// Consul服务器的地址
    /// 
    public string HttpEndPoint { get; set; }

    /// 
    /// 数据中心名称
    /// 
    public string DataCenter { get; set; }

    /// 
    /// Dns服务器端点
    /// 
    public DnsEndPoint DnsEndPoint { get; set; }
}
/// 
/// Dns节点
/// 
public class DnsEndPoint
{
    /// 
    /// 服务器地址
    /// 
    public string Address { get; set; }

    /// 
    /// 端口
    /// 
    public int Port { get; set; }

    /// 
    /// 转换为IpEndPoint
    /// 
    /// 
    public IPEndPoint ToIpEndPoint()
    {
        return new IPEndPoint(IPAddress.Parse(Address), Port);
    }
}
public class ServiceDiscoveryOptions
{
    /// 
    /// 服务名称
    /// 
    public string ServiceName { get; set; }

    /// 
    /// Consul配置
    /// 
    public ConsulOptions Consul { get; set; }
}
2. 在appsettings.json中添加配置
"ServiceDiscovery": {
    "ServiceName": "webapi1",
    "Consul": {
        "DataCenter": "dc1",
        "HttpEndPoint": "http://127.0.0.1:8500",
        "DnsEndPoint": {
          "Address": "127.0.0.1",
          "Port": 8600
        }
    }
}
3. 在startup中注册配置项
services.Configure(
    Configuration.GetSection("ServiceDiscovery"));
4. 注册IConsulClient服务
services.AddSingleton(new ConsulClient(config =>
{
    config.Address = new Uri(Configuration["ServiceDiscovery:Consul:HttpEndPoint"]);
    config.Datacenter = Configuration["ServiceDiscovery:Consul:DataCenter"];
}));
5. 在Configure中将自身注册到Consul服务
ILifeTime的ApplicationStarted,程序启动时注册到Consul,
使用IApplicationBuilder的Feature获取当前启动的ip和端口,作为服务的ID
public static class ApplicationBuilderExtensions
{
    /// 
    /// 获取当前启动应用绑定的IP和端口
    /// 
    public static IEnumerable GetAddress(this IApplicationBuilder app)
    {
        var features = app.Properties["server.Features"] as FeatureCollection;
        return features?.Get()
            .Addresses.Select(p => new Uri(p)) ?? new List();
    }
}
public void Configure(
    IApplicationBuilder app, IHostingEnvironment env,
    IApplicationLifetime lifetime, IConsulClient consul,
    IOptions serviceDiscoveryOptions)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    lifetime.ApplicationStarted.Register(() =>
    {
        var addresses = app.GetAddress();
        foreach (var address in addresses)
        {
            var serviceId =
            $"{serviceDiscoveryOptions.Value.ServiceName}_{address.Host}:{address.Port}";

            consul.Agent.ServiceRegister(new AgentServiceRegistration
            {
                ID = serviceId,
                Name = serviceDiscoveryOptions.Value.ServiceName,
                Address = address.Host,
                Port = address.Port,
                Check = new AgentServiceCheck
                {
                    DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),
                    Interval = TimeSpan.FromSeconds(5),
                    HTTP = new Uri(address, "api/Health/Check").OriginalString,
                    Timeout = TimeSpan.FromSeconds(5)
                }
            }).Wait();

            lifetime.ApplicationStopped.Register(() => 
            { 
                consul.Agent.ServiceDeregister(serviceId).Wait(); 
            });
        }
    });

    app.UseAuthentication();

    app.UseMvc();
}

三、在项目中使用Consul

1、 定义一个接口
public interface IServiceDiscoveryManager
{
    string GetApi(string serviceName);
}
2、 实现一个Consul的服务发现工具
public class ConsulServiceDiscoveryManager : IServiceDiscoveryManager
{
    private readonly IConsulClient _client;

    private readonly ILogger _logger;

    // 手动高亮,忽略大小写,这个很重要
    private readonly ConcurrentDictionary> _dict = 
        new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase);

    private DateTime _syncTime = DateTime.Now.AddMinutes(-11);

    private static object _lock = new object();

    public ConsulServiceDiscoveryManager(IConsulClient client, ILogger logger)
    {
        _client = client;
        _logger = logger;
    }

    private ConcurrentDictionary> GetAllServices()
    {
        if (_syncTime < DateTime.Now.AddSeconds(-10) || !_dict.Keys.Any())
        {
            _dict.Clear();
            var services = _client.Agent.Services().Result.Response.Select(s => s.Value).GroupBy(s => s.Service);
            foreach (var service in services)
            {
                _dict.TryAdd(service.Key, service.Select(s => $"{s.Address}:{s.Port}").ToList());
            }
            _syncTime = DateTime.Now;
        }
        return _dict;
    }

    public List GetApis(string serviceName)
    {
        if(!GetAllServices().TryGetValue(serviceName, out var hosts))
        {
            return new List();
        }
        return hosts;
    }

    public string GetApi(string serviceName)
    {
        var hosts = GetApis(serviceName);

        var count = hosts.Count();
        if(count == 0)
        {
            return string.Empty;
        }
        else if(count == 1)
        {
            return hosts.First();
        }

        var ran = new Random().Next(0, count);
        return hosts.ElementAt(ran);
    }
}
3、 在ConfigureServices中注册IServiceDiscoveryManager。
捎带把 IHttpConlientFactory 也注册掉,
注意在 appsettings.json 里把之前的 ServiceDiscoverty 的配置项添加进去,
也可以只添加Consul的部分
services.AddSingleton(new ConsulClient(config =>
{
    config.Address = new Uri(Configuration["ServiceDiscovery:Consul:HttpEndPoint"]);
    config.Datacenter = Configuration["ServiceDiscovery:Consul:DataCenter"];
}));

services.AddSingleton();

services.AddHttpClient();
4、 在Controller中使用,也可以封装一个Client,后面可以直接用。
public class TestController : Controller
{
    private readonly ILogger _logger;
    private readonly IHttpClientFactory _clientFactory;
    private readonly IServiceDiscoveryManager _serviceDiscoveryManager;

    public TestController(ILogger logger, 
        IConsulClient consul, 
        IHttpClientFactory clientFactory, 
        IServiceDiscoveryManager serviceDiscoveryManager)
    {
        _logger = logger;
        _clientFactory = clientFactory;
        _serviceDiscoveryManager = serviceDiscoveryManager;
    }

    public IActionResult Index()
    {
        var apiHost = _serviceDiscoveryManager.GetApi("WebApi1");
        using(var client = _clientFactory.CreateClient())
        {
            var result = client.GetAsync($"http://{apiHost}/api/values").Result;
            return Content(result.Content.ReadAsStringAsync().Result);
        }
    }
}

Ocelot 集成 Consul

一、 引入Nuget包
Ocelot
Ocelot.Provider.Consul
二、 在根目录下创建ocelog.json
{
  "ReRoutes": [
    {
      "DownstreamPathTemplate": "/api/{url}",
      "DownstreamScheme": "http",
      "UseServiceDiscovery": true,
      "ServiceName": "WebApi1",
      "LoadBalancerOptions": {
        "Type": "RoundRobin" //轮询
      },
      "UpstreamPathTemplate": "/WebApi1/{url}",
      "UpstreamHttpMethod": [ "Get", "Post" ]
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "http://127.0.0.1:5010",
    "ServiceDiscoveryProvider": {
      "Host": "localhost",
      "Port": 8500,
      "Type": "Consul"
    }
  }
}
三、 修改Program.cs,把刚刚创建好的ocelot.json添加到配置项
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration(configuration => 
        { 
            configuration.AddJsonFile("ocelot.json"); 
        })
        .UseUrls("http://localhost:5010")
        .UseStartup();
四、 修改Startup,注册服务,使用ocelot的中间件
public void ConfigureServices(IServiceCollection services)
{
    services.AddOcelot().AddConsul();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseOcelot().Wait();
}
五、 一个比较全的配置项说明
{
  "ReRoutes": [
    {//官方文档ReRoutes全节点示例
      //Upstream表示上游请求,即客户端请求到API Gateway的请求
      "UpstreamPathTemplate": "/", //请求路径模板
      "UpstreamHttpMethod": [ //请求方法数组
        "Get"
      ],

      //Downstreamb表示下游请求,即API Gateway转发的目标服务地址
      "DownstreamScheme": "http", //请求协议,目前应该是支持http和https
      "DownstreamHost": "localhost", //请求服务地址,应该是可以是IP及域名
      "DownstreamPort": 51779, //端口号
      "DownstreamPathTemplate": "/", //下游请求地址模板
      "RouteClaimsRequirement": { //标记该路由是否需要认证
        "UserType": "registered" //示例,K/V形式,授权声明,授权token中会包含一些claim,如填写则会判断是否和token中的一致,不一致则不准访问
      },
      //以下三个是将access claims转为用户的Header Claims,QueryString,该功能只有认证后可用
      "AddHeadersToRequest": { //
        "UserType": "Claims[sub] > value[0] > |", //示例
        "UserId": "Claims[sub] > value[1] > |"//示例
      },
      "AddClaimsToRequest": {},
      "AddQueriesToRequest": {},

      "RequestIdKey": "", //设置客户端的请求标识key,此key在请求header中,会转发到下游请求中
      "FileCacheOptions": { //缓存设置
        "TtlSeconds": 15, //ttl秒被设置为15,这意味着缓存将在15秒后过期。
        "Region": "" //缓存region ,可以使用administrator API清除
      },
      "ReRouteIsCaseSensitive": false, //路由是否匹配大小写
      "ServiceName": "", //服务名称,服务发现时必填

      "QoSOptions": { //断路器配置,目前Ocelot使用的Polly
        "ExceptionsAllowedBeforeBreaking": 0, //打开断路器之前允许的例外数量。
        "DurationOfBreak": 0,                 //断路器复位之前,打开的时间(毫秒)
        "TimeoutValue": 0                     //请求超时时间(毫秒)
      },
      "LoadBalancerOptions": {
        "type": "RoundRobin"   // 负载均衡 RoundRobin(轮询)/LeastConnection(最少连接数)
      }, 
      "RateLimitOptions": { //官方文档未说明
        "ClientWhitelist": [], // 白名单
        "EnableRateLimiting": false, // 是否限流
        "Period": "1m", // 1s,4m,1h,1d
        "PeriodTimespan": 0, // 多少秒之后客户端可以充实
        "Limit": 0 // 一个时间周期最多可以请求的次数
      },
      "AuthenticationOptions": { //认证配置
        "AuthenticationProviderKey": "", //这个key对应的是代码中.AddJWTBreark中的Key
        "AllowedScopes": []//使用范围
      },
      "HttpHandlerOptions": {
        "AllowAutoRedirect": true, //指示请求是否应该遵循重定向响应。 如果请求应该自动遵循来自Downstream资源的重定向响应,则将其设置为true; 否则为假。 默认值是true。
        "UseCookieContainer": true //该值指示处理程序是否使用CookieContainer属性来存储服务器Cookie,并在发送请求时使用这些Cookie。 默认值是true。
      },
      "UseServiceDiscovery": false //使用服务发现,目前Ocelot只支持Consul的服务发现
    }
  ],
  "GlobalConfiguration": {}
}
六、 再来一个简单的不使用Consul的配置项
{
  "ReRoutes": [
    {
      "DownstreamPathTemplate": "/api/{url}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5001
        },
        {
          "Host": "localhost",
          "Port": 5002
        }
      ],
      "LoadBalancerOptions": {
        "Type": "RoundRobin"
      },
      "UpstreamPathTemplate": "/UserService/{url}",
      "UpstreamHttpMethod": [ "Get", "Post" ]
    }
  ]
}
七、熔断
引入Nuget包
Ocelot.Provider.Polly

你可能感兴趣的:(Ocelot 网关 和 consul 服务发现)