• 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
  • 相关阅读:
    离开APM的弹性云还是真弹性吗
    系统性能工程师
    How the performance impacts your revenue-性能影响营收
    The Performance Manifesto
    APM系列-国外新兴厂商New Relic vs. AppDynamics
    Performance testing architecture
    Does Little'law really applicable to apply performance model now?
    Load average in Linux的精确含义
    Eric's并发用户数估算与Little定律的等价性
    Sublime Text 3 插件安装及Vim 模式设置
  • 原文地址:https://www.cnblogs.com/diwu0510/p/11706263.html
Copyright © 2020-2023  润新知