ロード バランサーは、最新のソフトウェア開発において非常に重要です。リクエストがどのように複数のサーバーに分散されるのか、またはトラフィックが多いときでも特定の Web サイトがなぜ速く感じるのか疑問に思ったことがある場合、その答えは効率的な負荷分散にあることがよくあります。
この投稿では、Go の ラウンド ロビン アルゴリズム を使用して、シンプルなアプリケーション ロード バランサーを構築します。この投稿の目的は、ロード バランサーが内部でどのように機能するかを段階的に理解することです。
ロード バランサーは、受信ネットワーク トラフィックを複数のサーバーに分散するシステムです。これにより、単一のサーバーに過大な負荷がかかることがなくなり、ボトルネックが防止され、全体的なユーザー エクスペリエンスが向上します。また、負荷分散アプローチにより、1 つのサーバーに障害が発生した場合、トラフィックが別の利用可能なサーバーに自動的に再ルーティングされるため、障害の影響が軽減され、可用性が向上します。
トラフィックを分散するには、さまざまなアルゴリズムと戦略があります:
この投稿では、ラウンド ロビン ロード バランサの実装に焦点を当てます。
ラウンド ロビン アルゴリズムは、受信した各リクエストを次に利用可能なサーバーに循環的に送信します。サーバー A が最初のリクエストを処理する場合、サーバー B が 2 番目のリクエストを処理し、サーバー C が 3 番目のリクエストを処理します。すべてのサーバーがリクエストを受信すると、サーバー 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 }
設定は config.json ファイルに保存されており、このファイルにはサーバー URL とヘルスチェック間隔が含まれています (詳細については、以下のセクションで説明します)。
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 中国語 Web サイトの他の関連記事を参照してください。