因爲微服務的盛行,很多公司都將原來細粒度比較大的服務拆分紅多個小的服務,讓每一個小服務作好本身的事便可。git
通過拆分以後,就避免不了服務之間的相互調用問題!若是調用沒有處理好,就有可能形成整個系統的癱瘓,比如說其中一些基礎服務出現了故障,那麼用到這些基礎服務的地方都是要作必定的處理的,不能讓它們出現大面積的癱瘓!!!github
正常狀況下的解決方案就要對服務進行熔斷處理,不能由於提供方出現了問題就讓調用方也廢了。json
熔斷通常是指軟件系統中,因爲某些緣由使得服務出現了過載現象,爲防止形成整個系統故障,從而採用的一種保護措施。api
對於這個問題,Steeltoe的Circuit Breaker是一個不錯的選擇。本文的示例代碼也是基於它的。緩存
Steeltoe是什麼呢?Steeltoe能夠說是構建微服務的一個解決方案吧。具體的能夠訪問它的官網:app
http://steeltoe.io/async
迴歸正題,先來看看官方對Circuit Breaker的描述:ide
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}"); } }
這裏有幾個地方要注意:
接下來要作的是在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的熔斷功能,用起來還算是比較簡單,也比較靈活。
更多配置和說明能夠參考官方文檔。
本文的示例代碼: