「労働者が自分の仕事をうまくやりたいなら、まず自分の道具を研ぎ澄まさなければなりません。」 - 孔子、「論語。陸霊公」
表紙 > プログラミング > Go でシンプルなロードバランサーを構築する

Go でシンプルなロードバランサーを構築する

2024 年 11 月 5 日に公開
ブラウズ:696

ロード バランサーは、最新のソフトウェア開発において非常に重要です。リクエストがどのように複数のサーバーに分散されるのか、またはトラフィックが多いときでも特定の Web サイトがなぜ速く感じるのか疑問に思ったことがある場合、その答えは効率的な負荷分散にあることがよくあります。

Building a simple load balancer in Go

この投稿では、Go の ラウンド ロビン アルゴリズム を使用して、シンプルなアプリケーション ロード バランサーを構築します。この投稿の目的は、ロード バランサーが内部でどのように機能するかを段階的に理解することです。

ロードバランサーとは何ですか?

ロード バランサーは、受信ネットワーク トラフィックを複数のサーバーに分散するシステムです。これにより、単一のサーバーに過大な負荷がかかることがなくなり、ボトルネックが防止され、全体的なユーザー エクスペリエンスが向上します。また、負荷分散アプローチにより、1 つのサーバーに障害が発生した場合でも、トラフィックが別の利用可能なサーバーに自動的に再ルーティングされるため、障害の影響が軽減され、可用性が向上します。

ロードバランサーを使用する理由は何ですか?

  • 高可用性: ロード バランサーはトラフィックを分散することで、1 台のサーバーに障害が発生した場合でもトラフィックを他の正常なサーバーにルーティングできるようにし、アプリケーションの復元力を高めます。
  • スケーラビリティ: ロード バランサを使用すると、トラフィックの増加に応じてサーバーを追加して、システムを水平方向に拡張できます。
  • 効率: すべてのサーバーがワークロードを均等に共有するようにすることで、リソースの使用率を最大化します。

負荷分散アルゴリズム

トラフィックを分散するにはさまざまなアルゴリズムと戦略があります:

  • ラウンドロビン: 利用可能な最も単純な方法の 1 つ。リクエストは利用可能なサーバー間で順番に分散されます。最後のサーバーに到達すると、最初からやり直します。
  • 加重ラウンドロビン: 各サーバーに固定数値の重みが割り当てられることを除いて、ラウンド ロビン アルゴリズムに似ています。この指定された重みは、トラフィックをルーティングするサーバーを決定するために使用されます。
  • 最小接続数: アクティブな接続数が最も少ないサーバーにトラフィックをルーティングします。
  • IP ハッシュ: クライアントの IP アドレスに基づいてサーバーを選択します。

この投稿では、ラウンド ロビンロード バランサの実装に焦点を当てます。

ラウンドロビンアルゴリズムとは何ですか?

ラウンドロビン アルゴリズムは、受信した各リクエストを次に利用可能なサーバーに循環的に送信します。サーバー A が最初のリクエストを処理する場合、サーバー B が 2 番目のリクエストを処理し、サーバー C が 3 番目のリクエストを処理します。すべてのサーバーがリクエストを受信すると、サーバー 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 


  • このメソッドは、サーバーのリストをラウンドロビン方式でループします。選択したサーバーが正常な場合は、そのサーバーを返して受信リクエストを処理します。
  • Mutex を使用して、一度に 1 つの goroutine だけがロード バランサーの Current フィールドにアクセスして変更できるようにしています。これにより、複数のリクエストが同時に処理されるときに、ラウンド ロビン アルゴリズムが正しく動作することが保証されます。
  • 各サーバーには独自のミューテックスもあります。 IsHealthy フィールドをチェックすると、サーバーの Mutex がロックされ、複数のゴルーチンからの同時アクセスが防止されます。
  • Mutex をロックしないと、別の goroutine が値を変更する可能性があり、その結果、不正なデータや一貫性のないデータが読み取られる可能性があります。
  • クリティカル セクションをできるだけ小さく保つために、Current フィールドを更新するか、IsHealthy フィールド値を読み取るとすぐに、ミューテックスのロックを解除します。このようにして、Mutex を使用して競合状態を回避しています。

ステップ 3: ロードバランサーの構成

設定は 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"
  ]
}

ステップ 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)
}
リバースプロキシとは何ですか?

リバース プロキシは、クライアントと 1 つ以上のバックエンド サーバーの間にあるサーバーです。クライアントのリクエストを受信し、それをバックエンド サーバーの 1 つに転送し、サーバーの応答をクライアントに返します。クライアントは、どの特定のバックエンド サーバーがリクエストを処理しているかを認識せずに、プロキシと対話します。

この場合、ロード バランサーはリバース プロキシとして機能し、複数のサーバーの前に配置され、受信した 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 侵害がある場合は、[email protected] に連絡して削除してください。
最新のチュートリアル もっと>

免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。

Copyright© 2022 湘ICP备2022001581号-3