سناریوی را در نظر بگیرید که در برنامه شما با استفاده از HttpClient به یک API ریکوئست ارسال میکنید. اگر سرویس خارجی از دسترس خارج شود، ریکوئست هایی که به برنامه شما ارسال میشود و شما از طریق HttpClient به این API ارسال میشود با تاخیر جواب داده میشود و احتمالا خطای Internal Server Error یا Request Timeout را دریافت میکنید. اگر تعداد کاربران برنامه شما زیاد باشد، این عمل به ازای هرکاربر اجرا میشود و تمامی کاربران با یک تاخیر چند ثانیه ای را روبه رو خواهند شد. برای حل این مشکل میتوانیم از Circuit Breaker استفاده کنیم که اگر سرویس موردنظر از دسترس خارج شد، پشت سرهم به آن ریکوئست ارسال نکنیم. برای پیاده سازی این سناریو یک پروژه تستی ایجاد میکنیم از نوع Asp.Net Core Web Application. در ادامه باید برای پیاده سازی عملیات Circuit Breaker باید پکیج Microsoft.Extensions.Http.Polly را نصب کنید.

سپس یک کلاس برای ارسال ریکوئست به سرویس موردنظر ایجاد میکنیم:

public class SomeService : ISomeService
{
    private readonly HttpClient _httpClient;

    public SomeService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<string> DoSomething(CancellationToken cancellationToken)
    {
        string result = string.Empty;
        try
        {
             await _httpClient.GetAsync("notfounduri", cancellationToken);
            result = "Ok";
        }
        catch (Polly.CircuitBreaker.BrokenCircuitException)
        {
            result = "Service is unavailable. please try again";
        }
        return result;
    }
}

در متد DoSomething  یک ریکوئست به یک آدرس ارسال میکنیم که وجود ندارد و عملا خطای 404 را برمیگرداند.

کنترلر مربوطه:

[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
    private readonly ISomeService _someService;

    public TestController(ISomeService someService)
    {
        _someService = someService;
    }
    [HttpGet]
    public async Task<IActionResult> Get(CancellationToken cancellationToken)
    {
        var result = await _someService.DoSomething(cancellationToken);
        return Ok(result);
    }
}

 سپس در کلاس Startup برای خطای 404 یک سناریو ایجاد میکنیم:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddScoped<ISomeService, SomeService>();
    services.AddHttpClient<ISomeService, SomeService>(options=>
    {
        options.BaseAddress = new Uri("https://stackoverflow.com");
    })
    .AddPolicyHandler(GetCircuitBreakerPolicy())
    .AddPolicyHandler(GetRetryPolicy());
}
static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
                   .HandleTransientHttpError()
                   .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
                   .WaitAndRetryAsync(2, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}
static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
    return HttpPolicyExtensions
                   .HandleTransientHttpError()
                   .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
                   .CircuitBreakerAsync(2, TimeSpan.FromSeconds(30));
}

در متد AddHttpClient مقدار BaseAddress را مشخص میکنیم که به سایت Stackoverflow ریکوئست ارسال میکند. 

متد GetRetryPolicy مشخص میکند که اگر ریکوئست ارسال شده با خطای 404 مواجه شد، به صورت خودکار 2 بار دیگر ریکوئست را ارسال میکند و تاخیر میان تلاش اول با تلاش بعدی به صورت Math.Pow(2, retryAttempt) مشخص میشود. یعنی اگر در ریکوئست اول با خطا مواجه شد، بعد از زمان ( 2 به توان (تعداد دفعه تلاش برای ارسال ریکوئست)) مجدد ریکوئست بعدی رو ارسال میکنه. یعنی تاخیر بین رکوئست های ارسالی بیشتر و بیشتر میشه. 

در متد GetCircuitBreakerPolicy مشخص کردیم که اگر دوبار این سناریو اجرا شد، یعنی 2 بار API مربوط به ارسال ریکوئست به سرویس "/api/test" فراخوانی شد ( نه تعداد retry مربوط به polly) و هردوبار با خطای 404 مواجه شد، تا 30 ثانیه بعد به سایت Stackoverflow ریکوئست ارسال نمیکند و یک خطا از نوع Polly.CircuitBreaker.BrokenCircuitException صادر میکند. با این کار فقط دو ریکوئست اول با تاخیر مربوط به 404 مواجه میشوند و تا 30 ثانیه بعد خطای مربوط به "Service is unavailable. please try again" به کاربر برگردانده میشود.

البته بهتر است برای خطاهای 500 این عملیات پیاده سازی شود، ولی برای پیاده سازی مثال مربوطه از خطای 404 استفاده کردیم.

منابع : 

یک مثال: فرض کنید شما در یک شرکت پرداخت الکترونیک مشغول به کار میباشید و میخواهید که یک ریکوئست به مخابرات برای پرداخت قبض ارسال کنید. کاربران شما عملا بسیاز زیاد خواهند بود و اگر سیستم مخابرات  پاسخگو نباشد، تمامی کاربران شما باید منتظر ریسپانس از سمت مخابرات باشند، اما اگر از Circuit Breaker استفاده کنید، بعد از چند ریکوئست اول، فورا به کاربران اعلام میکنید که سیستم مخابرات از دسترس خارج شده است و باید بعدا مجددا برای پرداخت قبض اقدام نمایند.

;)

Powered by Froala Editor

نظرات

سایر نظرات
  • کاربر مهمان

    سلام فرهاد جان. خیلی آموزنده و خوب بود . موفق باشی

    ارسال شده در تاریخ جمعه، ۰۲ مهر ۱۴۰۰
    • admin

      سلام دوست عزیز، خواهش میکنم. ممنون بابت نظرت. شما هم موفق باشی

      ارسال شده در تاریخ جمعه، ۰۲ مهر ۱۴۰۰