.NET 5 中使用 Consul+Ocelot+Polly緩存、限流、熔斷、降級(jí)

一、簡(jiǎn)介
Polly 是一個(gè) .NET 彈性和瞬態(tài)故障處理庫(kù),允許開(kāi)發(fā)人員以線程安全的方式來(lái)實(shí)現(xiàn)重試、斷路、超時(shí)、隔離和回退策略。
瞬態(tài)故障就是可能會(huì)出現(xiàn)的,比喻網(wǎng)絡(luò)不穩(wěn)定或無(wú)法訪問(wèn),或服務(wù)宕機(jī)。
二、Ocelot各種策略使用和解釋
下面各種策略都是基于前一篇Ocelot+Consul的配置基礎(chǔ)上修改。
2.1、Ocelot緩存
緩存能有效提升程序性能

在ocelot.json中增加緩存配置
??{
????"Routes":?[
??????{
????????//轉(zhuǎn)發(fā)到下游服務(wù)地址--url變量
????????"DownstreamPathTemplate":?"/api/{url}",
????????//下游http協(xié)議
????????"DownstreamScheme":?"http",
????????//負(fù)載方式,
????????"LoadBalancerOptions":?{
??????????"Type":?"RoundRobin"?//?輪詢
????????},
????????//上游地址
????????"UpstreamPathTemplate":?"/T1/{url}",?//網(wǎng)關(guān)地址--url變量???//沖突的還可以加權(quán)重Priority
????????"UpstreamHttpMethod":?[?"GET",?"POST",?"DELETE",?"PUT"?],
????????"UseServiceDisConvery":?true,?//使用服務(wù)發(fā)現(xiàn)
????????"ServiceName":?"api",?//Consul服務(wù)名稱
????????//緩存設(shè)置
????????"FileCacheOptions":?{
??????????"TtlSeconds":?10,?//緩存10s(同一個(gè)地址請(qǐng)求就返回緩存結(jié)果)
??????????"Region":?""//緩存region
????????}
??????}
????],
????"GlobalConfiguration":?{
??????//Ocelot應(yīng)用地址
??????"BaseUrl":?"http://172.16.2.9:5200",
??????"ServiceDiscoveryProvider":?{
????????//Consul地址
????????"Host":?"172.16.2.84",
????????//Consul端口
????????"Port":?8500,
????????"Type":?"Consul"//由Consul提供服務(wù)發(fā)現(xiàn),每次請(qǐng)求Consul
??????}
????}
??}
緩存是針對(duì)下游地址緩存的,同一個(gè)地址請(qǐng)求返回相同數(shù)據(jù),所以針對(duì)一些不變的數(shù)據(jù)才能做緩存,根據(jù)用戶登錄信息不同返回不同數(shù)據(jù)的就不能做了。
測(cè)試:訪問(wèn) http://172.16.2.9:5200/T1/Test/GetName

刷新后還是5201端口數(shù)據(jù),說(shuō)明是從緩存取的

10s后刷新端口變成5202

2.2、Ocelot限流
為什么要限流呢,防止請(qǐng)求過(guò)多把程序搞宕機(jī)了,也可以有效防止爬蟲和ddos攻擊,預(yù)估出服務(wù)的處理能力,然后設(shè)置限流,可以限制單位時(shí)間內(nèi)的訪問(wèn)量(失敗一部分請(qǐng)求比整個(gè)服務(wù)掛掉強(qiáng))。
ocelot.json文件增加限流配置
??{
????"Routes":?[
??????{
????????//轉(zhuǎn)發(fā)到下游服務(wù)地址--url變量
????????"DownstreamPathTemplate":?"/api/{url}",
????????//下游http協(xié)議
????????"DownstreamScheme":?"http",
????????//負(fù)載方式,
????????"LoadBalancerOptions":?{
??????????"Type":?"RoundRobin"?//?輪詢
????????},
????????//上游地址
????????"UpstreamPathTemplate":?"/T1/{url}",?//網(wǎng)關(guān)地址--url變量???//沖突的還可以加權(quán)重Priority
????????"UpstreamHttpMethod":?[?"GET",?"POST",?"DELETE",?"PUT"?],
????????"UseServiceDisConvery":?true,?//使用服務(wù)發(fā)現(xiàn)
????????"ServiceName":?"api",?//Consul服務(wù)名稱
????????//限流配置
????????"RateLimitOptions":?{
??????????"ClientWhitelist":?[?"admin"?],?//?白名單
??????????"EnableRateLimiting":?true,?//?是否啟用限流
??????????"Period":?"10s",?//?統(tǒng)計(jì)時(shí)間段:1s, 5m, 1h, 1d
??????????"PeriodTimespan":?10,?//?多少秒之后客戶端可以重試
??????????"Limit":?5?//?在統(tǒng)計(jì)時(shí)間段內(nèi)允許的最大請(qǐng)求數(shù)量
????????}
??????}
????],
????"GlobalConfiguration":?{
??????//Ocelot應(yīng)用地址
??????"BaseUrl":?"http://172.16.2.9:5200",
??????"ServiceDiscoveryProvider":?{
????????//Consul地址
????????"Host":?"172.16.2.84",
????????//Consul端口
????????"Port":?8500,
????????"Type":?"Consul"?//由Consul提供服務(wù)發(fā)現(xiàn),每次請(qǐng)求Consul
??????},
??????//限流
??????"RateLimitOptions":?{
????????"QuotaExceededMessage":?"errr:request?fast!",?//限流后響應(yīng)內(nèi)容
????????"HttpStatusCode":?666,?//http狀態(tài)碼可以自定義?????"ClientIdHeader":?"client_id"?//?用來(lái)識(shí)別客戶端的請(qǐng)求頭,默認(rèn)是?ClientId
??????}
????}
??}
這里用Postman來(lái)演示比較直觀。

可以看到,在10s內(nèi)請(qǐng)求了5次之后的請(qǐng)求就失敗了,返回的狀態(tài)碼是自定義的666,然后等10s過(guò)后又恢復(fù)訪問(wèn),上面設(shè)置的白名單在Headers加上就可以
不受限流影響,可以無(wú)限訪問(wèn)。

2.3、Ocelot+Polly的熔斷
安裝NuGet包 Ocelot.Provider.Polly。
Startup.cs增加 .AddPolly()
public?void?ConfigureServices(IServiceCollection?services)
{
???????????services.AddOcelot()
???????????????.AddConsul()
???????????????.AddPolly();
}
Ocelot.Provider.Polly的熔斷機(jī)制是一個(gè)超時(shí)和熔斷的組合,(Polly有超時(shí)策略,熔斷策略,這里是2個(gè)策略的結(jié)合使用,下面Polly策略會(huì)說(shuō)到),所以如果是單單是服務(wù)報(bào)500異常是觸發(fā)不了的。
接口超過(guò)多長(zhǎng)時(shí)間進(jìn)入半熔斷狀態(tài),返回服務(wù)不可用, 連續(xù)超過(guò)多少次進(jìn)入熔斷狀態(tài)就直接停掉該請(qǐng)求返回,多長(zhǎng)時(shí)間再恢復(fù)。
修改ocelot.json配置
??//*****************************單地址********************************
??{
????"Routes":?[
??????{
????????//轉(zhuǎn)發(fā)到下游服務(wù)地址--url變量
????????"DownstreamPathTemplate":?"/api/{url}",
????????//下游http協(xié)議
????????"DownstreamScheme":?"http",
????????//負(fù)載方式,
????????"LoadBalancerOptions":?{
??????????"Type":?"RoundRobin"?//?輪詢
????????},
????????//上游地址
????????"UpstreamPathTemplate":?"/T1/{url}",?//網(wǎng)關(guān)地址--url變量???//沖突的還可以加權(quán)重Priority
????????"UpstreamHttpMethod":?[?"GET",?"POST",?"DELETE",?"PUT"?],
????????"UseServiceDisConvery":?true,?//使用服務(wù)發(fā)現(xiàn)
????????"ServiceName":?"api",?//Consul服務(wù)名稱
????????//熔斷設(shè)置
????????"QoSOptions":?{
??????????"ExceptionsAllowedBeforeBreaking":?3,?//允許多少個(gè)異常請(qǐng)求
??????????"DurationOfBreak":?5000,?//?熔斷的時(shí)間5s,單位為ms
??????????"TimeoutValue":?5000?//單位ms,如果下游請(qǐng)求的處理時(shí)間超過(guò)多少則自如將請(qǐng)求設(shè)置為超時(shí)?默認(rèn)90秒
????????}
??????}
????],
????"GlobalConfiguration":?{
??????//Ocelot應(yīng)用對(duì)外地址
??????"BaseUrl":?"http://172.16.2.9:5200",
??????"ServiceDiscoveryProvider":?{
????????//Consul地址
????????"Host":?"172.16.2.84",
????????//Consul端口
????????"Port":?8500,
????????"Type":?"Consul"?//由Consul提供服務(wù)發(fā)現(xiàn),每次請(qǐng)求Consul
??????}
????}
??}
在之前啟動(dòng)的3個(gè)服務(wù)增加一個(gè)拋異常的接口和一個(gè)睡眠接口。
?[Route("api/[controller]/[action]")]
?public?class?TestController?:?Controller
?{
?????private?IConfiguration?_configuration;
?????public?TestController(IConfiguration?configuration)
?????{
?????????_configuration?=?configuration;
?????}
?????public?IActionResult?GetName()
?????{
?????????string?port?=?_configuration["port"];
?????????return?Json($"端口:{port},姓名:張三");
?????}public?IActionResult?GetSleep()
?????{
?????????string?port?=?_configuration["port"];
?????????//線程睡眠6s
?????????Thread.Sleep(6000);
?????????return?Json($"端口:{port},睡眠6s后返回");
?????}
?}
訪問(wèn)GetSleep()接口,前三次等待6s返回503,后面訪問(wèn)什么接口都是快速返回503,服務(wù)熔斷。

三、Polly各種策略使用和解釋
上面網(wǎng)關(guān)處做了Ocelot+Polly的熔斷策略,然后服務(wù)鏈上也是需要做一些策略,這里介紹的是在服務(wù)里用Polly做各種常用的策略。
3.1、Polly降級(jí)
降級(jí)就是當(dāng)我們指定的代碼處理失敗時(shí)就執(zhí)行我們備用的代碼。
現(xiàn)在在之前的三個(gè)服務(wù)中加入Polly降級(jí)策略
安裝NuGet包 --Polly
新建一個(gè)OrderController.cs
????[Route("api/[controller]/[action]")]
????public?class?OrderController?:?Controller
????{
????????private?readonly?OrderService?_orderService;
????????public?OrderController(OrderService?orderService)
????????{
????????????_orderService?=?orderService;
????????}
????????public?IActionResult?CreateOrder()
????????{
????????????string?result?=?_orderService.CreateOrder();
????????????return?Content(result);
????????}
????}
新建一個(gè)OrderService.cs
??public?class?OrderService
??{
??????private?Policy<string>?_policy;
??????public?OrderService()
??????{
??????????//降級(jí)
??????????_policy?=?Policy<string>
??????????????.Handle()?//異常故障
??????????????.Fallback(()?=>
??????????????{
??????????????????//降級(jí)回調(diào)?todo降級(jí)后邏輯
??????????????????return?"降級(jí)后的值";
??????????????});
??????}
??????public?string?CreateOrder()
??????{
??????????//用polly執(zhí)行
??????????return?_policy.Execute(()?=>
??????????{
??????????????//業(yè)務(wù)邏輯?todo
??????????????Console.WriteLine($"{DateTime.Now},開(kāi)始處理業(yè)務(wù)");
??????????????throw?new?Exception("233出錯(cuò)啦");
??????????????Console.WriteLine("處理完成");
??????????????return?"成功啦";
??????????});
??????}
??}
把OrderService注入IOC容器,注意,使用Polly時(shí),實(shí)例一定要以單例模式注入,因?yàn)槿绻敲看味紙?zhí)行構(gòu)造函數(shù)給_policy賦一次值,那_policy每次都是全新的,下面的熔斷策畋會(huì)不生效。
Startup.cs中的ConfigureServices()加上
?public?void?ConfigureServices(IServiceCollection?services)
?{
??????services.AddControllersWithViews();
??????services.AddControllers().AddJsonOptions(cfg?=>
??????{
??????services.AddSingleton();
?}
測(cè)試,訪問(wèn)http://ip:端口/api/Order/CreateOrder

可以看到返回的是降級(jí)后處理的值。
3.2、Polly熔斷
熔斷就是當(dāng)一處代碼報(bào)錯(cuò)超過(guò)多少次,就讓它熔斷多長(zhǎng)時(shí)間再恢復(fù),熔斷時(shí)Polly會(huì)截?cái)嗾?qǐng)求,不會(huì)再進(jìn)入到具體業(yè)務(wù),這能有效減少?zèng)]必要的業(yè)務(wù)性能損耗。
把OrderService構(gòu)建函數(shù)處改成
public?OrderService()
{
?????//熔斷
?????_policy?=?Policy<string>.Handle()
?????.CircuitBreaker(5,?TimeSpan.FromSeconds(10));//連續(xù)出錯(cuò)5次后熔斷10秒,不會(huì)在進(jìn)到業(yè)務(wù)代碼
}
測(cè)試結(jié)果:
5次前的返回:

熔斷后返回:

3.3、Polly重試
把OrderService的構(gòu)造函數(shù)處修改為:
??public?OrderService()
??{
??????//重試
??????//RetryForever()是一直重試直到成功
??????//Retry()是重試最多一次;
??????//Retry(n)?是重試最多n次;
??????//WaitAndRetry()可以實(shí)現(xiàn)“如果出錯(cuò)等待100ms再試還不行再等150ms秒。
???????_policy?=?Policy<string>.Handle()
?????????????.WaitAndRetry(new?TimeSpan[]?{?TimeSpan.FromSeconds(5),?TimeSpan.FromSeconds(10),?TimeSpan.FromSeconds(15)?});
??}
這里是業(yè)務(wù)處理失敗時(shí),重試3次,分別隔5s,10s,15s。
測(cè)試結(jié)果:

可以看到,第一次執(zhí)行因?yàn)橛挟惓?,然后分別隔5s,10s,15s重試,最后才拋出了異常。
3.4、Polly超時(shí)
所謂超時(shí),就是我們指定一段代碼的最大運(yùn)行時(shí)間,如果超過(guò)這段時(shí)間還沒(méi)有完成,就直接拋出異常。
這里判斷超時(shí)有兩種策略:一個(gè)是悲觀策略(Pessimistic),一個(gè)是樂(lè)觀策略(Optimistic)。一般我們用悲觀策略。需要注意的是,雖然超時(shí)拋除了異常,但這段代碼的運(yùn)行并沒(méi)有停止!
把OrderService構(gòu)建函數(shù)處改成
??public?OrderService()
??{
????????//超時(shí),業(yè)務(wù)處理超過(guò)3秒就直接返回異常
????????_policy?=?Policy.Timeout<string>(3,?Polly.Timeout.TimeoutStrategy.Pessimistic);
??}
把OrderService.cs的CreateOrder()方法讓線程睡眠10s
?public?string?CreateOrder()
?{
??????//用polly執(zhí)行
??????return?_policy.Execute(()?=>
??????{
??????????//業(yè)務(wù)邏輯?todo
??????????Console.WriteLine($"{DateTime.Now},開(kāi)始處理業(yè)務(wù)");
??????????Thread.Sleep(10000);?//睡眠10s
??????????Console.WriteLine("處理完成");
??????????return?"成功啦";
??????});
??}
執(zhí)行查看結(jié)果:


可以看到,在3s的時(shí)候報(bào)了polly的超時(shí)異常。
3.5、Polly組合策略
上面說(shuō)的都是單個(gè)策略的,其實(shí)這些策略是可以組合一起使用的,下面來(lái)演示一下。
把OrderService的構(gòu)造函數(shù)修改為:
?public?OrderService()
?{
?????//重試
?????Policy<string>?retry?=?Policy<string>.Handle()
???????.WaitAndRetry(new?TimeSpan[]?{?TimeSpan.FromSeconds(5),?TimeSpan.FromSeconds(10),?TimeSpan.FromSeconds(15)?});
?????//降級(jí)
?????Policy<string>?fallback?=?Policy<string>
??????????.Handle()?//異常故障
??????????.Fallback(()?=>
??????????{
?????????????//降級(jí)回調(diào)
?????????????return?"降級(jí)后的值";
??????????});
?????//Wrap:包裹。policyRetry在里面,policyFallback裹在外面。
?????//如果里面出現(xiàn)了故障,則把故障拋出來(lái)給外面
?????//_policy=Policy.Wrap(policy1, policy2, policy3, policy4, policy5);把更多一起封裝。
?????_policy?=?Policy.Wrap(fallback,?retry);?//?fallback.Wrap(retry);
?}
把CreateOrder()修改為
?public?string?CreateOrder()
?{
?????//用polly執(zhí)行
?????return?_policy.Execute(()?=>
?????{
?????????//業(yè)務(wù)邏輯?todo
?????????Console.WriteLine($"{DateTime.Now},開(kāi)始處理業(yè)務(wù)");
?????????throw?new?Exception("233出錯(cuò)啦");
?????????Console.WriteLine("處理完成");
?????????return?"成功啦";
?????});
?}
測(cè)試結(jié)果:

重試3次后,返回降級(jí)的結(jié)果。
上面的Ocelot+Polly的熔斷如果去查看Ocelot.Provider.Polly的源碼就會(huì)發(fā)現(xiàn)是超時(shí)和熔斷的組合實(shí)現(xiàn)。
需要注意的是,組合時(shí)Policy.Wrap(fallback, retry);里面的順序也要注意,測(cè)試結(jié)果是先執(zhí)行后面的,再執(zhí)行前面的,即前面的把后面的包在內(nèi)層,內(nèi)層執(zhí)行完再拋出給外層處理。
上面介紹了最常用的策略,Polly也有異步的方法,把上面定義的private Policy
轉(zhuǎn)自:包子wxl
鏈接:cnblogs.com/wei325/archive/2021/09/27/15308498.html
