로드 밸런서는 현대 소프트웨어 개발에서 매우 중요합니다. 요청이 여러 서버에 어떻게 분산되는지, 또는 트래픽이 많은 상황에서도 특정 웹사이트가 더 빠르게 느껴지는 이유가 무엇인지 궁금하신가요? 효율적인 로드 밸런싱에 답이 있는 경우가 많습니다.
이번 게시물에서는 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 < 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 }
우리 구성은 서버 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" ] }
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.
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) }
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.
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).
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()) }
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!