회로 차단기는 오류를 감지하고 오류가 지속적으로 반복되는 것을 방지하는 방식으로 이러한 오류를 처리하는 논리를 캡슐화합니다. 예를 들어, 외부 서비스, 데이터베이스 또는 일시적으로 오류가 발생할 수 있는 시스템의 모든 부분에 대한 네트워크 호출을 처리할 때 유용합니다. 회로 차단기를 사용하면 계단식 장애를 방지하고, 일시적인 오류를 관리하며, 시스템 고장 중에도 안정적이고 응답성이 뛰어난 시스템을 유지할 수 있습니다.
계단식 오류는 시스템 한 부분의 오류가 다른 부분의 오류를 유발하여 광범위한 중단을 초래할 때 발생합니다. 분산 시스템의 마이크로서비스가 응답하지 않아 종속 서비스가 시간 초과되어 결국 실패하는 경우를 예로 들 수 있습니다. 애플리케이션의 규모에 따라 이러한 오류의 영향은 치명적일 수 있으며 이로 인해 성능이 저하되고 심지어 사용자 경험에도 영향을 미칠 수 있습니다.
회로 차단기 자체는 기술/패턴이며 작동하는 세 가지 상태가 있습니다. 이에 대해 설명하겠습니다.
2. 열린 상태 : 열린 상태에서 회로 차단기는 대상 서비스에 접속을 시도하지 않고 들어오는 모든 요청을 즉시 실패합니다. 실패한 서비스의 추가 과부하를 방지하고 복구할 시간을 주기 위해 상태로 들어갑니다. 미리 정의된 시간 초과 후 회로 차단기는 반개방 상태로 이동합니다. 관련된 예는 다음과 같습니다. 온라인 상점에서 모든 구매 시도가 실패하는 갑작스러운 문제가 발생했다고 상상해 보세요. 시스템 과부하를 피하기 위해 매장에서는 신규 구매 요청 접수를 일시적으로 중단합니다.
3. 반 개방 상태 : 반 개방 상태에서 회로 차단기는 (구성 가능한) 제한된 수의 테스트 요청이 대상 서비스를 통과하도록 허용합니다. 그리고 이러한 요청이 성공하면 회로는 다시 닫힌 상태로 전환됩니다. 실패하면 회로는 열린 상태로 돌아갑니다. 위의 열린 상태에서 제공한 온라인 상점의 예에서 문제가 해결되었는지 확인하기 위해 온라인 상점에서 몇 번의 구매 시도를 허용하기 시작합니다. 몇 번의 시도가 성공하면 매장은 서비스를 완전히 재개하여 새로운 구매 요청을 수락합니다.
이 다이어그램은 회로 차단기가 서비스 B에 대한 요청이 성공했는지 확인한 후 실패/중단되는 경우를 보여줍니다.
다음 다이어그램은 서비스 B에 대한 테스트 요청이 성공하고 회선이 닫히고 모든 추가 통화가 다시 서비스 B로 라우팅되는 경우를 보여줍니다.
참고 : 회로 차단기의 주요 구성에는 실패 임계값(회로를 여는 데 필요한 실패 횟수), 열린 상태의 시간 제한, 반 열린 상태에서의 테스트 요청 수가 포함됩니다. 상태입니다.
이 글을 따라가려면 Go에 대한 사전 지식이 필요하다는 점을 언급하는 것이 중요합니다.
다른 소프트웨어 엔지니어링 패턴과 마찬가지로 회로 차단기도 다양한 언어로 구현될 수 있습니다. 그러나 이 기사에서는 Golang에서의 구현에 중점을 둘 것입니다. goresilience, go-resilience, gobreaker 등 이 목적으로 사용할 수 있는 여러 라이브러리가 있지만 특히 gobreaker 라이브러리 사용에 집중하겠습니다.
Pro Tip : gobreaker 패키지의 내부 구현은 여기에서 확인하세요.
외부 API에 대한 호출을 처리하기 위해 회로 차단기가 구현된 간단한 Golang 애플리케이션을 고려해 보겠습니다. 이 기본 예는 회로 차단기 기술을 사용하여 외부 API 호출을 래핑하는 방법을 보여줍니다.
몇 가지 중요한 사항을 살펴보겠습니다.
회로 차단기 구현을 확인하기 위해 몇 가지 단위 테스트를 작성해 보겠습니다. 이해해야 할 가장 중요한 단위 테스트만 설명하겠습니다. 전체 코드는 여기에서 확인하실 수 있습니다.
t.Run("FailedRequests", func(t *testing.T) { // Override callExternalAPI to simulate failure callExternalAPI = func() (int, error) { return 0, errors.New("simulated failure") } for i := 0; i < 4; i++ { _, err := cb.Execute(func() (interface{}, error) { return callExternalAPI() }) if err == nil { t.Fatalf("expected error, got none") } } if cb.State() != gobreaker.StateOpen { t.Fatalf("expected circuit breaker to be open, got %v", cb.State()) } })
//Simulates the circuit breaker being open, //wait for the defined timeout, //then check if it closes again after a successful request. t.Run("RetryAfterTimeout", func(t *testing.T) { // Simulate circuit breaker opening callExternalAPI = func() (int, error) { return 0, errors.New("simulated failure") } for i := 0; i < 4; i++ { _, err := cb.Execute(func() (interface{}, error) { return callExternalAPI() }) if err == nil { t.Fatalf("expected error, got none") } } if cb.State() != gobreaker.StateOpen { t.Fatalf("expected circuit breaker to be open, got %v", cb.State()) } // Wait for timeout duration time.Sleep(settings.Timeout + 1*time.Second) //We expect that after the timeout period, //the circuit breaker should transition to the half-open state. // Restore original callExternalAPI to simulate success callExternalAPI = func() (int, error) { resp, err := http.Get(server.URL) if err != nil { return 0, err } defer resp.Body.Close() return resp.StatusCode, nil } _, err := cb.Execute(func() (interface{}, error) { return callExternalAPI() }) if err != nil { t.Fatalf("expected no error, got %v", err) } if cb.State() != gobreaker.StateHalfOpen { t.Fatalf("expected circuit breaker to be half-open, got %v", cb.State()) } //After verifying the half-open state, another successful request is simulated to ensure the circuit breaker transitions back to the closed state. for i := 0; i < int(settings.MaxRequests); i++ { _, err = cb.Execute(func() (interface{}, error) { return callExternalAPI() }) if err != nil { t.Fatalf("expected no error, got %v", err) } } if cb.State() != gobreaker.StateClosed { t.Fatalf("expected circuit breaker to be closed, got %v", cb.State()) } })
t.Run("ReadyToTrip", func(t *testing.T) { failures := 0 settings.ReadyToTrip = func(counts gobreaker.Counts) bool { failures = int(counts.ConsecutiveFailures) return counts.ConsecutiveFailures > 2 // Trip after 2 failures } cb = gobreaker.NewCircuitBreaker(settings) // Simulate failures callExternalAPI = func() (int, error) { return 0, errors.New("simulated failure") } for i := 0; i < 3; i++ { _, err := cb.Execute(func() (interface{}, error) { return callExternalAPI() }) if err == nil { t.Fatalf("expected error, got none") } } if failures != 3 { t.Fatalf("expected 3 consecutive failures, got %d", failures) } if cb.State() != gobreaker.StateOpen { t.Fatalf("expected circuit breaker to be open, got %v", cb.State()) } })
회로 차단기 구현에 지수 백오프 전략을 추가하면 한 단계 더 나아갈 수 있습니다. 이 기사에서는 지수 백오프 전략의 예를 보여줌으로써 간단하고 간결하게 설명하겠습니다. 그러나 부하 차단, 격벽, 폴백 메커니즘, 컨텍스트 및 취소와 같이 언급할 가치가 있는 회로 차단기에 대한 다른 고급 전략도 있습니다. 이러한 전략은 기본적으로 회로 차단기의 견고성과 기능을 향상시킵니다. 다음은 지수 백오프 전략을 사용하는 예입니다.
지수 백오프
지수 백오프를 사용한 회로 차단기
몇 가지 사항을 명확하게 짚어보겠습니다.
사용자 정의 백오프 기능: exponentialBackoff 기능은 지터를 사용하여 지수 백오프 전략을 구현합니다. 기본적으로 시도 횟수를 기준으로 백오프 시간을 계산하므로 재시도를 시도할 때마다 지연이 기하급수적으로 증가합니다.
재시도 처리: /api 핸들러에서 볼 수 있듯이 이제 로직에는 지정된 시도 횟수(시도 := 5)까지 외부 API 호출을 시도하는 루프가 포함됩니다. 시도가 실패할 때마다 재시도하기 전에 exponentialBackoff 함수에 의해 결정된 기간 동안 기다립니다.
회로 차단기 실행: 회로 차단기는 루프 내에서 사용됩니다. 외부 API 호출이 성공하면( err == nil) 루프가 중단되고 성공적인 결과가 반환됩니다. 모든 시도가 실패하면 HTTP 503(서비스를 사용할 수 없음) 오류가 반환됩니다.
회로 차단기 구현에 사용자 지정 백오프 전략을 통합하는 것은 실제로 일시적인 오류를 보다 원활하게 처리하는 것을 목표로 합니다. 재시도 간 지연 시간이 늘어나면 실패한 서비스에 대한 로드를 줄여 복구할 시간을 확보하는 데 도움이 됩니다. 위 코드에서 알 수 있듯이 외부 API를 호출할 때 재시도 사이에 지연을 추가하기 위해 exponentialBackoff 함수가 도입되었습니다.
또한 측정항목과 로깅을 통합하여 실시간 모니터링 및 경고를 위한 Prometheus와 같은 도구를 사용하여 회로 차단기 상태 변경을 모니터링할 수 있습니다. 간단한 예는 다음과 같습니다.
고급 전략으로 서킷 브레이커 패턴 구현
보시겠지만 이제 우리는 다음을 수행했습니다.
프로 팁 : Go의 init 함수는 메인 함수나 패키지의 다른 코드가 실행되기 전에 패키지의 상태를 초기화하는 데 사용됩니다. 이 경우 init 함수는 Prometheus에 requestCount 지표를 등록합니다. 이는 본질적으로 Prometheus가 이 측정항목을 인식하고 애플리케이션이 실행되기 시작하자마자 데이터 수집을 시작할 수 있도록 보장합니다.
우리는 고장 카운터를 늘리고 회로 트립 시기를 결정하는 ReadyToTrip 기능을 포함하여 맞춤 설정으로 회로 차단기를 만듭니다.
상태 변경을 기록하고 해당 프로메테우스 측정항목을 증가시키는 OnStateChange
/metrics 엔드포인트에서 Prometheus 측정항목을 노출합니다
이 기사를 마무리하면서, 탄력적이고 안정적인 시스템을 구축하는 데 회로 차단기가 어떻게 큰 역할을 하는지 확인하셨기를 바랍니다. 연속적인 오류를 사전에 방지함으로써 마이크로서비스와 분산 시스템의 안정성을 강화하고 역경 속에서도 원활한 사용자 경험을 보장합니다.
확장성을 위해 설계된 모든 시스템에는 장애를 적절하게 처리하고 신속하게 복구하기 위한 전략이 통합되어야 한다는 점을 명심하세요 — Oluwafemi , 2024
원본은 https://oluwafemiakinde.dev 2024년 6월 7일에 게시되었습니다.
위 내용은 Go의 회로 차단기: 계단식 오류 중지의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!