谈谈Circuit Breaker在.NET Core中的简单应用
前言
由于微服務的盛行,不少公司都將原來細粒度比較大的服務拆分成多個小的服務,讓每個小服務做好自己的事即可。
經過拆分之后,就避免不了服務之間的相互調用問題!如果調用沒有處理好,就有可能造成整個系統的癱瘓,好比說其中一些基礎服務出現了故障,那么用到這些基礎服務的地方都是要做一定的處理的,不能讓它們出現大面積的癱瘓!!!
正常情況下的解決方案就要對服務進行熔斷處理,不能因為提供方出現了問題就讓調用方也廢了。
熔斷一般是指軟件系統中,由于某些原因使得服務出現了過載現象,為防止造成整個系統故障,從而采用的一種保護措施。
對于這個問題,Steeltoe的Circuit Breaker是一個不錯的選擇。本文的示例代碼也是基于它的。
Steeltoe的Circuit Breaker
Steeltoe是什么呢?Steeltoe可以說是構建微服務的一個解決方案吧。具體的可以訪問它的官網:
http://steeltoe.io/
回歸正題,先來看看官方對Circuit Breaker的描述:
What do you do when a service you depend on stops responding? Circuit breakers enable you to bypass a failing service, allowing it time to recover, and preventing your users from seeing nasty error messages. Steeltoe includes a .NET implementation of Netflix Hystrix, a proven circuit breaker implementation with rich metrics and monitoring features.
不難發現,Circuit Breaker可以讓我們很好的處理失敗的服務。它也包含了對Netflix Hystrix的.NET(Core)實現。
關于熔斷機制,有個非常經典的圖(這里直接拿了官方文檔的圖),核心描繪的就是三種狀態之間的變化關系。
說了那么多,下面還是來看個簡單的例子來略微深入理解一下吧。
注:服務發現和服務注冊不是本文的重點,所以這里不會使用Steeltoe相應的功能。
簡單例子
先定義一個簡單的訂單服務,這個服務很簡單,就一個返回直接返回對應訂單號的接口,這里用默認的ASP.NET Core Web API項目做一下調整就好了。
[Route("api/[controller]")]public class ValuesController : Controller{ ?
?// GET api/values/123[HttpGet("{id}")] ?
???public string Get(string id) ? ?{ ? ?
??? ?return $"order-{id}";} ? ? ? ? }
再來一個新服務去調用上面的訂單服務。
先拋開熔斷相關的,定義一個用于訪問訂單服務的Service接口和實現。
public interface IOrderService{ ? ?? ? Task<string> GetOrderDetailsAsync(string orderId); }
public class OrderService : IOrderService{ ?
?public async Task<string> GetOrderDetailsAsync(string orderId) ? ?{ ? ?
? ? ?using (HttpClient client = new HttpClient()){ ? ? ? ? ?
? ? ??return await client.GetStringAsync($"http://localhost:9999/api/values/{orderId}");} ? ? ? ? ? ? } }
比較簡單,就是發起HTTP請求到訂單服務,拿一下返回的結果。
忽略熔斷的話,現在已經可以通過這個OrderService去拿到結果了。
[HttpGet]public async Task<string> Get([FromServices] Services.IOrderService service, string id = "0"){ ?
?return await service.GetOrderDetailsAsync(id); }
結果如下:
這是最最最最理想的情況!如果我們把訂單服務停了,會發生什么事呢?
十分尷尬,這個訂單服務的調用方也廢了。
當然,try-catch也是可以幫我們處理這個尷尬的問題,但這并不是我們想要的結果啊!
下面來看看引入Circuit Breaker之后如何略微優雅一點去處理這個問題。
定義一個GetOrderDetailsHystrixCommand,讓它繼承HystrixCommand。
public class GetOrderDetailsHystrixCommand : HystrixCommand<string> { ?? ? private readonly IOrderService _service; ?
? ?private readonly ILogger<GetOrderDetailsHystrixCommand> _logger; ?
? ??private string _orderId; ?
? ??
? ??public GetOrderDetailsHystrixCommand(IHystrixCommandOptions options,IOrderService service,ILogger<GetOrderDetailsHystrixCommand> logger ? ? ? ?) : base(options) ? ?{ ? ? ?
? ?? ?this._service = service; ? ? ?
? ?? ? ?this._logger = logger; ? ? ?
? ?? ? ??this.IsFallbackUserDefined = true;} ?
? ??
? ???public async Task<string> GetOrderDetailsAsync(string orderId) ? ?{_orderId = orderId; ? ?
? ???? ?return await ExecuteAsync();} ?
? ?
? ??protected override async Task<string> RunAsync() ? ?{ ? ?
? ??? ?var result = await _service.GetOrderDetailsAsync(_orderId);_logger.LogInformation("Get the result : {0}", result); ? ? ? ?return result;} ? ?
? ??? ?
? ??protected override async Task<string> RunFallbackAsync() ? ?{ ? ? ? ?//斷路器已經打開if (!this._circuitBreaker.AllowRequest){ ? ? ? ? ?
? ?? ? ? ? ??return await Task.FromResult("Please wait for sometimes");}_logger.LogInformation($"RunFallback"); ? ?
? ?? ? ? ? ??? ?return await Task.FromResult<string>($"RunFallbackAsync---OrderId={_orderId}");}}
這里有幾個地方要注意:
構造函數一定要有IHystrixCommandOptions這個參數
RunAsync是真正執行調用的地方
RunFallbackAsync是由于某些原因不能拿到返回結果時會執行的地方,所謂的優雅降級。
接下來要做的是在Startup中進行注冊。
public void ConfigureServices(IServiceCollection services){services.AddSingleton<IOrderService, OrderService>();services.AddHystrixCommand<GetOrderDetailsHystrixCommand>("Order", Configuration);services.AddMvc(); }可以看到,在添加熔斷命令的時候,還用到了Configuration這個參數,這就說明,我們還少了配置!!
配置是放到appsettings.json里面的,下面來看一下要怎么配置:
{"hystrix": {"command": {"default": {"circuitBreaker": {//是否啟用,默認是true"enabled": true,//在指定時間窗口內,熔斷觸發的最小個數"requestVolumeThreshold": 5,//熔斷多少時間后去嘗試請求"sleepWindowInMilliseconds": 5000,//失敗率達到多少百分比后熔斷"errorThresholdPercentage": 50,//是否強制開啟熔斷"forceOpen": false,//是否強制關閉熔斷"forceClosed": false},//是否啟用fallback"fallback": {"enabled": true}}}}}需要添加一個名字為hystrix的節點,里面的command節點才是我們要關注的地方!
default,是默認的配置,針對所有的Command!如果說有某個特定的Command要單獨配置,可以在command下面添加相應的命令節點即可。
其他配置項,都已經用注釋的方式解釋了。
下面這張動圖模擬了訂單服務從可用->不可用->可用的情形。
除了服務不可用,可能還有一種情況發生的概率會比較大,超時!
舉個例子,有一個服務平常都是響應很快,突然有一段時間不知道什么原因,處理請求的速度慢了很多,這段時間內經常出現客戶端等待很長的時間,甚至超時了。
當遇到這種情況的時候,一般都會設置一個超時時間,只要在這個時間內沒有響應就認為是超時了!
可以通過下面的配置來完成超時的配置:
{"hystrix": {"command": {"default": {"execution": {"timeout": {"enabled": true},"isolation": {"strategy": "THREAD", "thread": {//超時時間"timeoutInMilliseconds": 1000} }},}}}}總結
這里也只是介紹了幾個比較常用和簡單的功能,它還可以合并多個請求,緩存請求等諸多實用的功能。總體來說,Steeltoe的熔斷功能,用起來還算是比較簡單,也比較靈活。
更多配置和說明可以參考官方文檔。
原文:http://www.cnblogs.com/catcher1994/p/8975192.html
.NET社區新聞,深度好文,歡迎訪問公眾號文章匯總 http://www.csharpkit.com
總結
以上是生活随笔為你收集整理的谈谈Circuit Breaker在.NET Core中的简单应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 发现 ASP.NET Core Sign
- 下一篇: .NET Core 在中国的现状调研