API 網關通常放到微服務的最前端,而且要讓API 網關變成由應用所發起的每一個請求的入口。這樣就能夠明顯的簡化客戶端實現和微服務應用程序之間的溝通方式。之前的話,客戶端不得不去請求微服務A,而後再到微服務B,而後是微服務C。客戶端須要去知道怎麼去一塊兒來消費這三個不一樣的service。使用API網關,咱們能夠抽象全部這些複雜性,並建立客戶端們可使用的優化後的端點,並向那些模塊們發出請求。API網關的核心要點是:全部的客戶端和消費端都經過統一的網關接入微服務,在網關層處理全部的非業務功能(好比驗證、鑑權、監控、限流、請求合併...)前端
Ocelot是一個使用.NET Core平臺上的一個API Gateway,這個項目的目標是在.NET上面運行微服務架構。它功能強大,包括了:路由、請求聚合、服務發現、認證、鑑權、限流熔斷、並內置了負載均衡器與Service Fabric、Butterfly Tracing集成,還引入了Polly來進行故障處理。node
Polly是一種.NET彈性和瞬態故障處理庫,容許咱們以很是順暢和線程安全的方式來執諸如行重試,斷路,超時,故障恢復等策略。json
重試策略(Retry)
重試策略針對的前置條件是短暫的故障延遲且在短暫的延遲以後可以自我糾正。容許咱們作的是可以自動配置重試機制。bootstrap
斷路器(Circuit-breaker)
斷路器策略針對的前置條件是當系統繁忙時,快速響應失敗總比讓用戶一直等待更好。保護系統故障免受過載,Polly能夠幫其恢復。api
超時(Timeout)
超時策略針對的前置條件是超過必定的等待時間,想要獲得成功的結果是不可能的,保證調用者沒必要等待超時。緩存
隔板隔離(Bulkhead Isolation)安全
隔板隔離針對的前置條件是當進程出現故障時,多個失敗一直在主機中對資源(例如線程/ CPU)一直佔用。下游系統故障也可能致使上游失敗。這兩個風險都將形成嚴重的後果。都說一粒老鼠子屎攪渾一鍋粥,而Polly則將受管制的操做限制在固定的資源池中,免其餘資源受其影響。架構
緩存(Cache)
緩存策略針對的前置條件是數據不會很頻繁的進行更新,爲了不繫統過載,首次加載數據時將響應數據進行緩存,若是緩存中存在則直接從緩存中讀取。app
回退(Fallback)
操做仍然會失敗,也就是說當發生這樣的事情時咱們打算作什麼。也就是說定義失敗返回操做。負載均衡
策略包裝(PolicyWrap)
策略包裝針對的前置條件是不一樣的故障須要不一樣的策略,也就意味着彈性靈活使用組合
Consul是HashiCorp公司推出的開源工具,用於實現分佈式系統的服務發現與配置,內置了服務註冊與發現框架、分佈一致性協議實現、健康檢查、Key/Value存儲、多數據中心方案、在Ocelot已經支持簡單的負載功能,也就是當下遊服務存在多個結點的時候,Ocelot可以承擔起負載均衡的做用。可是它不提供健康檢查,服務的註冊也只能經過手動在配置文件裏面添加完成。這不夠靈活而且在必定程度下會有風險。這個時候咱們就能夠用Consul來作服務發現並實現健康檢查。
文件結構信息
data
conf
consul.exe
配置conf.json信息 並運行 :consul agent -config-dir="E:/PersonCode/.Net Core/consul/conf/conf.json"
{
"datacenter": "wf",
"data_dir": "E:/PersonCode/.Net Core/consul/data",
"log_level": "INFO",
"server": true,
"ui": true,
"bind_addr": "192.168.14.8",
"client_addr": "127.0.0.1",
"advertise_addr": "192.168.14.8",
"bootstrap_expect": 1,
"ports":{
"http": 8500,
"dns": 8600,
"server": 8300,
"serf_lan": 8301,
"serf_wan": 8302
}
}
運行結果:
一、建立 api項目 添加swagger Ui
二、引用Consul-1.6.1.1版本
三、添加Consul 服務配置
"Consul": {
"ServiceName": "Zfkr.WF.Core.API",
"ServiceIP": "localhost",
"ConsulClientUrl": "http://localhost:8500",
"HealthCheckRelativeUrl": "/wf/Base/health",
"HealthCheckIntervalInSecond": 5
}
四、添加Consul 服務註冊類(RegisterCansulExtension)
五、添加服務註冊與中間件
六、啓用api 多個服務
dotnet Zfkr.WF.Core.API.dll --urls=http://*:8001
dotnet Zfkr.WF.Core.API.dll --urls=http://*:8002
dotnet Zfkr.WF.Core.API.dll --urls=http://*:8003
public static class RegisterCansulExtension
{
public static void RegisterToConsul(this IApplicationBuilder app, IConfiguration configuration, IHostApplicationLifetime lifetime)
{
lifetime.ApplicationStarted.Register(() =>
{
string serviceName = configuration.GetValue<string>("Consul:ServiceName");
string serviceIP = configuration.GetValue<string>("Consul:ServiceIP");
string consulClientUrl = configuration.GetValue<string>("Consul:ConsulClientUrl");
string healthCheckRelativeUrl = configuration.GetValue<string>("Consul:HealthCheckRelativeUrl");
int healthCheckIntervalInSecond = configuration.GetValue<int>("Consul:HealthCheckIntervalInSecond");
ICollection<string> listenUrls = app.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
if (string.IsNullOrWhiteSpace(serviceName))
{
throw new Exception("Please use --serviceName=yourServiceName to set serviceName");
}
if (string.IsNullOrEmpty(consulClientUrl))
{
consulClientUrl = "http://127.0.0.1:8500";
}
if (string.IsNullOrWhiteSpace(healthCheckRelativeUrl))
{
healthCheckRelativeUrl = "health";
}
healthCheckRelativeUrl = healthCheckRelativeUrl.TrimStart('/');
if (healthCheckIntervalInSecond <= 0)
{
healthCheckIntervalInSecond = 1;
}
string protocol;
int servicePort = 0;
if (!TryGetServiceUrl(listenUrls, out protocol, ref serviceIP, out servicePort, out var errorMsg))
{
throw new Exception(errorMsg);
}
var consulClient = new ConsulClient(ConsulClientConfiguration => ConsulClientConfiguration.Address = new Uri(consulClientUrl));
var httpCheck = new AgentServiceCheck()
{
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(10),//服務啓動多久後註冊
Interval = TimeSpan.FromSeconds(healthCheckIntervalInSecond),
HTTP = $"{protocol}://{serviceIP}:{servicePort}/{healthCheckRelativeUrl}",
Timeout = TimeSpan.FromSeconds(2)
};
// 生成註冊請求
var registration = new AgentServiceRegistration()
{
Checks = new[] { httpCheck },
ID = Guid.NewGuid().ToString(),
Name = serviceName,
Address = serviceIP,
Port = servicePort,
Meta = new Dictionary<string, string>() { ["Protocol"] = protocol },
Tags = new[] { $"{protocol}" }
};
consulClient.Agent.ServiceRegister(registration).Wait();
//服務中止時, 主動發出註銷
lifetime.ApplicationStopping.Register(() =>
{
try
{
consulClient.Agent.ServiceDeregister(registration.ID).Wait();
}
catch
{ }
});
});
}
private static bool TryGetServiceUrl(ICollection<string> listenUrls, out string protocol, ref string serviceIP, out int port, out string errorMsg)
{
protocol = null;
port = 0;
errorMsg = null;
if (!string.IsNullOrWhiteSpace(serviceIP)) // 若是提供了對外服務的IP, 只須要檢測是否在listenUrls裏面便可
{
foreach (var listenUrl in listenUrls)
{
Uri uri = new Uri(listenUrl);
protocol = uri.Scheme;
var ipAddress = uri.Host;
port = uri.Port;
if (ipAddress == serviceIP || ipAddress == "0.0.0.0" || ipAddress == "[::]")
{
return true;
}
}
errorMsg = $"The serviceIP that you provide is not in urls={string.Join(',', listenUrls)}";
return false;
}
else // 沒有提供對外服務的IP, 須要查找本機全部的可用IP, 看看有沒有在 listenUrls 裏面的
{
var allIPAddressOfCurrentMachine = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()
.Select(p => p.GetIPProperties())
.SelectMany(p => p.UnicastAddresses)
// 這裏排除了 127.0.0.1 loopback 地址
.Where(p => p.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && !System.Net.IPAddress.IsLoopback(p.Address))
.Select(p => p.Address.ToString()).ToArray();
var uris = listenUrls.Select(listenUrl => new Uri(listenUrl)).ToArray();
// 本機全部可用IP與listenUrls進行匹配, 若是listenUrl是"0.0.0.0"或"[::]", 則任意IP都符合匹配
var matches = allIPAddressOfCurrentMachine.SelectMany(ip =>
uris.Where(uri => ip == uri.Host || uri.Host == "0.0.0.0" || uri.Host == "[::]")
.Select(uri => new { Protocol = uri.Scheme, ServiceIP = ip, Port = uri.Port })
).ToList();
if (matches.Count == 0)
{
errorMsg = $"This machine has IP address=[{string.Join(',', allIPAddressOfCurrentMachine)}], urls={string.Join(',', listenUrls)}, none match.";
return false;
}
else if (matches.Count == 1)
{
protocol = matches[0].Protocol;
serviceIP = matches[0].ServiceIP;
port = matches[0].Port;
return true;
}
else
{
errorMsg = $"Please use --serviceIP=yourChosenIP to specify one IP, which one provide service: {string.Join(",", matches)}.";
return false;
}
}
}
一、建立.core Api 項目 並引用相關包文件
Consul 1.6.11
Ocelot 16.0.1
Ocelot.Provider.Consul 16.0.1
二、添加ocelot.json文件 並配置
注意:"ServiceName": "Zfkr.WF.Core.API", 服務名稱必須與第三步中的 註冊服務時的 服務名稱一致,若未註冊服務 可配置DownstreamHostAndPorts實現負載,之因此要使用Consul 是由於Ocelot自身 沒有沒法 實現健康檢查 服務自動添加與移除
{
"Routes": [
/*Swagger 網關*/
{
"DownstreamPathTemplate": "/swagger/WorkFlow/swagger.json",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 8001
}
],
"LoadBalancer": "RoundRobin",
"UpstreamPathTemplate": "/WorkFlow/swagger/WorkFlow/swagger.json",
"UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ]
},
//{
// "UseServiceDiscovery": true, // 使用服務發現
// "DownstreamPathTemplate": "/swagger/TaskSchedul/swagger.json",
// "DownstreamScheme": "http",
// "DownstreamHostAndPorts": [
// {
// "Host": "localhost",
// "Port": 8002
// }
// ],
// "LoadBalancer": "RoundRobin",
// "UpstreamPathTemplate": "/TaskSchedul/swagger/TaskSchedul/swagger.json",
// "UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ]
//},
/*Api網關*/
{
"UseServiceDiscovery": true, //啓用服務發現,若Ocelot集合Consul必須配置此項
"DownstreamPathTemplate": "/WF/{url}",
"DownstreamScheme": "http",
//"DownstreamHostAndPorts": [
// {
// "Host": "localhost",
// "Port": 8001
// },
// {
// "Host": "localhost",
// "Port": 8003
// }
//],
"UpstreamPathTemplate": "/WF/{url}",
"UpstreamHttpMethod": [ "Get", "Post" ],
"ServiceName": "Zfkr.WF.Core.API", //服務名稱
"LoadBalancerOptions": {
"Type": "RoundRobin"
}
}
//,
//{
// "DownstreamPathTemplate": "/TS/{url}",
// "DownstreamScheme": "http",
// "DownstreamHostAndPorts": [
// {
// "Host": "localhost",
// "Port": 8002
// }
// ],
// "ServiceName": "node-2", // 服務名稱
// "UseServiceDiscovery": true,
// "UpstreamPathTemplate": "/TS/{url}",
// "UpstreamHttpMethod": [ "Get", "Post" ],
// "LoadBalancerOptions": {
// "Type": "LeastConnection"
// }
//}
],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:8899", //網關對外地址
"ReRouteIsCaseSensitive": false, //是否區分路由字母大小寫
"ServiceDiscoveryProvider": { //服務發現提供者,配置Consul地址
"Host": "localhost", //Consul主機名稱
"Port": 8500, //Consul端口號
"Type": "Consul" //必須指定Consul服務發現類型
}
//,
//"限流相關配置"
//"RateLimitOptions": {
// "ClientIdHeader": "ClientId",
// "QuotaExceededMessage": "RateLimit SCscHero", //限流響應提示
// "RateLimitCounterPrefix": "ocelot",
// "DisableRateLimitHeaders": false,
// "HttpStatusCode": 429
//}
}
}
三、添加Consul註冊類 並註冊到中間件,添加健康檢查服務
3.一、添加appsettings 配置信息
"Consul": {
"ServiceName": "Gateway-9988-Service",
"Datacenter": "wf",
"ServiceIP": "localhost",
"ConsulClientUrl": "http://localhost:8500",
"HealthCheckRelativeUrl": "/wf/Base/health",
"HealthCheckIntervalInSecond": 5
}
3.二、添加Consul註冊類
public static class RegisterCansulExtension
{
public static void RegisterToConsul(this IApplicationBuilder app, IConfiguration configuration, IHostApplicationLifetime lifetime)
{
lifetime.ApplicationStarted.Register(() =>
{
string serviceName = configuration.GetValue<string>("Consul:ServiceName");
string serviceIP = configuration.GetValue<string>("Consul:ServiceIP");
string consulClientUrl = configuration.GetValue<string>("Consul:ConsulClientUrl");
string healthCheckRelativeUrl = configuration.GetValue<string>("Consul:HealthCheckRelativeUrl");
int healthCheckIntervalInSecond = configuration.GetValue<int>("Consul:HealthCheckIntervalInSecond");
ICollection<string> listenUrls = app.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
if (string.IsNullOrWhiteSpace(serviceName))
{
throw new Exception("Please use --serviceName=yourServiceName to set serviceName");
}
if (string.IsNullOrEmpty(consulClientUrl))
{
consulClientUrl = "http://127.0.0.1:8500";
}
if (string.IsNullOrWhiteSpace(healthCheckRelativeUrl))
{
healthCheckRelativeUrl = "health";
}
healthCheckRelativeUrl = healthCheckRelativeUrl.TrimStart('/');
if (healthCheckIntervalInSecond <= 0)
{
healthCheckIntervalInSecond = 1;
}
string protocol;
int servicePort = 0;
if (!TryGetServiceUrl(listenUrls, out protocol, ref serviceIP, out servicePort, out var errorMsg))
{
throw new Exception(errorMsg);
}
var consulClient = new ConsulClient(ConsulClientConfiguration => ConsulClientConfiguration.Address = new Uri(consulClientUrl));
var httpCheck = new AgentServiceCheck()
{
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(10),//服務啓動多久後註冊
Interval = TimeSpan.FromSeconds(healthCheckIntervalInSecond),
HTTP = $"{protocol}://{serviceIP}:{servicePort}/{healthCheckRelativeUrl}",
Timeout = TimeSpan.FromSeconds(2)
};
// 生成註冊請求
var registration = new AgentServiceRegistration()
{
Checks = new[] { httpCheck },
ID = Guid.NewGuid().ToString(),
Name = serviceName,
Address = serviceIP,
Port = servicePort,
Meta = new Dictionary<string, string>() { ["Protocol"] = protocol },
Tags = new[] { $"{protocol}" }
};
consulClient.Agent.ServiceRegister(registration).Wait();
//服務中止時, 主動發出註銷
lifetime.ApplicationStopping.Register(() =>
{
try
{
consulClient.Agent.ServiceDeregister(registration.ID).Wait();
}
catch
{ }
});
});
}
private static bool TryGetServiceUrl(ICollection<string> listenUrls, out string protocol, ref string serviceIP, out int port, out string errorMsg)
{
protocol = null;
port = 0;
errorMsg = null;
if (!string.IsNullOrWhiteSpace(serviceIP)) // 若是提供了對外服務的IP, 只須要檢測是否在listenUrls裏面便可
{
foreach (var listenUrl in listenUrls)
{
Uri uri = new Uri(listenUrl);
protocol = uri.Scheme;
var ipAddress = uri.Host;
port = uri.Port;
if (ipAddress == serviceIP || ipAddress == "0.0.0.0" || ipAddress == "[::]")
{
return true;
}
}
errorMsg = $"The serviceIP that you provide is not in urls={string.Join(',', listenUrls)}";
return false;
}
else // 沒有提供對外服務的IP, 須要查找本機全部的可用IP, 看看有沒有在 listenUrls 裏面的
{
var allIPAddressOfCurrentMachine = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()
.Select(p => p.GetIPProperties())
.SelectMany(p => p.UnicastAddresses)
// 這裏排除了 127.0.0.1 loopback 地址
.Where(p => p.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && !System.Net.IPAddress.IsLoopback(p.Address))
.Select(p => p.Address.ToString()).ToArray();
var uris = listenUrls.Select(listenUrl => new Uri(listenUrl)).ToArray();
// 本機全部可用IP與listenUrls進行匹配, 若是listenUrl是"0.0.0.0"或"[::]", 則任意IP都符合匹配
var matches = allIPAddressOfCurrentMachine.SelectMany(ip =>
uris.Where(uri => ip == uri.Host || uri.Host == "0.0.0.0" || uri.Host == "[::]")
.Select(uri => new { Protocol = uri.Scheme, ServiceIP = ip, Port = uri.Port })
).ToList();
if (matches.Count == 0)
{
errorMsg = $"This machine has IP address=[{string.Join(',', allIPAddressOfCurrentMachine)}], urls={string.Join(',', listenUrls)}, none match.";
return false;
}
else if (matches.Count == 1)
{
protocol = matches[0].Protocol;
serviceIP = matches[0].ServiceIP;
port = matches[0].Port;
return true;
}
else
{
errorMsg = $"Please use --serviceIP=yourChosenIP to specify one IP, which one provide service: {string.Join(",", matches)}.";
return false;
}
}
}
}
四、運行網關服務 dotnet Zfkr.WF.Core.Gateway.dll --urls=http://*:9988