ロード バランサーは、最新のソフトウェア開発において非常に重要です。リクエストがどのように複数のサーバーに分散されるのか、またはトラフィックが多いときでも特定の 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
設定は 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" ] }
受信トラフィックをルーティングする前に、サーバーが正常であることを確認したいと考えています。これは、定期的なヘルスチェックを各サーバーに送信することで行われます:
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) }
リバース プロキシは、クライアントと 1 つ以上のバックエンド サーバーの間にあるサーバーです。クライアントのリクエストを受信し、それをバックエンド サーバーの 1 つに転送し、サーバーの応答をクライアントに返します。クライアントは、どの特定のバックエンド サーバーがリクエストを処理しているかを認識せずに、プロキシと対話します。
この場合、ロード バランサーはリバース プロキシとして機能し、複数のサーバーの前に配置され、受信した 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