다음을 통해 공유


회로 차단기 패턴 구현

팁 (조언)

이 콘텐츠는 .NET Docs 또는 오프라인으로 읽을 수 있는 다운로드 가능한 무료 PDF로 제공되는 컨테이너화된 .NET 애플리케이션용 .NET 마이크로 서비스 아키텍처인 eBook에서 발췌한 내용입니다.

컨테이너화된 .NET 애플리케이션을 위한 .NET 마이크로서비스 아키텍처 eBook의 표지 썸네일.

앞에서 설명한 것처럼 원격 서비스 또는 리소스에 연결하려고 할 때와 같이 복구하는 데 가변적인 시간이 걸릴 수 있는 오류를 처리해야 합니다. 이러한 유형의 오류를 처리하면 애플리케이션의 안정성과 복원력을 향상시킬 수 있습니다.

분산 환경에서는 느린 네트워크 연결 및 시간 제한과 같은 일시적인 오류 또는 리소스가 느리게 응답하거나 일시적으로 사용할 수 없는 경우 원격 리소스 및 서비스에 대한 호출이 실패할 수 있습니다. 이러한 오류는 일반적으로 짧은 시간 후에 스스로 수정되며 강력한 클라우드 애플리케이션은 "재시도 패턴"과 같은 전략을 사용하여 이를 처리할 준비가 되어 있어야 합니다.

그러나 예기치 않은 이벤트로 인해 오류를 해결하는 데 훨씬 더 오래 걸릴 수 있는 상황도 있을 수 있습니다. 이러한 오류는 심각도의 범위가 연결의 부분적인 손실에서 서비스의 전체 오류까지 퍼질 수 있습니다. 이러한 상황에서 애플리케이션이 성공할 가능성이 없는 작업을 지속적으로 다시 시도하는 것은 무의미할 수 있습니다.

대신 작업이 실패했음을 수락하고 그에 따라 오류를 처리하도록 애플리케이션을 코딩해야 합니다.

Http 재시도를 부주의하게 사용하면 자체 소프트웨어 내에서 DoS(서비스 거부) 공격이 발생할 수 있습니다. 마이크로 서비스가 실패하거나 느리게 수행되면 여러 클라이언트가 실패한 요청을 반복적으로 다시 시도할 수 있습니다. 이로 인해 실패한 서비스를 대상으로 하는 트래픽이 기하급수적으로 증가할 위험이 있습니다.

따라서 계속 시도할 가치가 없을 때 과도한 요청이 중지되도록 일종의 방어 장벽이 필요합니다. 그 방어 장벽은 정확하게 회로 차단기입니다.

회로 차단기 패턴은 "재시도 패턴"이 아닌 다른 용도로 사용됩니다. "재시도 패턴"을 사용하면 애플리케이션이 작업이 결국 성공할 것으로 예상하여 작업을 다시 시도할 수 있습니다. 회로 차단기 패턴은 애플리케이션이 실패할 가능성이 있는 작업을 수행할 수 없도록 합니다. 애플리케이션은 이러한 두 패턴을 결합할 수 있습니다. 그러나 재시도 논리는 회로 차단기에서 반환된 예외에 민감해야 하며 회로 차단기에서 오류가 일시적이지 않음을 나타내는 경우 재시도 시도를 중단해야 합니다.

IHttpClientFactory와 Polly를 사용하여 회로 차단기 패턴 구현

재시도를 구현할 때 회로 차단기에 권장되는 방법은 Polly와 같은 검증된 .NET 라이브러리와 네이티브 통합 IHttpClientFactory을 활용하는 것입니다.

나가는 미들웨어 파이프라인에 IHttpClientFactory 회로 차단기 정책을 추가하는 것은 사용 IHttpClientFactory시 이미 가지고 있는 코드에 단일 증분 코드를 추가하는 것만큼 간단합니다.

HTTP 호출 재시도에 사용되는 코드에는 다음 증분 코드와 같이 사용할 정책 목록에 회로 차단기 정책을 추가하는 코드만 추가됩니다.

// Program.cs
var retryPolicy = GetRetryPolicy();
var circuitBreakerPolicy = GetCircuitBreakerPolicy();

builder.Services.AddHttpClient<IBasketService, BasketService>()
        .SetHandlerLifetime(TimeSpan.FromMinutes(5))  // Sample: default lifetime is 2 minutes
        .AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
        .AddPolicyHandler(retryPolicy)
        .AddPolicyHandler(circuitBreakerPolicy);

AddPolicyHandler() 메서드는 사용할 개체에 HttpClient 정책을 추가하는 것입니다. 이 경우 회로 차단기에 대한 Polly 정책을 추가합니다.

보다 모듈식 접근 방식을 사용하려면 회로 차단기 정책이 다음 코드와 같이 별도의 메서드로 GetCircuitBreakerPolicy()정의됩니다.

// also in Program.cs
static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
}

위의 코드 예제에서 회로 차단기 정책은 Http 요청을 다시 시도할 때 5개의 연속 오류가 발생한 경우 회로를 중단하거나 열도록 구성됩니다. 이러한 상황에서는 회로가 30초 동안 차단됩니다. 이 기간 동안 회로 차단기에 의해 호출이 즉시 실패하여 실제로는 호출이 이루어지지 않습니다. 정책은 관련 예외 및 HTTP 상태 코드를 오류로 자동으로 해석합니다.

HTTP 호출을 수행하는 클라이언트 애플리케이션 또는 서비스와 다른 환경에 배포된 특정 리소스에 문제가 있는 경우 회로 차단기를 사용하여 요청을 대체 인프라로 리디렉션해야 합니다. 이렇게 하면 백 엔드 마이크로 서비스만 영향을 주지만 클라이언트 애플리케이션에는 영향을 주지 않는 데이터 센터의 중단이 있는 경우 클라이언트 애플리케이션은 대체 서비스로 리디렉션할 수 있습니다. Polly는 이 장애 조치( failover ) 정책 시나리오를 자동화하는 새 정책을 계획하고 있습니다.

이러한 모든 기능은 위치 투명성을 통해 Azure에서 자동으로 관리되는 것과 달리 .NET 코드 내에서 장애 조치(failover)를 관리하는 경우를 위한 것입니다.

사용 관점에서 HttpClient를 사용하는 경우 이전 섹션과 같이 코드가 사용 HttpClient 시와 IHttpClientFactory동일하기 때문에 여기에 새로운 항목을 추가할 필요가 없습니다.

eShopOnContainers에서 Http 재시도 및 회로 차단기 테스트

Docker 호스트에서 eShopOnContainers 솔루션을 시작할 때마다 여러 컨테이너를 시작해야 합니다. 일부 컨테이너는 SQL Server 컨테이너와 같이 시작 및 초기화 속도가 느립니다. 특히 eShopOnContainers 애플리케이션을 Docker에 처음 배포할 때는 이미지와 데이터베이스를 설정해야 하기 때문입니다. 일부 컨테이너가 다른 컨테이너보다 느리게 시작된다는 사실은 이전 섹션에서 설명한 대로 docker-compose 수준에서 컨테이너 간에 종속성을 설정하더라도 나머지 서비스가 처음에 HTTP 예외를 throw할 수 있습니다. 컨테이너 간의 이러한 docker-compose 종속성은 프로세스 수준에 불과합니다. 컨테이너의 진입점 프로세스가 시작될 수 있지만 SQL Server가 쿼리할 준비가 되지 않았을 수 있습니다. 결과는 오류의 연속일 수 있으며 애플리케이션은 특정 컨테이너를 사용하려고 할 때 예외를 가져올 수 있습니다.

애플리케이션이 클라우드에 배포될 때 시작 시 이러한 유형의 오류가 표시될 수도 있습니다. 이 경우 오케스트레이터는 클러스터 노드에서 컨테이너 수를 분산할 때 컨테이너를 한 노드 또는 VM에서 다른 노드(즉, 새 인스턴스 시작)로 이동할 수 있습니다.

'eShopOnContainers'가 모든 컨테이너를 시작할 때 이러한 문제를 해결하는 방법은 앞에서 설명한 재시도 패턴을 사용하는 것입니다.

eShopOnContainers에서 회로 차단기 테스트

회로를 중단/열고 eShopOnContainers로 테스트할 수 있는 몇 가지 방법이 있습니다.

한 가지 옵션은 회로 차단기 정책에서 허용되는 재시도 횟수를 1로 낮추고 전체 솔루션을 Docker에 다시 배포하는 것입니다. 한 번의 재시도로 배포 중에 HTTP 요청이 실패하고 회로 차단기가 열리고 오류가 발생할 가능성이 있습니다.

또 다른 옵션은 Basket 마이크로 서비스에서 구현된 사용자 지정 미들웨어를 사용하는 것입니다. 이 미들웨어를 사용하도록 설정하면 모든 HTTP 요청을 catch하고 상태 코드 500을 반환합니다. 다음과 같이 실패한 URI에 대한 GET 요청을 수행하여 미들웨어를 사용하도록 설정할 수 있습니다.

  • GET http://localhost:5103/failing
    이 요청은 미들웨어의 현재 상태를 반환합니다. 미들웨어를 사용하도록 설정하면 요청이 상태 코드 500을 반환합니다. 미들웨어를 사용하지 않도록 설정하면 응답이 없습니다.

  • GET http://localhost:5103/failing?enable
    이 요청은 미들웨어를 사용하도록 설정합니다.

  • GET http://localhost:5103/failing?disable
    이 요청은 미들웨어를 사용하지 않도록 설정합니다.

예를 들어 애플리케이션이 실행되면 모든 브라우저에서 다음 URI를 사용하여 요청을 수행하여 미들웨어를 사용하도록 설정할 수 있습니다. 주문 마이크로 서비스는 포트 5103을 사용합니다.

http://localhost:5103/failing?enable

그런 다음 그림 8-5와 같이 URI http://localhost:5103/failing를 사용하여 상태를 확인할 수 있습니다.

실패한 미들웨어 시뮬레이션의 상태를 확인하는 스크린샷.

그림 8-5. "실패" ASP.NET 미들웨어의 상태를 확인합니다. 이 경우 사용하지 않도록 설정됩니다.

이 시점에서 Basket 마이크로 서비스는 호출할 때마다 상태 코드 500으로 응답합니다.

미들웨어가 실행되면 MVC 웹 애플리케이션에서 주문을 시도할 수 있습니다. 요청이 실패하기 때문에 회로가 열립니다.

다음 예제에서는 MVC 웹 애플리케이션에 주문에 대한 논리에 catch 블록이 있음을 확인할 수 있습니다. 코드가 오픈 회로 예외를 catch하는 경우 대기하라는 친숙한 메시지가 사용자에게 표시됩니다.

public class CartController : Controller
{
    //…
    public async Task<IActionResult> Index()
    {
        try
        {
            var user = _appUserParser.Parse(HttpContext.User);
            //Http requests using the Typed Client (Service Agent)
            var vm = await _basketSvc.GetBasket(user);
            return View(vm);
        }
        catch (BrokenCircuitException)
        {
            // Catches error when Basket.api is in circuit-opened mode
            HandleBrokenCircuitException();
        }
        return View();
    }

    private void HandleBrokenCircuitException()
    {
        TempData["BasketInoperativeMsg"] = "Basket Service is inoperative, please try later on. (Business message due to Circuit-Breaker)";
    }
}

요약은 다음과 같습니다. 다시 시도 정책은 HTTP 요청을 만들기 위해 여러 번 시도하고 HTTP 오류를 가져옵니다. 재시도 횟수가 회로 차단기 정책에 설정된 최대 수(이 경우 5)에 도달하면 애플리케이션은 BrokenCircuitException을 throw합니다. 결과는 그림 8-6과 같이 친숙한 메시지입니다.

장바구니 서비스 작동하지 않은 오류가 있는 MVC 웹앱의 스크린샷

그림 8-6. UI에 오류를 반환하는 회로 차단기

회로를 열거나 중단해야 하는 경우에 대해 다른 논리를 구현할 수 있습니다. 또는 대체 데이터 센터 또는 중복 백 엔드 시스템이 있는 경우 다른 백 엔드 마이크로 서비스에 대해 HTTP 요청을 시도할 수 있습니다.

마지막으로, CircuitBreakerPolicy의 또 다른 방법은 Isolate을 사용하여 회로를 강제로 열고 그대로 두는 것이며, Reset를 사용하여 회로를 다시 닫는 것입니다. 정책에서 직접 격리 및 재설정을 호출하는 유틸리티 HTTP 엔드포인트를 빌드하는 데 사용할 수 있습니다. 이러한 HTTP 엔드포인트는 업그레이드하려는 경우와 같이 다운스트림 시스템을 일시적으로 격리하기 위해 프로덕션 환경에서 적절하게 보호된 상태로 사용될 수도 있습니다. 또는 오류가 발생했다고 의심되는 다운스트림 시스템을 보호하기 위해 회로를 수동으로 차단할 수 있습니다.

추가 리소스