1. 程式人生 > >.NET Core中Circuit Breaker

.NET Core中Circuit Breaker

face The please res 一段時間 故障 cte dds 理想

談談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 GetOrderDetailsAsync(string orderId);
}

public class OrderService : IOrderService
{
public async Task GetOrderDetailsAsync(string orderId)
{
using (HttpClient client = new HttpClient())
{
return await client.GetStringAsync($"http://localhost:9999/api/values/{orderId}");
}
}
}
比較簡單,就是發起HTTP請求到訂單服務,拿一下返回的結果。

忽略熔斷的話,現在已經可以通過這個OrderService去拿到結果了。

[HttpGet]
public async Task Get([FromServices] Services.IOrderService service, string id = "0")
{
return await service.GetOrderDetailsAsync(id);
}
結果如下:

這是最最最最理想的情況!如果我們把訂單服務停了,會發生什麽事呢?

十分尷尬,這個訂單服務的調用方也廢了。

當然,try-catch也是可以幫我們處理這個尷尬的問題,但這並不是我們想要的結果啊!

下面來看看引入Circuit Breaker之後如何略微優雅一點去處理這個問題。

定義一個GetOrderDetailsHystrixCommand,讓它繼承HystrixCommand。

public class GetOrderDetailsHystrixCommand : HystrixCommand
{
private readonly IOrderService _service;
private readonly ILogger _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();
services.AddHystrixCommand("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的熔斷功能,用起來還算是比較簡單,也比較靈活。

更多配置和說明可以參考官方文檔。

本文的示例代碼:

CircuitBreakerDemo

如果您認為這篇文章還不錯或者有所收獲,可以點擊右下角的【推薦】按鈕,因為你的支持是我繼續寫作,分享的最大動力!
作者:Catcher ( 黃文清 )

.NET Core中Circuit Breaker