.Net Core with 微服务 - Polly 服务降级熔断
在我們實(shí)施微服務(wù)之后,服務(wù)間的調(diào)用變的異常頻繁。多個(gè)服務(wù)之間可能是互相依賴的關(guān)系。某個(gè)服務(wù)出現(xiàn)故障或者是服務(wù)間的網(wǎng)絡(luò)出現(xiàn)故障都會(huì)造成服務(wù)調(diào)用的失敗,進(jìn)而影響到某個(gè)業(yè)務(wù)服務(wù)處理失敗。某一個(gè)服務(wù)調(diào)用失敗輕則造成當(dāng)前相關(guān)業(yè)務(wù)無法處理;重則可能耗盡資源而拉垮整個(gè)應(yīng)用。為了盡可能的保證我們生產(chǎn)環(huán)境的可用性,至少是部分可用性我們就需要一些策略來保護(hù)我們的服務(wù)。
服務(wù)降級
比如我們的訂單詳情服務(wù)里面會(huì)調(diào)用會(huì)員信息服務(wù)接口。如果會(huì)員信息服務(wù)接口故障會(huì)造成訂單詳情服務(wù)也同樣故障。這時(shí)候我們可以對會(huì)員信息服務(wù)接口進(jìn)行降級,在發(fā)生故障的時(shí)候直接返回固定的信息從而保證訂單詳情主服務(wù)是可用的。
另外一種情況是服務(wù)器的資源總是有限的,在面對突發(fā)的高并發(fā),高流量情況下我們也可以對部分服務(wù)進(jìn)行降級處理,從而釋放更多的資源給核心服務(wù),從而保證核心業(yè)務(wù)正常工作。
熔斷
我們的服務(wù)很可能是一個(gè)鏈?zhǔn)降恼{(diào)用的過程。期間如果某個(gè)服務(wù)出現(xiàn)故障,特別是出現(xiàn)超時(shí)故障的時(shí)候很有可能耗盡服務(wù)器的資源從而影響整個(gè)服務(wù)。比如訂單詳情服務(wù)依賴會(huì)員信息服務(wù),如果會(huì)員信息服務(wù)因?yàn)槟承┰虺霈F(xiàn)處理過慢、異常等情況,會(huì)阻塞整個(gè)訂單詳情服務(wù)的鏈路。而可能其它服務(wù)同樣依賴訂單詳情服務(wù),這樣其它服務(wù)同樣也會(huì)被阻塞。資源被越來越多的消耗而不釋放,造成所有服務(wù)處理越來越慢,積壓的請求越來越多, 猶如死循環(huán)一般,直到所有資源都被耗盡,整個(gè)生成環(huán)境奔潰。
所以面對這種情況當(dāng)我們某個(gè)服務(wù)持續(xù)出現(xiàn)故障的時(shí)候我們可以直接斷開對它的調(diào)用依賴,從而保證不會(huì)因?yàn)檎埱蠓e壓造成資源耗盡的情況發(fā)生。
Polly
Polly 是一個(gè)開源的彈性跟瞬態(tài)故障處理類庫。它可以在你的程序出現(xiàn)故障,超時(shí),或者返回值達(dá)成某種條件的時(shí)候進(jìn)行多種策略處理,比如重試、降級、熔斷等等。它是 .NET Foundation 的成員項(xiàng)目。
Policy.Handle< T >
Policy.Handle< T > 用來定義異常的類型,表示當(dāng)執(zhí)行的方法發(fā)生某種異常的時(shí)候定義為故障。
當(dāng)故障發(fā)生的時(shí)候 Polly 會(huì)為我們自動(dòng)執(zhí)行某種恢復(fù)策略,比如重試。
我們演示項(xiàng)目中,訂單接口需要獲取會(huì)員的詳細(xì)信息。
http 有一定幾率失敗,下面我們演示下如果使用 Polly 在出現(xiàn)當(dāng)請求網(wǎng)絡(luò)失敗的時(shí)候進(jìn)行3次重試。
使用 Policy.Handle< HttpRequestException > 來捕獲網(wǎng)絡(luò)異常。當(dāng)發(fā)生 HttpRequestException 的時(shí)候觸發(fā) RetryAsync 重試,并且最多重試3次。以下我們接著演示下當(dāng) http 的返回值是500的時(shí)候進(jìn)行3次重試:
Policy.HandleResult< T>
Policy.HandleResult< T > 用來定義返回值的類型,表示當(dāng)執(zhí)行的方法返回值達(dá)成某種條件的時(shí)候定義為故障。
當(dāng)故障發(fā)生的時(shí)候 Polly 會(huì)為我們自動(dòng)執(zhí)行某種恢復(fù)策略,比如重試。
下面我們演示下如何使用 Polly 在出現(xiàn)當(dāng)請求結(jié)果為 http status_code 500 的時(shí)候進(jìn)行3次重試。
Policy.TimeoutAsync
Policy.TimeoutAsync 表示當(dāng)一個(gè)操作超過設(shè)定時(shí)間時(shí)會(huì)引發(fā)一個(gè) TimeoutRejectedException 。
這也是一個(gè)很常用的故障處理策略。
以上代碼表示當(dāng)獲取會(huì)員詳情的接口超過10秒還未返回結(jié)果的時(shí)候直接拋出一個(gè) TimeoutRejectedException 異常終止執(zhí)行。
服務(wù)降級
以上我們演示了出現(xiàn)故障的時(shí)候如何進(jìn)行重試,但是所有重試都失敗我們的程序還是會(huì)故障。
因?yàn)槠陂g某個(gè)服務(wù)持續(xù)的故障導(dǎo)致更多的服務(wù)出現(xiàn)故障,一系列連鎖反應(yīng)后很可能導(dǎo)致整個(gè)應(yīng)用癱瘓。
面對這種情況我們可以把相關(guān)服務(wù)進(jìn)行降級。
當(dāng)相關(guān)服務(wù)調(diào)用失敗的時(shí)候我們可以給出一個(gè)統(tǒng)一標(biāo)準(zhǔn)的失敗返回值,而不是直接拋出異常。讓我們的程序依然能夠繼續(xù)執(zhí)行下去。
下面我們演示下如何使用 Polly 進(jìn)行服務(wù)調(diào)用的降級處理。
首先我們使用 Policy 的 FallbackAsync("FALLBACK") 方法設(shè)置降級的返回值。當(dāng)我們服務(wù)需要降級的時(shí)候會(huì)返回 "FALLBACK" 的固定值。
同時(shí)使用 WrapAsync 方法把重試策略包裹起來。這樣我們就可以達(dá)到當(dāng)服務(wù)調(diào)用失敗的時(shí)候重試3次,如果重試依然失敗那么返回值降級為固定的 "FALLBACK" 值。
熔斷
通過以上演示,我們的服務(wù)當(dāng)發(fā)生故障的時(shí)候可以自動(dòng)重試,自動(dòng)降級了。雖然現(xiàn)在看起來挺健壯,但是還是會(huì)有不小的問題。
當(dāng)我們引入重試策略后,如果服務(wù)調(diào)用一直失敗,每次調(diào)用都會(huì)反復(fù)進(jìn)行重試,雖然最后會(huì)進(jìn)行降級處理,但是這勢必會(huì)影響服務(wù)的處理速度。
當(dāng)流量很大的時(shí)候,某個(gè)接口服務(wù)調(diào)用很慢有可能會(huì)阻塞整個(gè)服務(wù),請求不斷積壓,資源不斷耗盡,速度越來越慢,這是一種惡性循環(huán)。最終同樣可能導(dǎo)致整個(gè)應(yīng)用全部癱瘓的嚴(yán)重后果。
面對這種情況我們就需要引入熔斷機(jī)制。當(dāng)一個(gè)服務(wù)的調(diào)用頻繁出現(xiàn)故障的時(shí)候我們可以認(rèn)為它當(dāng)前是不穩(wěn)定的,在一段時(shí)間內(nèi)我們不應(yīng)該再去調(diào)用這個(gè)服務(wù)。
首先定義 circuitBreaker 熔斷器策略。這個(gè)策略注意最好定義成靜態(tài)變量。這樣能夠以整個(gè)完整服務(wù)的錯(cuò)誤為基礎(chǔ)來判斷是否開啟斷路器。
然后在業(yè)務(wù)代碼內(nèi)定義重試策略,降級策略。我們使這些策略一一嵌套。fallback => circuitBreaker => retry ,表示當(dāng)發(fā)生異常的時(shí)候首先開始重試, 重試失敗后嘗試熔斷,如果達(dá)到熔斷的條件就拋出 BrokenCircuitException 異常,降級策略捕獲到 HttpRequestException 或者 BrokenCircuitException 進(jìn)行降級操作。
Polly 還有很多用法比如“緩存”、“隔離” 等策略,這里不在一一演示了。更多請查看文檔:https://github.com/App-vNext/Polly/wiki
使用AOP思想改進(jìn)體驗(yàn)
通過以上對于 Polly 的演示,雖然我們完成了簡單的重試、服務(wù)降級、熔斷等功能。但是顯然對于每個(gè)方法都去使用 Polly 編寫一堆策略的話實(shí)在是太麻煩了。那么有什么辦法能改進(jìn)一下 Polly 的使用體驗(yàn)嗎?答案是使用 AOP 的思想,通過在執(zhí)行的方法上打上 Attribute 的方式來指定 Polly 的策略。
下面我們使用 lemon 大佬的 AspectCore AOP 組件結(jié)合 Polly 來演示下如何通過 AOP 的思想來處理重試、降級、熔斷等策略。
通過 nuget 安裝 AspectCore 核心類庫。
public class PollyHandleAttribute : AbstractInterceptorAttribute{/// <summary>/// 重試次數(shù)/// </summary>public int RetryTimes { get; set; } /// <summary>/// 是否熔斷/// </summary>public bool IsCircuitBreaker { get; set; }/// <summary>/// 熔斷前的異常次數(shù)/// </summary>public int ExceptionsAllowedBeforeBreaking { get; set; }/// <summary>/// 熔斷時(shí)間/// </summary>public int SecondsOfBreak { get; set; }/// <summary>/// 降級方法/// </summary>public string FallbackMethod { get; set; }/// <summary>/// 一些方法級別統(tǒng)一計(jì)數(shù)的策略,比如熔斷/// </summary>static ConcurrentDictionary<string, AsyncCircuitBreakerPolicy> policyCaches = new ConcurrentDictionary<string, AsyncCircuitBreakerPolicy>();public PollyHandleAttribute(){}public override async Task Invoke(AspectContext context, AspectDelegate next){Context pollyCtx = new Context();pollyCtx["aspectContext"] = context;Polly.Wrap.AsyncPolicyWrap policyWarp = null;var retry = Policy.Handle<HttpRequestException>().RetryAsync(RetryTimes);var fallback = Policy.Handle<Exception>().FallbackAsync(async (fallbackContent, token) =>{AspectContext aspectContext = (AspectContext)fallbackContent["aspectContext"];var fallBackMethod = context.ServiceMethod.DeclaringType.GetMethod(this.FallbackMethod);var fallBackResult = fallBackMethod.Invoke(context.Implementation, context.Parameters);aspectContext.ReturnValue = fallBackResult;}, async (ex, t) => { });AsyncCircuitBreakerPolicy circuitBreaker = null;if (IsCircuitBreaker){var cacheKey = $"{context.ServiceMethod.DeclaringType.ToString()}_{context.ServiceMethod.Name}";if (policyCaches.TryGetValue(cacheKey, out circuitBreaker)){//從緩存內(nèi)獲取該方法的全局熔斷策略}else{circuitBreaker = Policy.Handle<Exception>().CircuitBreakerAsync(exceptionsAllowedBeforeBreaking: this.ExceptionsAllowedBeforeBreaking,durationOfBreak: TimeSpan.FromSeconds(this.SecondsOfBreak));policyCaches.TryAdd(cacheKey, circuitBreaker);}}if (circuitBreaker == null){policyWarp = fallback.WrapAsync(retry);}else{policyWarp = fallback.WrapAsync(circuitBreaker.WrapAsync(retry));}await policyWarp.ExecuteAsync(ctx => next(context), pollyCtx);}}定義一個(gè) PollyHandleAttribute 類,它繼承自 AbstractInterceptorAttribute 類,然后實(shí)現(xiàn) Invoke 方法。我們需要在 Invoke 方法內(nèi)動(dòng)態(tài)構(gòu)造出 Polly 的相關(guān)策略,然后通過 Polly 去執(zhí)行真正的方法。這里主要需要注意的是熔斷策略不能每次新建,因?yàn)閷τ谌蹟鄟碚f是需要全局統(tǒng)計(jì)該方法的異常數(shù)量來判斷是否熔斷的,所以需要把熔斷策略緩存起來。
這個(gè)類參考了 Edison Zhou 大佬的部分代碼,原文:Polly+AspectCore實(shí)現(xiàn)熔斷與降級機(jī)制
因?yàn)槲覀冃枰诜椒ㄉ蠘?biāo)記 PollyHandleAttribute ,所以把獲取會(huì)員相關(guān)的邏輯封住進(jìn) MemberService 的 GetMemberInfo 方法內(nèi)。并且在方法上打上Attribute :[PollyHandle(IsCircuitBreaker = true, FallbackMethod = "GetMemberInfoFallback", ExceptionsAllowedBeforeBreaking = 5, SecondsOfBreak = 30, RetryTimes = 3)] 直接通過 AOP 的方式來配置 Polly 的策略,這樣就方便了很多。
上面這些配置好之后,下面開始就是如何使 aspectcore 接管 asp.net core 的依賴注入了。根據(jù)文檔也很簡單:
通過 nuget 安裝 AspectCore.Extensions.DependencyInjection 包。
public static IHostBuilder CreateHostBuilder(string[] args) =>Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>{webBuilder.ConfigureKestrel(options =>{options.ListenAnyIP(6001);});webBuilder.UseStartup<Startup>();}).UseServiceProviderFactory(new DynamicProxyServiceProviderFactory());在 CreateHostBuilder 內(nèi)使用 UseServiceProviderFactory 替換 ServiceProviderFactory 為 aspectcore 的實(shí)現(xiàn)。
public void ConfigureServices(IServiceCollection services){services.AddSingleton<IMemberService, MemberService>();...services.ConfigureDynamicProxy();}在 ConfigureServices 方法內(nèi)配置 IMemberService 的依賴關(guān)系以及配置 aspectcore 的動(dòng)態(tài)代理。
總結(jié)
通過以上文字我們大致了解了什么是服務(wù)降級、什么是熔斷。并且通過 Polly 演示了如何處理這些情況。最后使用 lemon 大佬的 AspectCore 封裝成一個(gè) Attribute 來演示如何通過 AOP 的思想來簡化 Polly 的使用。
謝謝閱讀。
演示項(xiàng)目地址
https://github.com/kklldog/myhotel_microservice
相關(guān)文章
NET Core with 微服務(wù) - 什么是微服務(wù)
.Net Core with 微服務(wù) - 架構(gòu)圖
.Net Core with 微服務(wù) - Ocelot 網(wǎng)關(guān)
.Net Core with 微服務(wù) - Consul 注冊中心
.Net Core with 微服務(wù) - Seq 日志聚合
.Net Core with 微服務(wù) - Elastic APM
.Net Core with 微服務(wù) - Consul 配置中心
關(guān)注我的公眾號(hào)一起玩轉(zhuǎn)技術(shù)
總結(jié)
以上是生活随笔為你收集整理的.Net Core with 微服务 - Polly 服务降级熔断的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 天呐!你知道MSBuild都干了些什么
- 下一篇: 预约 .NET Conf: Focus