Ocelot 網關 和 consul 服務發現


服務發現 Consul

一、安裝和啟動

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

下載完成后,解壓,只有一個consul.exe,把目錄添加到環境變量的PATH,注意添加到系統變量,僅僅加入用戶變量不起作用。打開cmd,輸入

consul agen -dev  // 啟動Consul服務

二、在aspnetcore中注冊Consul

1. 定義配置項
/// <summary>
/// Consul配置
/// </summary>
public class ConsulOptions
{
    /// <summary>
    /// Consul服務器的地址
    /// </summary>
    public string HttpEndPoint { get; set; }

    /// <summary>
    /// 數據中心名稱
    /// </summary>
    public string DataCenter { get; set; }

    /// <summary>
    /// Dns服務器端點
    /// </summary>
    public DnsEndPoint DnsEndPoint { get; set; }
}
/// <summary>
/// Dns節點
/// </summary>
public class DnsEndPoint
{
    /// <summary>
    /// 服務器地址
    /// </summary>
    public string Address { get; set; }

    /// <summary>
    /// 端口
    /// </summary>
    public int Port { get; set; }

    /// <summary>
    /// 轉換為IpEndPoint
    /// </summary>
    /// <returns></returns>
    public IPEndPoint ToIpEndPoint()
    {
        return new IPEndPoint(IPAddress.Parse(Address), Port);
    }
}
public class ServiceDiscoveryOptions
{
    /// <summary>
    /// 服務名稱
    /// </summary>
    public string ServiceName { get; set; }

    /// <summary>
    /// Consul配置
    /// </summary>
    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<ServiceDiscoveryOptions>(
    Configuration.GetSection("ServiceDiscovery"));
4. 注冊IConsulClient服務
services.AddSingleton<IConsulClient>(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
{
    /// <summary>
    /// 獲取當前啟動應用綁定的IP和端口
    /// </summary>
    public static IEnumerable<Uri> GetAddress(this IApplicationBuilder app)
    {
        var features = app.Properties["server.Features"] as FeatureCollection;
        return features?.Get<IServerAddressesFeature>()
            .Addresses.Select(p => new Uri(p)) ?? new List<Uri>();
    }
}
public void Configure(
    IApplicationBuilder app, IHostingEnvironment env,
    IApplicationLifetime lifetime, IConsulClient consul,
    IOptions<ServiceDiscoveryOptions> 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<ConsulServiceDiscoveryManager> _logger;

    // 手動高亮,忽略大小寫,這個很重要
    private readonly ConcurrentDictionary<string, List<string>> _dict = 
        new ConcurrentDictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);

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

    private static object _lock = new object();

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

    private ConcurrentDictionary<string, List<string>> 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<string> GetApis(string serviceName)
    {
        if(!GetAllServices().TryGetValue(serviceName, out var hosts))
        {
            return new List<string>();
        }
        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<IConsulClient>(new ConsulClient(config =>
{
    config.Address = new Uri(Configuration["ServiceDiscovery:Consul:HttpEndPoint"]);
    config.Datacenter = Configuration["ServiceDiscovery:Consul:DataCenter"];
}));

services.AddSingleton<IServiceDiscoveryManager, ConsulServiceDiscoveryManager>();

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

    public TestController(ILogger<TestController> 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>();
四、 修改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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM