ホームページ  >  記事  >  Java  >  Java は複数の負荷分散アルゴリズムを実装しています

Java は複数の負荷分散アルゴリズムを実装しています

怪我咯
怪我咯オリジナル
2017-04-05 16:10:401265ブラウズ

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

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

負荷分散とは、英語名はLoad Balanceであり、作業タスクをまとめて完了するために、Webサーバー、FTPサーバー、エンタープライズキーアプリケーションサーバー、その他の基幹サーバーなどの複数のオペレーティングユニットに実行を割り当てることを意味します。 。

この記事では、「外部から特定のサーバーに送信されるリクエストを対称構造で均等に分散する」ためのさまざまなアルゴリズムについて説明し、Java コードを使用して各アルゴリズムの具体的な実装を示します。トピックに入る前に、まず IP リストをシミュレートするクラスを記述します:

import java.util.HashMap;

/**
 * @author [email protected]
 * @date 二月 07, 2017
 */

public class IpMap   {
    // 待路由的Ip列表,Key代表Ip,Value代表该Ip的权重
    public static HashMap<String, Integer> serverWeightMap =
            new HashMap<String, Integer>();

    static
    {
        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 まで順番に内部サーバーに割り当てることです (内部サーバーの数)、ループを再起動します。このアルゴリズムの利点は、現在のすべての接続のステータスを記録する必要がないため、ステートレスなスケジューリングであることです。

コードの実装はおおよそ次のとおりです:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author [email protected]
 * @date 二月 07, 2017
 */

class RoundRobin   {
    private static Integer pos = 0;

    public static String getServer()
    {
        // 重建一个Map,避免服务器的上下线导致的并发问题
        Map<String, Integer> serverMap =
                new HashMap<String, Integer>();
        serverMap.putAll(IpMap.serverWeightMap);

        // 取得Ip地址List
        Set keySet = serverMap.keySet();
        ArrayList keyList = new ArrayList();
        keyList.addAll(keySet);

        String server = null;
        synchronized (pos)
        {
            if (pos > keySet.size())
                pos = 0;
            server = keyList.get(pos);
            pos ++;
        }

        return server;
    }
}

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

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

ポーリング方式の利点は、リクエスト転送において絶対的なバランスを達成しようとすることです。

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

ランダム方式

システムのランダムアルゴリズムにより、バックエンドサーバーのリストサイズ値に基づいて、サーバーの 1 つがランダムに選択されてアクセスされます。確率と統計の理論から、クライアントがサーバーを呼び出す回数が増えるにつれて、実際の効果はポーリングの結果である各バックエンド サーバーへの呼び出しを均等に分散することにますます近づいていくことがわかります。 。

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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author [email protected]
 * @date 二月 07, 2017
 */

 class Random   {
    public static String getServer()
    {
        // 重建一个Map,避免服务器的上下线导致的并发问题   
        Map<String, Integer> serverMap =
                new HashMap<String, Integer>();
        serverMap.putAll(IpMap.serverWeightMap);

        // 取得Ip地址List   
        Set keySet = serverMap.keySet();
        ArrayList keyList = new ArrayList();
        keyList.addAll(keySet);

        java.util.Random random = new java.util.Random();
        int randomPos = random.nextInt(keyList.size());

        return keyList.get(randomPos);
    }
}

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

ソースアドレスハッシュ(Hash)メソッド

ソースアドレスハッシュの考え方は、クライアントのIPアドレスに基づいてハッシュ関数によって計算された値を取得し、この値を使用してサーバーリストのサイズを剰余することです結果は、クライアントがサーバーにアクセスしたいシリアル番号です。送信元アドレス ハッシュ方式は、バックエンド サーバーのリストが変更されない限り、同じ IP アドレスを持つクライアントが毎回アクセスするために同じバックエンド サーバーにマップされます。

ソースアドレスハッシュアルゴリズムのコード実装はおおよそ次のとおりです:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author [email protected]
 * @date 二月 07, 2017
 */

 class Hash      {
    public static String getServer()
    {
        // 重建一个Map,避免服务器的上下线导致的并发问题
        Map<String, Integer> serverMap =
                new HashMap<String, Integer>();
        serverMap.putAll(IpMap.serverWeightMap);

        // 取得Ip地址List
        Set keySet = serverMap.keySet();
        ArrayList keyList = new ArrayList();
        keyList.addAll(keySet);

        // 在Web应用中可通过HttpServlet的getRemoteIp方法获取
        String remoteIp = "127.0.0.1";
        int hashCode = remoteIp.hashCode();
        int serverListSize = keyList.size();
        int serverPos = hashCode % serverListSize;

        return keyList.get(serverPos);
    }
}

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

index

値です。 送信元アドレスハッシュの利点は、バックエンドサーバーリストが変更されるまで、同じクライアントIPアドレスが同じバックエンドサーバーにハッシュされることが保証されることです。この機能により、サービス利用者とサービス提供者との間でステートフルセッションを確立することができる。

  源地址哈希算法的缺点在于:除非集群中服务器的非常稳定,基本不会上下线,否则一旦有服务器上线、下线,那么通过源地址哈希算法路由到的服务器是服务器上线、下线前路由到的服务器的概率非常低,如果是session则取不到session,如果是缓存则可能引发"雪崩"。如果这么解释不适合明白,可以看我之前的一篇文章MemCache超详细解读,一致性Hash算法部分。

  加权轮询(Weight Round Robin)法

  不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。加权轮询法的代码实现大致如下:

import java.util.*;

/**
 * @author [email protected]
 * @date 二月 07, 2017
 */
class WeightRoundRobin   {
    private static Integer pos;

    public static String getServer()
    {
        // 重建一个Map,避免服务器的上下线导致的并发问题
        Map<String, Integer> serverMap =
                new HashMap<String, Integer>();
        serverMap.putAll(IpMap.serverWeightMap);

        // 取得Ip地址List
        Set keySet = serverMap.keySet();
        Iterator iterator = keySet.iterator();

        List serverList = new ArrayList();
        while (iterator.hasNext())
        {
            String server = iterator.next();
            int weight = serverMap.get(server);
            for (int i = 0; i < weight; i++)
                serverList.add(server);
        }

        String server = null;
        synchronized (pos)
        {
            if (pos > keySet.size())
                pos = 0;
            server = serverList.get(pos);
            pos ++;
        }

        return server;
    }
}

  与轮询法类似,只是在获取服务器地址之前增加了一段权重计算的代码,根据权重的大小,将地址重复地增加到服务器地址列表中,权重越大,该服务器每轮所获得的请求数量越多。

  加权随机(Weight Random)法

  与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。

import java.util.*;

/**
 * @author [email protected]
 * @date 二月 07, 2017
 */

 class WeightRandom   {
    public static String getServer()
    {
        // 重建一个Map,避免服务器的上下线导致的并发问题
        Map<String, Integer> serverMap =
                new HashMap<String, Integer>();
        serverMap.putAll(IpMap.serverWeightMap);

        // 取得Ip地址List
        Set keySet = serverMap.keySet();
        Iterator iterator = keySet.iterator();

        List serverList = new ArrayList();
        while (iterator.hasNext())
        {
            String server = iterator.next();
            int weight = serverMap.get(server);
            for (int i = 0; i < weight; i++)
                serverList.add(server);
        }

        java.util.Random random = new java.util.Random();
        int randomPos = random.nextInt(serverList.size());

        return serverList.get(randomPos);
    }
}

  这段代码相当于是随机法和加权轮询法的结合,比较好理解,就不解释了。

  最小连接数(Least Connections)法

  最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它是根据后端服务器当前的连接情况,动态地选取其中当前

  积压连接数最少的一台服务器来处理当前的请求,尽可能地提高后端服务的利用效率,将负责合理地分流到每一台服务器。

  前面几种方法费尽心思来实现服务消费者请求次数分配的均衡,当然这么做是没错的,可以为后端的多台服务器平均分配工作量,最大程度地提高服务器的利用率,但是实际情况是否真的如此?实际情况中,请求次数的均衡真的能代表负载的均衡吗?这是一个值得思考的问题。

  上面的问题,再换一个角度来说就是:以后端服务器的视角来观察系统的负载,而非请求发起方来观察。最小连接数法便属于此类。

  最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它正是根据后端服务器当前的连接情况,动态地选取其中当前积压连接数最少的一台服务器来处理当前请求,尽可能地提高后端服务器的利用效率,将负载合理地分流到每一台机器。由于最小连接数设计服务器连接数的汇总和感知,设计与实现较为繁琐,此处就不说它的实现了。


以上がJava は複数の負荷分散アルゴリズムを実装していますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。