第21章: ロードバランシング

複数のサーバーが同じリクエストを処理できる場合、ロードバランサーがどのサーバーが各リクエストを受け取るべきかを決定します。効果的なロードバランシングは、他のサーバーがアイドル状態にある間に1台のサーバーがボトルネックにならないことを保証します。分散システムにおけるスケーラビリティと信頼性の両方を達成するための基本的な技術です。

ゲートウェイロードバランサー

私たちのシステムのエントリポイントは、複数のフロントエンドインスタンスの前に位置するゲートウェイロードバランサーです。ヘルスステータスとアクティブ接続数で追跡されるバックエンドのプールを維持します:

完全な実装はloadbalancer/src/main.rsを参照してください。
struct Backend {
    address: String,
    healthy: bool,
    active_connections: usize,
}

struct LoadBalancer {
    backends: Vec<Backend>,
    strategy: String,
    next_index: usize,
}

ロードバランサーはバックエンドを動的に発見します。5秒ごとにバックグラウンドタスクがdiscovery::list("frontend")を呼び出してバックエンドリストを更新します。新しいバックエンドは自動的に追加されます。ディスカバリから消えたバックエンドは削除されます。

バランシング戦略

ゲートウェイは4つの戦略をサポートしており、/__lb_strategyエンドポイントまたはSTRATEGY環境変数で実行時に選択できます:

ラウンドロビンはリクエストを順番に配分し、不健全なバックエンドをスキップします。サーバーとリクエストが均一な場合にうまく機能します:

// round-robin: increment next_index, skip unhealthy
let start = self.next_index;
let total = self.backends.len();
for offset in 0..total {
    let idx = (start + offset) % total;
    if self.backends[idx].healthy {
        self.next_index = (idx + 1) % total;
        return Some(idx);
    }
}

最少接続は各リクエストをアクティブ接続が最も少ない健全なバックエンドに送信します。これは異種のリクエストコストに自然に対応します——遅いリクエストは接続をより長く占有し、後続のトラフィックを他に振り向けます:

// least-connections: pick the healthy backend with fewest active
let mut best = healthy[0];
for &i in &healthy {
    if self.backends[i].active_connections
        < self.backends[best].active_connections
    {
        best = i;
    }
}
Some(best)

ランダムは健全なバックエンドを一様にランダムに選択します。シンプルでステートレスですが、バックエンド数が少ない場合に不均一な分配を生む可能性があります。

二者択一(Pick-2)は特に優雅なアルゴリズムです:2つのランダムな健全なバックエンドを選び、アクティブ接続が少ない方を選択します。研究により、これは純粋なランダム選択よりも指数関数的に良い負荷分配を達成し、最小限の調整で済むことが示されています。

// pick-2: choose 2 random, take the less loaded
let a = healthy[rng.gen_range(0..healthy.len())];
let mut b = a;
while b == a {
    b = healthy[rng.gen_range(0..healthy.len())];
}
if backends[a].active_connections <= backends[b].active_connections {
    Some(a)
} else {
    Some(b)
}

設計の議論

バランシング戦略の選択はワークロードに依存します。ラウンドロビンはリクエストのコストがほぼ等しくサーバーが均一な場合にうまく機能します。最少接続は異種のリクエストコストに自然に適応します。Pick-2はバランスを取り、最少接続のほとんどの利点をランダム選択のシンプルさで提供します。

2層アーキテクチャ——エッジでのゲートウェイバランシングとルーティング層内でのサービスレベルバランシング——は深層防御を提供します。ゲートウェイがシンプルなラウンドロビンを使用していても、ルーティング層は個々のバックエンドサービスへのトラフィックを独立して最適化できます。