再探Circuit Breaker之使用Polly

前言

上一篇介紹了使用Steeltoe來處理服務熔斷,這篇咱們將用Polly來處理服務熔斷。html

不廢話了,直接進正題。git

簡單的例子

一樣先定義一個簡單的服務。github

[Route("api/[controller]")]
public class ValuesController : Controller
{
    // GET api/values
    [HttpGet]
    public string Get()
    {
        return "service--a";
    }
}

再來一個新服務去調用上面的服務。api

定義一個用於訪問服務的Service接口和實現。瀏覽器

public interface IAService
{
    Task<string> GetAsync();
}

public class AService : IAService
{
    private PolicyWrap<string> _policyWrap;

    private ILogger _logger;

    public AService(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<AService>();

        //超時
        var timeout = Policy
              .TimeoutAsync(1, Polly.Timeout.TimeoutStrategy.Pessimistic, (context, ts, task) =>
              {
                  _logger.LogInformation("AService timeout");
                  return Task.CompletedTask;
              });
        
        //熔斷
        var circuitBreaker = Policy
            .Handle<Exception>()
            .CircuitBreakerAsync(2, TimeSpan.FromSeconds(5), (ex, ts) =>
            {
                _logger.LogInformation($"AService OnBreak -- ts = {ts.Seconds}s ,ex.message = {ex.Message}");
            }, () =>
            {
                _logger.LogInformation("AService OnReset");
            });
        
        //Fallback + 熔斷 + 超時
        _policyWrap = Policy<string>
            .Handle<Exception>()
            .FallbackAsync(GetFallback(), (x) =>
            {
                _logger.LogInformation($"AService Fallback -- {x.Exception.Message}");                    
                return Task.CompletedTask;
            })
            .WrapAsync(circuitBreaker)
            .WrapAsync(timeout);
    }

    //降級處理
    private string GetFallback()
    {
        return "fallback";
    }

    public async Task<string> GetAsync()
    {
        return await _policyWrap.ExecuteAsync(() =>
        {
            return QueryAsync();
        });
    }

    private async Task<string> QueryAsync()
    {
        using (var client = new HttpClient())
        {
            var res = await client.GetStringAsync("http://localhost:9001/api/values");
            return res;
        }
    }
}

要注意的有幾個地方。async

Polly沒有既包含熔斷又包含降級又包含超時的,這個須要本身去組合。相對來講,Hystrix在這一方面彷佛好一點點。ui

可是,各有各的好,Polly分離了每個模塊,讓咱們自由組合,也是很靈活的。code

因此能夠看到,咱們定義了3個Policy,再把它們Wrap起來。orm

另外,還在觸發每個Policy的時候,都會輸出相應的日記,方便咱們後面看效果。htm

對於寫日記這一塊,我的認爲對比Steeltoe,Polly的方式要更加方便和簡單。

下面是控制器的使用。

// GET api/values
[HttpGet]
public async Task<string> A([FromServices]IAService aService)
{
    return await aService.GetAsync();
}

還有一個關鍵的步驟:在Startup註冊咱們的AService。

services.AddSingleton<IAService, AService>();

切記是Singleton!否則熔斷就不會起做用了!!

直接上效果圖

簡單說明一下這張圖,一開始,服務A和調用方都是正常的,後面中斷服務A,使其不可用,這個時候調用方就會走降級處理。

調用方多請求幾回,就能夠看到OnBreak的日記輸出,說明斷路器已經處於Open狀態,不會直接走真正的請求,而是走的Fallback。

最後啓動服務A,能夠看到OnReset的日記輸出,說明斷路器已經處於Closed狀態了,瀏覽器顯示的也是服務A的返回結果。

再來模擬一下超時的情形。

由於上面設置的超時時間是1秒,因此讓其休息1001毫秒就能夠模擬了。

// GET api/values
[HttpGet]
public string Get()
{
    System.Threading.Thread.Sleep(1001);
    return "service--a";
}

再來看看效果圖

調用方一直是提示由於超時而降級,而熔斷。從日記也能夠看出,是由於超時而致使熔斷的。

前面還提到一個註冊服務的問題,這裏解釋一下爲何咱們要讓其註冊成Singleton?

咱們先把註冊服務這一塊調整爲不是Singleton,這裏以Scope爲例,Transient也是同樣的。

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IAService, AService>();
    //services.AddSingleton<IAService, AService>();
    services.AddMvc();
}

效果以下:

能夠看到,日記一直輸出超時!並無提示熔斷相關的信息!這說明咱們設置的熔斷並無起做用!!

這個問題與實例的生命週期有着密不可分的關係!

試想一下,若是每次請求,都建立一個AService的實例,一樣的每次都會從新建立一個新的熔斷器,那熔斷還會生效嗎?

反之,若是熔斷器只有一個,那麼不管發起多少次請求,它都是惟一的,因此它才能統計到有多少次異常,從而去觸發熔斷。

這也是一個咱們須要特別注意的地方。否則一個不當心就入坑了。

總結

Polly用起來仍是比較簡單,比較靈活的,咱們能夠組合多種不一樣的Policy來達到咱們想要的結果。

本文的示例代碼:

CircuitBreakerDemo

相關文章
相關標籤/搜索