로드 밸런서는 현대 소프트웨어 개발에 매우 중요합니다. 요청이 여러 서버에 어떻게 분산되는지, 또는 트래픽이 많은 상황에서도 특정 웹사이트가 더 빠르게 느껴지는 이유가 무엇인지 궁금하신 경우, 효율적인 로드 밸런싱에 답이 있는 경우가 많습니다.
이 게시물에서는 Go의 라운드 로빈 알고리즘을 사용하여 간단한 애플리케이션 로드 밸런서를 구축하겠습니다. 이 게시물의 목적은 로드 밸런서가 내부적으로 어떻게 작동하는지 단계별로 이해하는 것입니다.
로드 밸런서는 들어오는 네트워크 트래픽을 여러 서버에 분산시키는 시스템입니다. 단일 서버가 너무 많은 로드를 감당하지 못하도록 하여 병목 현상을 방지하고 전반적인 사용자 경험을 향상시킵니다. 또한 로드 밸런싱 접근 방식은 한 서버에 장애가 발생하면 트래픽이 사용 가능한 다른 서버로 자동으로 다시 라우팅될 수 있도록 보장하여 장애의 영향을 줄이고 가용성을 높입니다.
트래픽을 분산시키는 다양한 알고리즘과 전략이 있습니다.
이 게시물에서는 라운드 로빈 로드 밸런서 구현에 중점을 두겠습니다.
라운드 로빈 알고리즘은 들어오는 각 요청을 순환 방식으로 사용 가능한 다음 서버로 보냅니다. 서버 A가 첫 번째 요청을 처리하면 서버 B가 두 번째 요청을 처리하고 서버 C가 세 번째 요청을 처리합니다. 모든 서버가 요청을 받으면 서버 A에서 다시 시작됩니다.
이제 코드로 넘어가 로드 밸런서를 구축해 보겠습니다.
type LoadBalancer struct { Current int Mutex sync.Mutex }
먼저 다음 요청을 처리해야 하는 서버를 추적하기 위해 Current 필드가 있는 간단한 LoadBalancer 구조체를 정의하겠습니다. Mutex는 우리 코드가 동시에 사용하기에 안전한지 보장합니다.
우리가 로드 밸런싱하는 각 서버는 서버 구조에 의해 정의됩니다:
type Server struct { URL *url.URL IsHealthy bool Mutex sync.Mutex }
여기서 각 서버에는 URL과 서버가 요청을 처리할 수 있는지 여부를 나타내는 IsHealthy 플래그가 있습니다.
로드 밸런서의 핵심은 라운드 로빈 알고리즘입니다. 작동 방식은 다음과 같습니다.
func (lb *LoadBalancer) getNextServer(servers []*Server) *Server { lb.Mutex.Lock() defer lb.Mutex.Unlock() for i := 0; i
구성은 서버 URL과 상태 확인 간격이 포함된 config.json 파일에 저장됩니다(자세한 내용은 아래 섹션 참조).
type Config struct { Port string `json:"port"` HealthCheckInterval string `json:"healthCheckInterval"` Servers []string `json:"servers"` }
구성 파일은 다음과 같습니다.
{ "port": ":8080", "healthCheckInterval": "2s", "servers": [ "http://localhost:5001", "http://localhost:5002", "http://localhost:5003", "http://localhost:5004", "http://localhost:5005" ] }
우리는 들어오는 트래픽을 서버로 라우팅하기 전에 서버가 건강한지 확인하고 싶습니다. 이는 각 서버에 정기적인 상태 확인을 전송하여 수행됩니다.
func healthCheck(s *Server, healthCheckInterval time.Duration) { for range time.Tick(healthCheckInterval) { res, err := http.Head(s.URL.String()) s.Mutex.Lock() if err != nil || res.StatusCode != http.StatusOK { fmt.Printf("%s is down\n", s.URL) s.IsHealthy = false } else { s.IsHealthy = true } s.Mutex.Unlock() } }
몇 초마다(구성에 지정된 대로) 로드 밸런서는 각 서버에 HEAD 요청을 보내 서버가 정상인지 확인합니다. 서버가 다운되면 IsHealthy 플래그가 false로 설정되어 향후 트래픽이 해당 서버로 라우팅되지 않습니다.
로드 밸런서는 요청을 받으면 역방향 프록시를 사용하여 사용 가능한 다음 서버로 요청을 전달합니다. Golang에서 httputil 패키지는 역방향 프록시를 처리하는 기본 제공 방법을 제공하며 ReverseProxy 함수를 통해 코드에서 이를 사용합니다.
func (s *Server) ReverseProxy() *httputil.ReverseProxy { return httputil.NewSingleHostReverseProxy(s.URL) }
역방향 프록시는 클라이언트와 하나 이상의 백엔드 서버 사이에 위치하는 서버입니다. 클라이언트의 요청을 수신하고 이를 백엔드 서버 중 하나로 전달한 다음 서버의 응답을 클라이언트에 반환합니다. 클라이언트는 어떤 특정 백엔드 서버가 요청을 처리하고 있는지 알지 못한 채 프록시와 상호 작용합니다.
우리의 경우 로드 밸런서는 역방향 프록시 역할을 하여 여러 서버 앞에 앉아 수신 HTTP 요청을 분산시킵니다.
클라이언트가 로드 밸런서에 요청하면 getNextServer 함수의 라운드 로빈 알고리즘 구현을 사용하여 사용 가능한 다음 정상 서버를 선택하고 클라이언트 요청을 해당 서버로 프록시합니다. 사용 가능한 정상 서버가 없으면 클라이언트에 서비스 사용 불가 오류를 보냅니다.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { server := lb.getNextServer(servers) if server == nil { http.Error(w, "No healthy server available", http.StatusServiceUnavailable) return } w.Header().Add("X-Forwarded-Server", server.URL.String()) server.ReverseProxy().ServeHTTP(w, r) })
ReverseProxy 메소드는 요청을 실제 서버로 프록시하며 디버깅 목적으로 사용자 정의 헤더 X-Forwarded-Server도 추가합니다(프로덕션에서는 이와 같은 내부 서버 세부 정보가 노출되지 않도록 해야 합니다).
마지막으로 지정된 포트에서 로드 밸런서를 시작합니다.
log.Println("Starting load balancer on port", config.Port) err = http.ListenAndServe(config.Port, nil) if err != nil { log.Fatalf("Error starting load balancer: %s\n", err.Error()) }
이 게시물에서는 라운드 로빈 알고리즘을 사용하여 Golang에서 기본 로드 밸런서를 처음부터 구축했습니다. 이는 여러 서버에 트래픽을 분산하고 시스템이 더 높은 로드를 효율적으로 처리할 수 있도록 하는 간단하면서도 효과적인 방법입니다.
정교한 상태 확인 추가, 다양한 로드 밸런싱 알고리즘 구현, 내결함성 개선 등 살펴볼 내용이 많이 있습니다. 하지만 이 기본적인 예는 구축을 위한 견고한 기반이 될 수 있습니다.
이 GitHub 저장소에서 소스 코드를 찾을 수 있습니다.
부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.
Copyright© 2022 湘ICP备2022001581号-3