一、簡介
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.UseCors(_defaultCorsPolicyName);
app.UseLogDashboard();
app.UseOcelot().Wait();
}
網關IdentityServer4鑒權只需要加如下配置即可:
/** * IdentityServer4鑒權: * 1、安裝IdentityServer4.AccessTokenValidation NuGet包 * 2、在Routes下加配置文件: * "AuthenticationOptions": { "AuthenticationProviderKey": "AuthKey", "AllowedScopes": [] } 3、注冊中間件: services.AddAuthentication() .AddIdentityServerAuthentication("AuthKey", options => { options.Authority = "http://localhost:7889"; options.RequireHttpsMetadata = false; options.ApiName = "api"; options.SupportedTokens = SupportedTokens.Both; options.ApiSecret = "secret"; }); */
新建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": { "RequestIdKey": "OcelotRequestId", //Consul服務發現 "ServiceDiscoveryProvider": { "Scheme": "http", "Host": "192.168.1.205", "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.Host,//注意:這個地方不能帶Schema,否則網關會找不到服務;網關配置文件里面需要配置DownstreamScheme 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服務到服務器,訪問你的網關地址:
可能會遇到的跨域問題解決:
/** * 解決PUT和DELETE請求跨域問題(https://brockallen.com/2012/10/18/cors-iis-and-webdav/): * WebDAV 是超文本傳輸協議 (HTTP) 的一組擴展,為 Internet 上計算機之間的編輯和文件管理提供了標准. * 利用這個協議用戶可以通過Web進行遠程的基本文件操作,如拷貝、移動、刪除等。 * 在IIS 7.0中,WebDAV是作為獨立擴展模塊,需要單獨進行下載,而IIS 7.5中將集成WebDAV, * 然而WebDav把Put,Delete給移除了, * 所以在IIS 7.5上部署的RESTful服務(WCF Data Service,WCF Rest Service,ASP.NET Web API,ASP.Net MVC)就悲劇了, * 當發送Put請求就會發生HTTP Error 405.0 – Method Not Allowed錯誤,解決方法也很簡單,在Web.config里面加入如下設置: * * <system.webServer> <modules> <remove name="WebDAVModule" /> </modules> <handlers> <remove name="WebDAV" /> </handlers> </system.webServer> */