Hystrix-Go를 사용한 Go 마이크로서비스의 서킷 브레이커 구현
Lukas Schneider
DevOps Engineer · Leapcell

소개
마이크로서비스 아키텍처의 복잡한 세계에서 단일 실패 서비스는 사용자 경험을 저하시키고 비즈니스 운영에 영향을 미치며 광범위한 중단으로 빠르게 확산될 수 있습니다. 종종 "연쇄 실패(cascading failure)"라고 불리는 이 현상은 분산 시스템의 안정성과 복원력에 심각한 도전 과제를 제시합니다. 이를 극복하기 위해 서킷 브레이커와 같은 패턴은 복원력 있는 마이크로서비스를 구축하는 데 중요한 구성 요소로 부상했습니다. 이 기사에서는 hystrix-go
와 같은 라이브러리를 활용하여 Go 마이크로서비스에서 서킷 브레이커 패턴의 실제 적용을 살펴보고, 연쇄 실패를 완화하고 시스템 견고성을 보장합니다. 개념을 탐구하고, 구현을 살펴보고, 실제 Go 코드 예제로 효과를 설명합니다.
서킷 브레이커 및 관련 개념 이해
구현에 앞서 관련된 핵심 개념을 명확히 이해해 봅시다.
-
마이크로서비스: 느스하게 결합된 독립적으로 배포 가능한 서비스 컬렉션으로 애플리케이션을 구조화하는 아키텍처 스타일입니다. 유연성을 제공하지만, 이러한 분산 특성은 서비스 간 통신 및 오류 처리에 복잡성을 야기합니다.
-
연쇄 실패(Cascading Failure): 한 서비스의 실패가 이후 종속 서비스의 실패를 유발하여 잠재적으로 전체 시스템 붕괴로 이어지는 연쇄 반응입니다. 데이터베이스 연결 풀이 고갈되어 데이터 서비스가 실패하고, 이로 인해 API 게이트웨이가 중단되어 궁극적으로 전체 애플리케이션이 다운되는 시나리오를 상상해 보세요.
-
서킷 브레이커 패턴: 전기 서킷 브레이커에서 영감을 받은 이 패턴은 잠재적으로 실패하는 서비스에 대한 호출을 모니터링합니다. 실패율이 미리 정의된 임계값을 초과하면 서킷이 "트립(trips)"(열림)되어 실패하는 서비스에 대한 추가 호출을 방지하고 복구할 시간을 줍니다. 비정상적인 서비스에 부딪히는 대신 즉각적인 대체 메커니즘이 트리거됩니다. 이는 실패한 서비스에 대한 리소스 소비를 방지하고, 더 빠른 실패 응답을 제공하며, 상위 서비스가 무기한 차단되는 것을 방지합니다.
-
hystrix-go
: Netflix의 Hystrix 라이브러리에서 영감을 받은 Hystrix 생태계에 대한 Go 기여입니다. 중요 서비스에 대한 내결함성, 지연 시간 내성 및 동시성 제한을 포함하여 강력한 서킷 브레이커 기능을 제공합니다.
Hystrix-Go로 서킷 브레이커 구현하기
서킷 브레이커 패턴은 세 가지 주요 상태로 작동합니다.
-
닫힘(Closed): 서킷은 처음에 닫혀 요청이 대상 서비스로 통과하도록 허용합니다. Hystrix-Go는 이러한 요청의 성공 및 실패율을 모니터링합니다.
-
열림(Open): 정의된 롤링 창 내의 실패(또는 지연 시간) 수가 구성된 임계값을 초과하면 서킷이 열립니다. 대상 서비스에 대한 모든 후속 요청은 즉시 거부되며, 실패하는 서비스를 호출하지 않고도 대체 함수가 호출됩니다. 이 상태는 일반적으로 구성 가능한 "슬립 창(sleep window)" 또는 "시간 초과(timeout)"를 가집니다.
-
반 열림(Half-Open): 슬립 창이 만료된 후 서킷은 반 열림 상태로 전환됩니다. 제한된 수의 테스트 요청이 대상 서비스로 통과하도록 허용됩니다. 이러한 요청이 성공하면 서킷이 다시 닫힙니다(서비스가 복구되었다고 가정). 실패하면 서킷은 다른 슬립 창을 위해 열린 상태로 돌아갑니다.
Hystrix-Go 구성
hystrix-go
는 구성을 통해 서킷 브레이커 동작에 대한 세밀한 제어를 허용합니다.
package main import ( "fmt" "io/ioutil" "net/http" "time" "github.com/afex/hystrix-go/hystrix" ) func main() { // 특정 명령 이름에 대한 Hystrix 구성 hystrix.ConfigureCommand("my_service_call", hystrix.CommandConfig{ Timeout: 1000, // 밀리초 단위 명령 실행 시간 초과 MaxRequests: 10, // 서킷이 트립되기 전 롤링 창당 최대 요청 수 ErrorPercentThreshold: 25, // 서킷을 트립하는 오류 비율 SleepWindow: 5000, // 서킷이 닫히려고 시도하기 전에 열려 있는 시간(밀리초) RequestVolumeThreshold: 5, // 서킷을 트립하기 위한 롤링 창 내 최소 요청 수 }) // 예: 실패하는 서비스 시뮬레이션 failingServiceEndpoint := "http://localhost:8081/fail" // 이 엔드포인트는 실패를 시뮬레이션합니다. // 실패하는 서비스 시뮬레이션을 위한 고루틴 시작 go startFailingService() // 잠재적으로 실패하는 서비스에 여러 번 호출 for i := 0; i < 20; i++ { fmt.Printf("시도 %d: ", i+1) err := hystrix.Do("my_service_call", func() error { // 이것은 서비스에 대한 실제 호출을 수행하는 함수입니다. resp, err := http.Get(failingServiceEndpoint) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("서비스가 OK가 아닌 상태를 반환했습니다: %d", resp.StatusCode) } body, _ := ioutil.ReadAll(resp.Body) fmt.Printf("서비스 응답: %s\n", string(body)) return nil }, func(err error) error { // 이것은 서킷이 열려 있거나 원래 호출이 실패했을 때 실행되는 대체 함수입니다. fmt.Printf("대체 트리거됨! 오류: %v\n", err) return nil // 오류를 조용히 처리하려면 nil을 반환합니다. }) if err != nil { fmt.Printf("Hystrix `Do` 오류 반환: %v\n", err) } time.Sleep(500 * time.Millisecond) // 요청 간에 일부 지연 시뮬레이션 } // 실패하는 서비스 고루틴이 종료될 시간을 줍니다. time.Sleep(2 * time.Second) } // startFailingService는 때때로 실패하는 서비스를 시뮬레이션합니다. func startFailingService() { http.HandleFunc("/fail", func(w http.ResponseWriter, r *http.Request) { // 50%의 확률로 실패를 시뮬레이션하거나, 서비스가 너무 많이 호출된 경우 if time.Now().Second()%2 == 0 { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("시뮬레이션된 내부 서버 오류")) } else { w.WriteHeader(http.StatusOK) w.Write([]byte("서비스가 요청을 성공적으로 처리했습니다.")) } }) fmt.Println("실패하는 서비스가 :8081에서 수신 대기 중입니다.") http.ListenAndServe(":8081", nil) }
이 예제에서는:
"my_service_call"
이라는 Hystrix 명령을 정의합니다.Timeout
:http.Get
호출이 1000ms 이상 걸리면 실패로 간주됩니다.ErrorPercentThreshold
: 롤링 창 내에서 요청의 25%가 실패하면 서킷이 트립됩니다.SleepWindow
: 트립된 후 서킷은 5000ms(5초) 동안 열려 있습니다.RequestVolumeThreshold
: 서킷이 트립되기 전에 롤링 창 내에서 최소 5개의 요청이 발생해야 합니다.hystrix.Do
함수는 두 개의 익명 함수를 인수로 받습니다.- 첫 번째 함수는 외부 서비스를 호출하려는 주요 함수입니다.
- 두 번째 함수는 기본 함수가 실패하거나 서킷이 열려 있는 경우 실행되는 대체 함수입니다.
서킷 브레이커 동작 관찰
위에 제공된 코드를 실행하면 다음을 관찰할 수 있습니다.
- 초기 요청은 시뮬레이션된 실패하는 서비스에 도달할 가능성이 높습니다.
- 오류율이
ErrorPercentThreshold
(예: 5개 요청에 대해 25%는 2개 오류)를 넘어서면 `