Polly+HttpClientFactory

Polly

在.Net Core中有一個被.Net基金會承認的庫Polly,它一種彈性和瞬態故障處理庫,能夠用來簡化對服務熔斷降級的處理。
Polly的策略主要由「故障」和「動做」兩個部分組成,「故障」能夠包括異常、超時等狀況,「動做」則包括Fallback(降級)、重試(Retry)、熔斷(Circuit-Breaker)等。策略則用來執行業務代碼,當業務代碼出現了「故障」中的狀況時就開始執行「動做」。
主要包含如下功能:git

  • 重試(Retry)
  • 斷路器(Circuit-breaker)
  • 超時檢測(Timeout)
  • 回退(FallBack)
  • 策略包裝(PolicyWrap)

故障定義

故障也能夠說是觸發條件,它使用Handle 來定義,表示在什麼狀況下,纔對其進行處理(熔斷,降級,重試等)。 github

// 單一異常種類
Policy.Handle<HttpRequestException>();
// 帶條件判斷的單一異常
Policy.Handle<SqlException>(ex => ex.Number == 10)
// 多種異常
Policy.Handle<HttpRequestException>().Or<OperationCanceledException>()
// 多種異常
Policy.Handle<HttpRequestException>().OrResult<HttpResponseMessage>(res => res.StatusCode != HttpStatusCode.OK)
// 返回結果異常
Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode != HttpStatusCode.OK)

重試(Retry)

重試就是指Polly在調用失敗時捕獲咱們指定的異常,並從新發起調用,若是重試成功,那麼對於調用者來講,就像沒有發生過異常同樣。在網絡調用中常常出現瞬時故障,那麼重試機制就很是重要。bash

var client = new HttpClient();
Policy
    // 處理什麼異常,好比httprequrest異常
    .Handle<HttpRequestException>()
    // 或者處理response的httpstatuscode 不等於200的狀況
    .OrResult<HttpResponseMessage>(res => res.StatusCode != HttpStatusCode.OK)
    // 重試次數 3
    .Retry(3,
        (ex, retryCount,content) => 
        {
            Console.WriteLine($"請求Api異常,進行第{retryCount}次重試,ErrorCode:{ex.Result.StatusCode}");
        })
    // 要執行的任務
    .Execute(() =>
    {
        HttpResponseMessage res = client.GetAsync("http://qa.xx.com/Social/policy/1").Result;
        return res;
    });

回退(FallBack)

回退也稱服務降級,用來指定發生故障時的備用方案。網絡

var client = new HttpClient();
Policy
    .Handle<HttpRequestException>()
    .OrResult<HttpResponseMessage>(res => res.StatusCode != HttpStatusCode.OK)
    // 出現異常只會回退處理
    .Fallback(() =>
    {
        HttpResponseMessage res =
            client.GetAsync("http://qa.xx.com/Social/policy/2").Result;
        Console.WriteLine("Fallback(降級)處理.");
        return res;
    })
    
    .Execute(() =>
    {
        HttpResponseMessage res =
            client.GetAsync("http://qa.xx.com/Social/policy/1").Result;
        return res;
    });

超時(Timeou)

Polly支持兩種超時策略:async

  • TimeoutStrategy.Pessimistic: 悲觀模式
    當委託到達指定時間沒有返回時,不繼續等待委託完成,並拋超時TimeoutRejectedException異常。
  • TimeoutStrategy.Optimistic:樂觀模式
    這個模式依賴於 co-operative cancellation,只是觸發CancellationTokenSource.Cancel函數,須要等待委託自行終止操做。
var timeoutPolicy = Policy.TimeoutAsync(1, TimeoutStrategy.Pessimistic,
    (context, timespan, task) =>
    {
        Console.WriteLine("請求超時.");
        return Task.CompletedTask;
    });
timeoutPolicy.ExecuteAsync(async () =>
{
    var client = new HttpClient();
    await   client.GetAsync("http://localhost:5000/home/delay");
    return Task.CompletedTask;
});

熔斷(Circuit-breaker)

若是調用某個目標服務出現過多超時、異常等狀況,能夠採起必定時間內熔斷該服務的調用,熔斷期間的請求將再也不繼續調用目標服務,而是直接返回,節約資源,提升服務的穩定性,熔斷週期結束後若是目標服務狀況好轉則恢復調用。函數

熔斷狀態
  • 打開(Open)

熔斷器打開狀態,此時對目標服務的調用都直接返回錯誤,熔斷週期內不會走網絡請求,當熔斷週期結束時進入半開狀態;ui

  • 關閉(Closed)

關閉狀態下正常發生網絡請求,但會記錄符合熔斷條件的連續執行次數,若是錯誤數量達到設定的閾值(若是在沒有達到閾值以前恢復正常,以前的累積次數將會歸零),熔斷狀態進入到打開狀態;spa

  • 半開(Half-Open)

半開狀態下容許定量的服務請求,若是調用都成功則認爲恢復了,關閉熔斷器,不然認爲還沒好,又回到熔斷器打開狀態;rest

注意:爲了服務的穩定性,在執行須要屢次 Retry(重試策略)的狀況下,最好組合熔斷策略,預防可能存在的風險。code

var client = new HttpClient();
var ciruitBreaker = Policy.Handle<Exception>()
    // 熔斷前容許出現3次錯誤,熔斷時間10s,熔斷時觸發, 熔斷恢復時觸發,在熔斷時間到了以後觸發
    .CircuitBreaker(3, TimeSpan.FromSeconds(10),
        (ex, breakDelay) =>
        {
            //熔斷時觸發
            Console.WriteLine("斷路器打開,熔斷觸發.");
        },
        () =>
        {
            //熔斷恢復時觸發
            Console.WriteLine("熔斷器關閉了.");
        },
        () =>
        {
            //在熔斷時間到了以後觸發
            Console.WriteLine("熔斷時間到,進入半開狀態");
        }
// 模擬屢次調用,觸發熔斷
for (int i = 1; i <= 150; i++)
{
    try
    {
        ciruitBreaker.Execute(() =>
        {
            Console.WriteLine($"第{i}次開始執行.");
            var res = client.GetAsync("http://localhost:5000/home/delay").Result;
            Console.WriteLine($"第{i}次執行:正常:" + res.StatusCode);
            Thread.Sleep(TimeSpan.FromSeconds(1));
            return res;
        });
    }
    catch (Exception e)
    {
        Console.WriteLine($"第{i}次執行:異常:" + e.Message);
        Thread.Sleep(TimeSpan.FromSeconds(1));
    }
}
熔斷高級配置

根據時間段內總請求數中的異常比例觸發熔斷

var client = new HttpClient();
var advancedCircuitBreaker = Policy.Handle<Exception>()
    .AdvancedCircuitBreaker(0.5, TimeSpan.FromSeconds(10), 3, TimeSpan.FromSeconds(10),
        (ex, breakDelay) =>
        {
            Console.WriteLine("斷路器打開,熔斷觸發.");
        }, () =>
        {
            Console.WriteLine("熔斷器關閉了.");
        }, () =>
        {
            Console.WriteLine("熔斷時間到,進入半開狀態");
        });

// 模擬屢次調用,觸發熔斷
for (int i = 1; i <= 150; i++)
{
    try
    {
        advancedCircuitBreaker.Execute(() =>
        {
            Console.WriteLine($"第{i}次開始執行.");
            var res = client.GetAsync("http://localhost:5000/home/delay").Result;
            Console.WriteLine($"第{i}次執行:正常:" + res.StatusCode);
            Thread.Sleep(TimeSpan.FromSeconds(1));
            return res;
        });
    }
    catch (Exception e)
    {
        Console.WriteLine($"第{i}次執行:異常:" + e.Message);
        Thread.Sleep(TimeSpan.FromSeconds(1));
    }
}

策略包裝(PolicyWrap)

策略包提供了一種靈活的方式來封裝多個彈性策略(從右往左).

//定義超時
var timeOut = Policy.Timeout(TimeSpan.FromSeconds(10),
    ((context, timespan, task) =>
    {
        Console.WriteLine("請求超時.");
    }));

//定義重試
var retry = Policy.Handle<Exception>()
    .Retry(3, ((exception, retryCount, context) =>
    {
        Console.WriteLine($"第{retryCount}次重試.");
    })
// 定義熔斷策略
var circuitBreaker = Policy.Handle<Exception>()
    // 熔斷前容許出現3次錯誤,熔斷時間10s,熔斷時觸發, 熔斷恢復時觸發,在熔斷時間到了以後觸發
    .CircuitBreaker(3, TimeSpan.FromSeconds(10),
        (ex, breakDelay) =>
        {
            //熔斷時觸發
            Console.WriteLine("斷路器打開,熔斷觸發.");
        },
        () =>
        {
            //熔斷恢復時觸發
            Console.WriteLine("熔斷器關閉了.");
        },
        () =>
        {
            //在熔斷時間到了以後觸發
            Console.WriteLine("熔斷時間到,進入半開狀態");
        });

//定義回退策略
var fallback = Policy.Handle<Exception>()
    .Fallback(() =>
    {
        Console.WriteLine("正在降級處理.");
    }
fallback.Wrap(Policy.Wrap(circuitBreaker,retry, timeOut)).Execute(() =>
{
    Console.WriteLine("start.");
});

HttpClientFactory

簡單使用

Install Microsoft.Extensions.Http
若是有多個能夠同時使用

// StartUp->ConfigureServices
services.AddHttpClient("local",options =>
{
    options.BaseAddress = new Uri("http://localhost:5000");
}
services.AddHttpClient("fanyou",options =>
{
    options.BaseAddress = new Uri("http://qa.fanyouvip.com");
});

//使用
[Route("client")]
public class ClientController : ControllerBase
{
    private readonly IHttpClientFactory _clientFactory;
    public ClientController(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    [HttpGet("Local")]
    public async Task<IActionResult> Local()
    {
        var client = _clientFactory.CreateClient("local");
        var res = await client.GetAsync("/home/delay");
        return Ok(res);
    }
    
    [HttpGet("Fanyou")]
    public async Task<IActionResult> Fanyou()
    {
        var client = _clientFactory.CreateClient("fanyou");
        var res = await client.GetAsync("/social");
        return Ok(res);
    }
}

結合Polly

Install Microsoft.Extensions.Http.Polly

// 第一種方式
services.AddHttpClient("local",
        options => { options.BaseAddress = new Uri("http://localhost:5000"); })
    .AddTransientHttpErrorPolicy(p =>
    {
        var handlers = p.OrResult(result => result.StatusCode != HttpStatusCode.OK)
            .RetryAsync(3,
                (ex, retryCount, context) =>
                {
                    Console.WriteLine($"第{retryCount}次重試.異常:{ex.Exception.Message}");
                });
        return handlers;
    }).AddTransientHttpErrorPolicy(p =>
    {
        var breaker = p.CircuitBreakerAsync(3, TimeSpan.FromSeconds(10));
        return breaker;
    });

//第二種方式
services.AddHttpClient("Test",
        options => { options.BaseAddress = new Uri("http://localhost:5003"); })
    .AddPolicyHandler(RetryPolicy())
    .AddPolicyHandler(CircuiBreakerPolicy());

/// <summary>
/// 重試策略
/// </summary>
/// <returns>IAsyncPolicy<HttpResponseMessage></returns>
private IAsyncPolicy<HttpResponseMessage> RetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .OrResult(res => res.StatusCode != HttpStatusCode.OK)
        .WaitAndRetryAsync(3, retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount)));

/// <summary>
/// 熔斷策略
/// </summary>
/// <returns>IAsyncPolicy<HttpResponseMessage></returns>
private IAsyncPolicy<HttpResponseMessage> CircuiBreakerPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .CircuitBreakerAsync(5, TimeSpan.FromMinutes(1));
}

源碼示例

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息