1. 程式人生 > 實用技巧 >.Net Core 服務閘道器、服務註冊、服務發現總結

.Net Core 服務閘道器、服務註冊、服務發現總結

一、簡介

Ocelot:Ocelot是一個用.NET Core實現並且開源的API閘道器,它功能強大,包括了:路由、請求聚合、服務發現、認證、鑑權、限流熔斷、並內建了負載均衡器與Service Fabric、Butterfly Tracing整合,官方文件:https://ocelot.readthedocs.io/en/latest/index.html

Consul:Consul本質上是一個Socket通訊中介軟體。它主要實現了兩個功能,服務註冊與發現與自身的負載均衡的叢集。官方文件:https://www.consul.io/docs

二、API閘道器搭建

1、新建一個ASP.NET Core Web專案,選用空模板建立

2、安裝Ocelot相關包

3、在Startup中配置Ocelot

public void ConfigureServices(IServiceCollection services)
        {
            services.AddLogDashboard(opt =>
            {
                //授權登陸
                opt.AddAuthorizationFilter(new LogDashboardBasicAuthFilter("admin", "123qwE*"));
                
//請求追蹤 opt.CustomLogModel<RequestTraceLogModel>(); }); services .AddOcelot() //服務發現 .AddConsul() //快取 .AddCacheManager(x => { x.WithDictionaryHandle(); })
//服務質量控制 .AddPolly(); services.AddCors(options => { options.AddPolicy(_defaultCorsPolicyName, builder => builder .WithOrigins( this.Configuration["App:CorsOrigins"] .Split(",", StringSplitOptions.RemoveEmptyEntries) .ToArray() ) .AllowAnyMethod() .AllowAnyHeader()); }); }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseLogDashboard();

            app.UseOcelot().Wait();

            app.UseCors(_defaultCorsPolicyName);
        }

新建Ocelot.json檔案,開啟了服務發現,後面講解服務發現配置,具體如下:

{
  "Routes": [
    {
      //服務名稱,開啟服務發現時需要配置
      "ServiceName": "web-api",
      //是否開啟服務發現
      "UseServiceDiscovery": true,
      //下游服務路由模板
      "DownstreamPathTemplate": "/{url}",
      //下游服務http schema
      "DownstreamScheme": "http",
      //下游服務的地址,如果使用LoadBalancer的話這裡可以填多項
      //"DownstreamHostAndPorts": [
      //  {
      //    "Host": "192.168.1.205",
      //    "Port": 12000
      //  }
      //],
      "UpstreamPathTemplate": "/{url}",
      //上游請求http方法,可使用陣列
      "UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE", "OPTIONS" ],
      /**
       * 負載均衡的演算法:
       * LeastConnection        – 跟蹤哪些服務正在處理請求,並將新請求傳送到具有最少現有請求的服務。演算法狀態沒有分佈在Ocelot叢集中。
       * RoundRobin             – 遍歷可用服務併發送請求。演算法狀態沒有分佈在Ocelot叢集中。
       * NoLoadBalance          – 從配置或服務發現中獲取第一個可用服務
       * CookieStickySessions   -  使用cookie將所有請求貼上到特定伺服器
       */
      "LoadBalancerOptions": {
        "Type": "LeastConnection"
        //以下配置再設定了 CookieStickySessions 後需要開啟
        //用於粘性會話的cookie的金鑰
        //"Key": "ASP.NET_SessionId",
        //會話被阻塞的毫秒數
        //"Expiry": 1800000
      },
      //快取
      "FileCacheOptions": {
        "TtlSeconds": 15
        //"Region": ""
      },
      //限流
      "RateLimitOptions": {
        //包含客戶端白名單的陣列。這意味著該陣列中的客戶端將不受速率限制的影響
        "ClientWhitelist": [],
        //是否啟用端點速率限制
        "EnableRateLimiting": true,
        //指定限制所適用的期間,例如1s,5m,1h,1d等。如果在該期間內發出的請求超出限制所允許的數量,則需要等待PeriodTimespan過去,然後再發出其他請求
        "Period": "1s",
        //指定可以在一定秒數後重試
        "PeriodTimespan": 1,
        //指定客戶端在定義的時間內可以發出的最大請求數
        "Limit": 10
      },
      //熔斷
      "QoSOptions": {
        //允許多少個異常請求
        "ExceptionsAllowedBeforeBreaking": 3,
        //熔斷的時間,單位為毫秒
        "DurationOfBreak": 1000,
        //如果下游請求的處理時間超過多少則自如將請求設定為超時
        "TimeoutValue": 5000
      },
      "HttpHandlerOptions": {
        //是否開啟路由追蹤
        "UseTracing": false
      }
    }
  ],
  "GlobalConfiguration": {
    //Consul服務發現
    "ServiceDiscoveryProvider": {
      "Host": "192.168.1.37",
      "Port": 8500,
      "Type": "Consul"
    },
    //外部暴露的Url
    "BaseUrl": "http://localhost:17450",
    //限流擴充套件配置
    "RateLimitOptions": {
      //指定是否禁用X-Rate-Limit和Retry-After標頭
      "DisableRateLimitHeaders": false,
      //當請求過載被截斷時返回的訊息
      "QuotaExceededMessage": "Oh,Oops!",
      //當請求過載被截斷時返回的http status
      "HttpStatusCode": 4421,
      //用來識別客戶端的請求頭,預設是 ClientId
      "ClientIdHeader": "ClientId"
    }
  }
}

appsettings.json配置如下:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "App": {
    "CorsOrigins": "http://192.168.1.205:4422"
  }
}

修改Program.cs,安裝NLog,因為閘道器啟用了日誌面板

public class Program
    {
        public static void Main(string[] args)
        {
            var logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
            try
            {
                logger.Debug("init main");
                CreateWebHostBuilder(args).Build().Run();
            }
            catch (Exception ex)
            {
                //NLog: catch setup errors
                logger.Error(ex, "Stopped program because of exception");
                throw;
            }
            finally
            {
                // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
                NLog.LogManager.Shutdown();
            }
        }

        private static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .ConfigureAppConfiguration((hostingContext, builder) =>
                {
                    builder
                        .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
                        .AddJsonFile("appsettings.json", true, true)
                        .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
                        .AddJsonFile("Ocelot.json");
                })
                .UseStartup<Startup>()
                .ConfigureLogging(logging =>
                {
                    logging.ClearProviders();
                    logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
                })
                .UseNLog();  // NLog: setup NLog for Dependency injection
    }

nlog.config配置如下:

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      throwExceptions="false"
      internalLogLevel="Off" internalLogFile="nlog-internal.log">

  <variable name="myvar" value="myvalue"/>

  <targets>
    <target xsi:type="file" name="File" fileName="App_Data/Logs/${shortdate}.log"
            layout="${longdate}||${level}||${logger}||${message}||${exception:format=ToString:innerFormat=ToString:maxInnerExceptionLevel=10:separator=\r\n} || ${aspnet-traceidentifier} ||end" />
</targets>

  <rules>
    <logger name="*" minlevel="Debug" writeTo="file" />
  </rules>
</nlog>

至此閘道器配置完成,接下來開始配置服務發現

三、服務註冊、發現

下載consul,使用以下命令啟動服務(這裡不講解叢集搭建方式):

consul agent -server -ui -bootstrap-expect=1 -data-dir=/tmp/consul -node=consul-1 -client=0.0.0.0 -bind=192.168.1.37 -datacenter=dc1

瀏覽器開啟UI介面如下:

新建WebApi專案,同時安裝Consul包到你的專案

新增服務發現擴充套件類ServiceDiscoveryExtensions.cs

public static class ServiceDiscoveryExtensions
    {
        public static void AddConsul(this IServiceCollection serviceCollection)
        {
            IConfiguration configuration;
            using (var serviceProvider = serviceCollection.BuildServiceProvider())
            {
                configuration = serviceProvider.GetService<IConfiguration>();
            }

            ConsulOptions option = configuration.GetSection("Consul").Get<ConsulOptions>();

            if (option.Enabled)
            {
                serviceCollection.AddSingleton<IConsulClient>(c => new ConsulClient(cfg =>
                {
                    //Consul主機地址
                    if (!string.IsNullOrEmpty(option.Host))
                    {
                        cfg.Address = new Uri(option.Host);
                    }
                }));
            }
        }

        public static void UseConsul(this IApplicationBuilder app, IApplicationLifetime lifetime)
        {
            using (var scope = app.ApplicationServices.CreateScope())
            {
                var configuration = scope.ServiceProvider.GetService<IConfiguration>();

                ConsulOptions option = configuration.GetSection("Consul").Get<ConsulOptions>();

                if (option.Enabled)
                {
                    Guid serviceId = Guid.NewGuid();
                    string consulServiceID = $"{ option.App.Name }:{serviceId}";

                    var client = scope.ServiceProvider.GetService<IConsulClient>();

                    //健康檢查
                    var httpCheck = new AgentServiceCheck()
                    {
                        DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服務啟動多久後註冊
                        Interval = TimeSpan.FromSeconds(10),//間隔固定的時間訪問一次
                        HTTP = $"{ option.App.Scheme }://{ option.App.Host }:{ option.App.Port }/api/Health/Check",//健康檢查地址
                        Timeout = TimeSpan.FromSeconds(5)
                    };

                    var consulServiceRistration = new AgentServiceRegistration
                    {
                        ID = consulServiceID,
                        Name = option.App.Name,
                        Address = $"{ option.App.Scheme }://{ option.App.Host }",
                        Port = option.App.Port,
                        Tags = option.App.Tags,
                        Checks = new[] { httpCheck }
                    };

                    client.Agent.ServiceRegister(consulServiceRistration);

                    lifetime.ApplicationStopping.Register(() =>
                    {
                        client.Agent.ServiceDeregister(consulServiceRistration.ID).Wait();
                    });
                }
            }
        }
    }

配置實體ConsulOptions.cs:

/// <summary>
    /// Consul配置專案
    /// </summary>
    public class ConsulOptions
    {
        /// <summary>
        /// 是否啟用
        /// </summary>
        public bool Enabled { get; set; }
        /// <summary>
        /// Consul主機地址
        /// </summary>
        public string Host { get; set; }
        /// <summary>
        /// 應用資訊
        /// </summary>
        public AppInfo App { get; set; }
    }

    public class AppInfo
    {
        /// <summary>
        /// 應用名稱
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 協議
        /// </summary>
        public string Scheme { get; set; }
        /// <summary>
        /// 應用主機地址
        /// </summary>
        public string Host { get; set; }
        /// <summary>
        /// 應用監聽埠
        /// </summary>
        public int Port { get; set; }
        /// <summary>
        /// 標籤
        /// </summary>
        public string[] Tags { get; set; }
    }

分別在ConfigureServices和Configure新增如下程式碼:

services.AddConsul();

app.UseConsul(lifetime);

appsetting.json新增如下配置專案:

"Consul": {
    //是否啟用
    "Enabled": true,
    //Consul主機地址
    "Host": "http://192.168.1.37:8500",
    "App": {
      //應用名稱
      "Name": "web-api",
      //協議
      "Scheme": "http",
      //應用主機地址
      "Host": "localhost",
      //應用監聽埠
      "Port": 10002,
      //標籤
      "Tags": [
        "web-api-node-1"
      ]
    }
  }

至此程式碼部分就完成了,接下來分別釋出你的閘道器、API服務到伺服器,訪問你的閘道器地址: