"일꾼이 일을 잘하려면 먼저 도구를 갈고 닦아야 한다." - 공자, 『논어』.
첫 장 > 프로그램 작성 > Go에서 간단한 로드 밸런서 구축

Go에서 간단한 로드 밸런서 구축

2024-11-05에 게시됨
검색:273

로드 밸런서는 현대 소프트웨어 개발에 매우 ​​중요합니다. 요청이 여러 서버에 어떻게 분산되는지, 또는 트래픽이 많은 상황에서도 특정 웹사이트가 더 빠르게 느껴지는 이유가 무엇인지 궁금하신 경우, 효율적인 로드 밸런싱에 답이 있는 경우가 많습니다.

Building a simple load balancer in Go

이 게시물에서는 Go의 라운드 로빈 알고리즘을 사용하여 간단한 애플리케이션 로드 밸런서를 구축하겠습니다. 이 게시물의 목적은 로드 밸런서가 내부적으로 어떻게 작동하는지 단계별로 이해하는 것입니다.

로드 밸런서란 무엇입니까?

로드 밸런서는 들어오는 네트워크 트래픽을 여러 서버에 분산시키는 시스템입니다. 단일 서버가 너무 많은 로드를 감당하지 못하도록 하여 병목 현상을 방지하고 전반적인 사용자 경험을 향상시킵니다. 또한 로드 밸런싱 접근 방식은 한 서버에 장애가 발생하면 트래픽이 사용 가능한 다른 서버로 자동으로 다시 라우팅될 수 있도록 보장하여 장애의 영향을 줄이고 가용성을 높입니다.

로드 밸런서를 사용하는 이유는 무엇입니까?

  • 고가용성: 로드 밸런서는 트래픽을 분산함으로써 한 서버에 장애가 발생하더라도 트래픽이 다른 정상 서버로 라우팅될 수 있도록 보장하여 애플리케이션의 탄력성을 더욱 향상시킵니다.
  • 확장성: 로드 밸런서를 사용하면 트래픽이 증가함에 따라 더 많은 서버를 추가하여 시스템을 수평으로 확장할 수 있습니다.
  • 효율성: 모든 서버가 작업 부하를 동일하게 공유하도록 하여 리소스 활용도를 극대화합니다.

로드 밸런싱 알고리즘

트래픽을 분산시키는 다양한 알고리즘과 전략이 있습니다.

  • 라운드 로빈: 사용 가능한 가장 간단한 방법 중 하나입니다. 사용 가능한 서버 간에 요청을 순차적으로 배포합니다. 마지막 서버에 도달하면 처음부터 다시 시작됩니다.
  • 가중 라운드 로빈: 각 서버에 고정된 숫자 가중치가 할당된다는 점을 제외하면 라운드 로빈 알고리즘과 유사합니다. 이 주어진 가중치는 트래픽 라우팅을 위한 서버를 결정하는 데 사용됩니다.
  • 최소 연결: 활성 연결이 가장 적은 서버로 트래픽을 라우팅합니다.
  • IP 해싱: 클라이언트의 IP 주소를 기준으로 서버를 선택합니다.

이 게시물에서는 라운드 로빈 로드 밸런서 구현에 중점을 두겠습니다.

라운드 로빈 알고리즘이란 무엇입니까?

라운드 로빈 알고리즘은 들어오는 각 요청을 순환 방식으로 사용 가능한 다음 서버로 보냅니다. 서버 A가 첫 번째 요청을 처리하면 서버 B가 두 번째 요청을 처리하고 서버 C가 세 번째 요청을 처리합니다. 모든 서버가 요청을 받으면 서버 A에서 다시 시작됩니다.

이제 코드로 넘어가 로드 밸런서를 구축해 보겠습니다.

1단계: 로드 밸런서 및 서버 정의

type LoadBalancer struct {
    Current int
    Mutex   sync.Mutex
}

먼저 다음 요청을 처리해야 하는 서버를 추적하기 위해 Current 필드가 있는 간단한 LoadBalancer 구조체를 정의하겠습니다. Mutex는 우리 코드가 동시에 사용하기에 안전한지 보장합니다.

우리가 로드 밸런싱하는 각 서버는 서버 구조에 의해 정의됩니다:

type Server struct {
    URL       *url.URL
    IsHealthy bool
    Mutex     sync.Mutex
}

여기서 각 서버에는 URL과 서버가 요청을 처리할 수 있는지 여부를 나타내는 IsHealthy 플래그가 있습니다.

2단계: 라운드 로빈 알고리즘

로드 밸런서의 핵심은 라운드 로빈 알고리즘입니다. 작동 방식은 다음과 같습니다.

func (lb *LoadBalancer) getNextServer(servers []*Server) *Server {
    lb.Mutex.Lock()
    defer lb.Mutex.Unlock()

    for i := 0; i 


  • 이 방법은 라운드 로빈 방식으로 서버 목록을 반복합니다. 선택한 서버가 정상이면 들어오는 요청을 처리하기 위해 해당 서버를 반환합니다.
  • 우리는 한 번에 하나의 고루틴만 로드 밸런서의 Current 필드에 액세스하고 수정할 수 있도록 Mutex를 사용하고 있습니다. 이렇게 하면 여러 요청이 동시에 처리될 때 라운드 로빈 알고리즘이 올바르게 작동합니다.
  • 각 서버에는 자체 Mutex도 있습니다. IsHealthy 필드를 확인할 때 여러 고루틴의 동시 액세스를 방지하기 위해 서버의 Mutex를 잠급니다.
  • 뮤텍스 잠금이 없으면 다른 고루틴이 값을 변경하여 부정확하거나 일관되지 않은 데이터를 읽을 수 있습니다.
  • Current 필드를 업데이트하거나 IsHealthy 필드 값을 읽어 임계 섹션을 가능한 한 작게 유지하는 즉시 Mutex의 잠금을 해제합니다. 이러한 방식으로 경쟁 조건을 피하기 위해 Mutex를 사용하고 있습니다.

3단계: 로드 밸런서 구성

구성은 서버 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"
  ]
}

4단계: 상태 확인

우리는 들어오는 트래픽을 서버로 라우팅하기 전에 서버가 건강한지 확인하고 싶습니다. 이는 각 서버에 정기적인 상태 확인을 전송하여 수행됩니다.

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로 설정되어 향후 트래픽이 해당 서버로 라우팅되지 않습니다.

5단계: 역방향 프록시

로드 밸런서는 요청을 받으면 역방향 프록시를 사용하여 사용 가능한 다음 서버로 요청을 전달합니다. Golang에서 httputil 패키지는 역방향 프록시를 처리하는 기본 제공 방법을 제공하며 ReverseProxy 함수를 통해 코드에서 이를 사용합니다.

func (s *Server) ReverseProxy() *httputil.ReverseProxy {
    return httputil.NewSingleHostReverseProxy(s.URL)
}
역방향 프록시란 무엇입니까?

역방향 프록시는 클라이언트와 하나 이상의 백엔드 서버 사이에 위치하는 서버입니다. 클라이언트의 요청을 수신하고 이를 백엔드 서버 중 하나로 전달한 다음 서버의 응답을 클라이언트에 반환합니다. 클라이언트는 어떤 특정 백엔드 서버가 요청을 처리하고 있는지 알지 못한 채 프록시와 상호 작용합니다.

우리의 경우 로드 밸런서는 역방향 프록시 역할을 하여 여러 서버 앞에 앉아 수신 HTTP 요청을 분산시킵니다.

6단계: 요청 처리

클라이언트가 로드 밸런서에 요청하면 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도 추가합니다(프로덕션에서는 이와 같은 내부 서버 세부 정보가 노출되지 않도록 해야 합니다).

7단계: 로드 밸런서 시작

마지막으로 지정된 포트에서 로드 밸런서를 시작합니다.

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())
}

작업 데모

TL;DR

이 게시물에서는 라운드 로빈 알고리즘을 사용하여 Golang에서 기본 로드 밸런서를 처음부터 구축했습니다. 이는 여러 서버에 트래픽을 분산하고 시스템이 더 높은 로드를 효율적으로 처리할 수 있도록 하는 간단하면서도 효과적인 방법입니다.

정교한 상태 확인 추가, 다양한 로드 밸런싱 알고리즘 구현, 내결함성 개선 등 살펴볼 내용이 많이 있습니다. 하지만 이 기본적인 예는 구축을 위한 견고한 기반이 될 수 있습니다.

이 GitHub 저장소에서 소스 코드를 찾을 수 있습니다.

릴리스 선언문 본 글은 https://dev.to/vivekalhat/building-a-simple-load-balancer-in-go-70d?1 에서 복제하였습니다.1 침해 내용이 있는 경우, [email protected]으로 연락하여 삭제해 주시기 바랍니다.
최신 튜토리얼 더>

부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.

Copyright© 2022 湘ICP备2022001581号-3