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

WBOY
풀어 주다: 2024-09-07 22:30:37
원래의
503명이 탐색했습니다.

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

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 < len(servers); i++ {
        idx := lb.Current % len(servers)
        nextServer := servers[idx]
        lb.Current++

        nextServer.Mutex.Lock()
        isHealthy := nextServer.IsHealthy
        nextServer.Mutex.Unlock()

        if isHealthy {
            return nextServer
        }
    }

    return nil
}
로그인 후 복사
  • 이 방법은 라운드 로빈 방식으로 서버 목록을 반복합니다. 선택한 서버가 정상이면 들어오는 요청을 처리하기 위해 해당 서버를 반환합니다.
  • 우리는 한 번에 하나의 고루틴만 로드 밸런서의 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"
  ]
}
로그인 후 복사

Step 4: Health Checks

We want to make sure that the servers are healthy before routing any incoming traffic to them. This is done by sending periodic health checks to each server:

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()
    }
}
로그인 후 복사

Every few seconds (as specified in the config), the load balancer sends a HEAD request to each server to check if it is healthy. If a server is down, the IsHealthy flag is set to false, preventing future traffic from being routed to it.

Step 5: Reverse Proxy

When the load balancer receives a request, it forwards the request to the next available server using a reverse proxy. In Golang, the httputil package provides a built-in way to handle reverse proxying, and we will use it in our code through the ReverseProxy function:

func (s *Server) ReverseProxy() *httputil.ReverseProxy {
    return httputil.NewSingleHostReverseProxy(s.URL)
}
로그인 후 복사
What is a Reverse Proxy?

A reverse proxy is a server that sits between a client and one or more backend severs. It receives the client's request, forwards it to one of the backend servers, and then returns the server's response to the client. The client interacts with the proxy, unaware of which specific backend server is handling the request.

In our case, the load balancer acts as a reverse proxy, sitting in front of multiple servers and distributing incoming HTTP requests across them.

Step 6: Handling Requests

When a client makes a request to the load balancer, it selects the next available healthy server using the round robin algorithm implementation in getNextServer function and proxies the client request to that server. If no healthy server is available then we send service unavailable error to the client.

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)
    })
로그인 후 복사

The ReverseProxy method proxies the request to the actual server, and we also add a custom header X-Forwarded-Server for debugging purposes (though in production, we should avoid exposing internal server details like this).

Step 7: Starting the Load Balancer

Finally, we start the load balancer on the specified port:

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())
}
로그인 후 복사

Working Demo

TL;DR

In this post, we built a basic load balancer from scratch in Golang using a round robin algorithm. This is a simple yet effective way to distribute traffic across multiple servers and ensure that your system can handle higher loads efficiently.

There's a lot more to explore, such as adding sophisticated health checks, implementing different load balancing algorithms, or improving fault tolerance. But this basic example can be a solid foundation to build upon.

You can find the source code in this GitHub repo.

위 내용은 Go에서 간단한 로드 밸런서 구축의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!