複数の負荷分散アルゴリズムとそのJavaコード実装

複数の負荷分散アルゴリズムとそのJavaコード実装

まず、負荷分散とは何かを紹介します(百科事典より)

負荷分散は既存のネットワーク構造に基づいて構築され、ネットワーク デバイスとサーバーの帯域幅を拡張し、スループットを向上させ、ネットワーク データ処理機能を強化し、ネットワークの柔軟性と可用性を向上させるための、安価で効果的かつ透過的な方法を提供します。

負荷分散(英語名は Load Balance)とは、Web サーバー、FTP サーバー、企業の主要なアプリケーション サーバー、その他のミッション クリティカルなサーバーなどの複数の操作ユニットに実行を分散し、作業タスクをまとめて完了することを意味します。

この記事では、「外部から送られてきたリクエストを対称構造で均等にサーバーに分配する」ためのさまざまなアルゴリズムについて説明し、各アルゴリズムの具体的な実装を Java コードで示します。では、本題に入りましょう。本題に入る前に、IP リストをシミュレートするクラスを作成します。

java.util.HashMap をインポートします。

/** * @author ashang.peng @aliyun .com * @date 2017年2月7日 */

パブリッククラスIpMap {
    // ルーティングする IP のリスト。キーは IP を表し、値は IP の重みを表します。
    パブリック静的HashMap<String, Integer> serverWeightMap =
            新しいHashMap<String, Integer>();

    静的
    {
        serverWeightMap.put( "192.168.1.100" , 1 );
        serverWeightMap.put( "192.168.1.101" , 1 );
        // 重みは4
        serverWeightMap.put( "192.168.1.102" , 4 );
        serverWeightMap.put( "192.168.1.103" , 1 );
        serverWeightMap.put( "192.168.1.104" , 1 );
        // 重みは3
        serverWeightMap.put( "192.168.1.105" , 3 );
        serverWeightMap.put( "192.168.1.106" , 1 );
        // 重みは2
        serverWeightMap.put( "192.168.1.107" , 2 );
        serverWeightMap.put( "192.168.1.108" , 1 );
        serverWeightMap.put( "192.168.1.109" , 1 );
        serverWeightMap.put( "192.168.1.110" , 1 );
    }
}

ラウンドロビン

ポーリング スケジューリング アルゴリズムの原理は、ユーザーからの要求を 1 から N (内部サーバーの数) まで順番に内部サーバーに割り当て、その後サイクルを再開することです。このアルゴリズムの利点は、そのシンプルさです。現在のすべての接続のステータスを記録する必要がないため、ステートレス スケジューリングになります。

コードの実装はおおよそ次のようになります。

 java.util.ArrayListをインポートします。
java.util.HashMapをインポートします。
java.util.Mapをインポートします。
java.util.Setをインポートします/** * @author [email protected] * @date 2017年2月7日 */

クラスラウンドロビン{
    プライベート静的整数 pos = 0 ;

    パブリック静的文字列getServer()
    {
        // サーバーの起動と停止による同時実行の問題を回避するためにマップを再構築します
        マップ<文字列、整数> serverMap =
                新しいHashMap< String , Integer>();
        serverMap.putAll(IpMap.serverWeightMap);

        // IPアドレスリストを取得
        <文字列> keySet = serverMap.keySet()を設定します。
        ArrayList< String > keyList =新しいArrayList< String >();
        キーリストにすべてを追加します(キーセット)。

        文字列サーバー = null ;
        同期(正)
        {
            (位置> キーセットのサイズ())
                位置 = 0 ;
            サーバー = keyList.get(pos);
            位置++;
        }

        サーバーを返す。
    }
}

serverWeightMap のアドレス リストは動的であるため、マシンはいつでもオンラインになったり、オフラインになったり、クラッシュしたりする可能性があります。したがって、同時実行の問題を回避するために、メソッド内にローカル変数 serverMap が作成されます。serverMap の内容は、複数のスレッドによって変更されないように、スレッドにローカルにコピーされるようになりました。これにより、新たな問題が発生する可能性があります。レプリケーション後、serverWeightMap への変更は serverMap に反映されません。つまり、このサーバー選択ラウンドでは、負荷分散アルゴリズムは新しいサーバーまたはオフライン サーバーを認識しません。新しいアドレスを追加しても問題ありません。サーバーがオフラインまたはクラッシュしている場合は、存在しないアドレスにアクセスする可能性があります。したがって、サービス呼び出し元は、サーバーの選択と呼び出しの再開など、対応するフォールト トレラント処理を備えている必要があります。

現在のポーリング位置変数 pos については、サーバーの選択順序を保証するために、操作中にロックして、同時に 1 つのスレッドだけが pos の値を変更できるようにする必要があります。そうしないと、pos 変数が同時に変更された場合、サーバーの選択順序が保証されず、keyList 配列が境界を越える可能性もあります。

ラウンドロビン方式の利点は、リクエスト転送の絶対的なバランスを実現しようとすることです。

ポーリング方式の欠点は、リクエスト転送の絶対的なバランスを実現するために、かなりの代償を払わなければならないことです。POS 変数の変更の相互排他性を保証するために、重い悲観的ロック同期を導入する必要があり、これにより、このポーリング コードの同時スループットが大幅に低下します。

ランダム法

システムのランダム アルゴリズムにより、バックエンド サーバーのリスト サイズ値に基づいて、アクセスするサーバーの 1 つがランダムに選択されます。確率統計理論から、クライアントがサーバーを呼び出す回数が増えると、

実際の効果は、ポーリングの結果、バックエンドの各サーバーに呼び出し量が均等に分散されることに近づいています。

ランダム メソッドのコード実装は次のとおりです。

 java.util.ArrayListをインポートします。
java.util.HashMapをインポートします。
java.util.Mapをインポートします。
java.util.Setをインポートします/** * @author [email protected] * @date 2017年2月7日 */

 クラスランダム{
    パブリック静的文字列getServer()
    {
        // サーバーの起動と停止による同時実行の問題を回避するためにマップを再構築します
        マップ<文字列、整数> serverMap =
                新しいHashMap< String , Integer>();
        serverMap.putAll(IpMap.serverWeightMap);

        // IPアドレスリストを取得
        <文字列> keySet = serverMap.keySet()を設定します。
        ArrayList< String > keyList =新しいArrayList< String >();
        キーリストにすべてを追加します(キーセット)。

        java.util.Random ランダム = new java.util.Random();
        int randomPos = random.nextInt(keyList.size());

        keyList.get(randomPos);を返します。
    }
}

全体的なコードのアイデアはポーリング メソッドと一致しています。まず serverMap を再構築し、次にサーバー リストを取得します。サーバーを選択する際は、Random の nextInt メソッドを使用して 0 ~ keyList.size() の範囲でランダムな値を取得し、サーバーリストからサーバーアドレスをランダムに取得して返します。確率統計の理論に基づくと、スループットが大きいほど、ランダム アルゴリズムの効果がポーリング アルゴリズムの効果に近くなります。

送信元アドレスハッシュ方式

ソース アドレス ハッシュの考え方は、クライアントの IP アドレスを取得し、ハッシュ関数を通じて値を計算することです。この値は、サーバー リストのサイズに対してモジュロ演算を実行するために使用されます。結果は、クライアントがアクセスするサーバーのシリアル番号です。負荷分散には送信元アドレスハッシュ方式が使用されます。同じ IP アドレスを持つクライアントの場合、バックエンド サーバー リストが変更されていない場合は、毎回同じバックエンド サーバーにマッピングされてアクセスされます。

送信元アドレス ハッシュ アルゴリズムのコード実装は次のとおりです。

 java.util.ArrayListをインポートします。
java.util.HashMapをインポートします。
java.util.Mapをインポートします。
java.util.Setをインポートします/** * @author [email protected] * @date 2017年2月7日 */

 クラスハッシュ{
    パブリック静的文字列getServer()
    {
        // サーバーの起動と停止による同時実行の問題を回避するためにマップを再構築します
        マップ<文字列、整数> serverMap =
                新しいHashMap< String , Integer>();
        serverMap.putAll(IpMap.serverWeightMap);

        // IPアドレスリストを取得
        <文字列> keySet = serverMap.keySet()を設定します。
        ArrayList< String > keyList =新しいArrayList< String >();
        キーリストにすべてを追加します(キーセット)。

        // Webアプリケーションでは、HttpServletのgetRemoteIpメソッドを通じて取得できます
        文字列remoteIp = "127.0.0.1" ;
        int ハッシュコード = remoteIp.hashCode();
        int serverListSize = keyList.size();
        int serverPos = ハッシュコード % serverListSize;

        keyList.get(serverPos);を返します。
    }
}

最初の2つの部分はポーリング方式とランダム方式と同じなので、詳細は説明しません。違いはルーティングの選択部分にあります。クライアントの IP、つまり remoteIp を通じて、そのハッシュ値を取得し、サーバー リストのサイズを法として計算します。その結果が、サーバー リスト内の選択されたサーバーのインデックス値になります。

ソース アドレス ハッシュ方式の利点は、バックエンド サーバー リストが変更されるまで、同じクライアント IP アドレスが同じバックエンド サーバーにハッシュされることが保証されることです。この機能に基づいて、サービス コンシューマーとサービス プロバイダーの間でステートフル セッションを確立できます。

ソース アドレス ハッシュ アルゴリズムの欠点は、クラスター内のサーバーが非常に安定していて、基本的にオンラインまたはオフラインにならない限り、サーバーがオンラインまたはオフラインになると、ソース アドレス ハッシュ アルゴリズムによってルーティングされたサーバーが、サーバーがオンラインまたはオフラインになる前にルーティングされたサーバーである確率が非常に低くなることです。セッションの場合はセッションが取得できず、キャッシュの場合は「雪崩」が発生する可能性があります。この説明が適切でない場合は、MemCache の非常に詳細な解釈である、一貫性ハッシュ アルゴリズムに関する部分を書いた私の以前の記事を読んでください。

加重ラウンドロビン

バックエンド サーバーによってマシン構成や現在のシステム負荷が異なる場合があり、そのためストレス耐性も異なります。構成が高く負荷が低いマシンに高い重みを割り当てて、より多くのリクエストを処理できるようにします。構成が低く負荷が高いマシンに低い重みを割り当てて、システム負荷を軽減します。重み付けポーリングはこの問題を適切に処理し、重みに従って順番にバックエンドにリクエストを分散します。加重ラウンドロビン方式のコード実装は次のとおりです。

 java.util.*をインポートします/** * @author [email protected] * @date 2017年2月7日 */
クラスWeightRoundRobin {
    プライベート静的整数 pos;

    パブリック静的文字列getServer()
    {
        // サーバーの起動と停止による同時実行の問題を回避するためにマップを再構築します
        マップ<文字列、整数> serverMap =
                新しいHashMap< String , Integer>();
        serverMap.putAll(IpMap.serverWeightMap);

        // IPアドレスリストを取得
        <文字列> keySet = serverMap.keySet()を設定します。
        イテレータ<文字列> iterator = keySet.iterator();

        List< String > serverList =新しいArrayList< String >();
        (イテレータ.hasNext())の間
        {
            文字列サーバー = iterator.next();
            int 重み = serverMap.get(server);
            (int i = 0 ; i < 重み; i++)の場合
                serverList.add(サーバー);
        }

        文字列サーバー = null ;
        同期(正)
        {
            (位置> キーセットのサイズ())
                位置 = 0 ;
            サーバー = serverList.get(pos);
            位置++;
        }

        サーバーを返す。
    }
}

ポーリング方式と似ていますが、サーバー アドレスを取得する前に重み計算コードが追加されます。重みに応じて、アドレスはサーバー アドレス リストに繰り返し追加されます。重みが大きいほど、サーバーが各ラウンドで受信するリクエストが多くなります。

加重ランダム法

加重ラウンドロビン方式と同様に、加重ランダム方式でも、バックエンド マシンの構成とシステムの負荷に基づいて異なる重みが割り当てられます。違いは、順序ではなく重みに応じてバックエンド サーバーをランダムに要求することです。

 java.util.*をインポートします/** * @author [email protected] * @date 2017年2月7日 */

 クラスWeightRandom {
    パブリック静的文字列getServer()
    {
        // サーバーの起動と停止による同時実行の問題を回避するためにマップを再構築します
        マップ<文字列、整数> serverMap =
                新しいHashMap< String , Integer>();
        serverMap.putAll(IpMap.serverWeightMap);

        // IPアドレスリストを取得
        <文字列> keySet = serverMap.keySet()を設定します。
        イテレータ<文字列> iterator = keySet.iterator();

        List< String > serverList =新しいArrayList< String >();
        (イテレータ.hasNext())の間
        {
            文字列サーバー = iterator.next();
            int 重み = serverMap.get(server);
            (int i = 0 ; i < 重み; i++)の場合
                serverList.add(サーバー);
        }

        java.util.Random ランダム = new java.util.Random();
        int randomPos = random.nextInt(serverList.size());

        serverList.get(randomPos);を返します。
    }
}

このコードはランダム方式と重み付けポーリング方式を組み合わせたものと同じです。わかりやすいので説明は省きます。

最小接続法

最小接続数アルゴリズムはより柔軟でインテリジェントです。バックエンド サーバーの構成が異なるため、リクエストの処理速度が異なる場合があります。バックエンド サーバーの現在の接続状態に基づいて、現在の接続数を動的に選択します。

バックログ接続数が最も少ないサーバーが現在のリクエストを処理し、バックエンド サービスの利用効率を最大化し、各サーバーにトラフィックを合理的に分散する役割を担います。

これまでの方法では、サービス コンシューマーの要求時間をバランスよく分散させるために最善を尽くしてきました。もちろん、これは正しいです。複数のバックエンド サーバーのワークロードを均等に分散し、サーバーの使用率を最大化できます。しかし、これは本当にそうでしょうか?実際の状況では、リクエスト時間のバランスは本当に負荷のバランスを表すことができるのでしょうか?これは考える価値のある質問です。

上記の問題を別の観点から見ると、リクエストの開始者ではなく、バックエンド サーバーの観点からシステム負荷を観察することを意味します。最小接続数方式はこのカテゴリに属します。

最小接続数アルゴリズムは、より柔軟でインテリジェントです。バックエンド サーバーの構成が異なるため、リクエストの処理速度は速くなったり遅くなったりする場合があります。バックエンド サーバーの現在の接続状態に基づいて、現在のリクエストを処理するために接続バックログが最も少ないサーバーを動的に選択し、バックエンド サーバーの利用効率を最大化し、各マシンに負荷を合理的に分散します。最小接続数は、サーバー接続数の集計と認識を伴うため、設計と実装がかなり面倒です。そのため、ここでは実装については説明しません。

「NGINX の実装理由」​​の説明を添付します。ご覧ください: blog.csdn.net

<<:  2017 年に注目すべき人工知能の 7 つのホットなトレンド

>>:  Google がディープラーニング ライブラリ TensorFlow Fold をリリース、動的計算グラフをサポート

ブログ    
ブログ    
ブログ    

推薦する

AIを活用した未来における教育の再考

大学を卒業するデータ サイエンティストの数が依然として不足していますが、今後の AI 革命には、AI...

大規模機械学習の台頭と「ゼロトラスト」アーキテクチャの出現、2021年の9つの主要な技術トレンド

[[373625]]このほど、デロイト マネジメント コンサルティングは「2021 年テクノロジー ...

テスラは大きな疑問に直面:オートパイロットは事故の1秒前に自動的に終了

この記事はAI新メディアQuantum Bit(公開アカウントID:QbitAI)より許可を得て転載...

...

...

ターゲット検出にはこの記事で十分です! 2019年版オブジェクト検出の総合ガイド

[[272485]]ビッグデータダイジェスト制作編纂者:張瑞怡、寧静コンピュータ ビジョンは、デジタ...

2020 DIGIXグローバルキャンパスAIアルゴリズムエリートコンペティションが成功裏に終了し、キャンパスのイノベーションを刺激

11月13日〜14日、江蘇省人工知能学会、ファーウェイ端末クラウドサービス、ファーウェイ南京研究所が...

...

決済の未来は生体認証にかかっている

現在、生体認証技術は比較的成熟しており、さまざまな応用シナリオがあります。国内の生体認証市場全体は、...

機械学習が将来の雇用市場にどのような影響を与えるか

機械学習は、あらゆる業界、特に雇用と求人市場に変革をもたらし、エントリーレベルの職からトップレベルの...

...

...

データセンターは大量の電力を消費します。しかしAIはエネルギーを大量に消費する必要はない

世界経済フォーラム(AI が地球を救う 8 つの方法)を含む多くの予測では、人工知能 (AI) が「...

...

ニューラルネットワークの詳細な説明、順方向伝播と逆方向伝播

主にロジスティック回帰について説明します。ロジスティック回帰には多くの基本概念が含まれており、ニュー...