リボンカスタム負荷分散アルゴリズム

リボンとは?

リボンはNetflixがリリースしたオープンソースプロジェクトであり、その主な機能は、クライアント側のソフトウェア負荷分散アルゴリズムを提供し、Netflixの中間層サービスを相互に接続することです。リボンクライアントコンポーネントは、接続タイムアウト、再試行などの一連の完全な構成アイテムを提供します。簡単に言えば、構成ファイルにロードバランサー(略してLB)の背後にあるすべてのマシンを一覧表示することです。リボンは、特定のルール(単純なポーリング、ランダム接続など)に基づいてこれらのマシンを自動的に接続するのに役立ちます。リボンを使用してカスタムの負荷分散アルゴリズムを実装することも簡単です。

負荷分散

負荷分散。負荷(作業タスク、アクセス要求)を分散し、実行のために複数の操作ユニット(サーバー、コンポーネント)に分散することを意味します。これは、高性能、単一障害点(高可用性)、スケーラビリティ(水平スケーラビリティ)を解決するための究極のソリューションです。

ここに画像の説明を挿入します

一元化されたLBと処理中のLB

また、負荷分散は、集中型負荷分散とインプロセス負荷分散の2つのカテゴリに分けられます。集中型負荷分散とインプロセス負荷分散とは何ですか。

一元化された負荷分散
は、サービスのコンシューマーとプロバイダーの間で独立した負荷分散機能(F5などのハードウェアまたはnginxなどのソフトウェア)を使用し、機能は特定の戦略を通じてアクセス要求を転送する責任があります。サービスプロバイダー

インプロセス負荷分散
は、負荷分散ロジックをコンシューマーに統合します。コンシューマーは、サービスレジストリから使用可能なアドレスを学習し、これらのアドレスから適切なサーバーを選択します。たとえば、リボン、リボンは、に統合された単なるクラスライブラリです。コンシューマーコンシューマーがサービスプロバイダーのアドレスを取得するプロセス

Riboonはどのようにして負荷分散を実現しますか?

リボンの概略図を見てください。
以下の概略図からサービスプロバイダーがサービスをレジストリEurekaに登録し、サービスコンシューマーがリボンを使用してサービスレジストリに移動し、リストを取得してクエリを実行していることがわかります。これらの利用可能なサービスのうち、呼び出される呼び出しに応じて、そのサービスは負荷分散要求を行います
ここに画像の説明を挿入します

リボン独自の負荷分散アルゴリズム

ここに画像の説明を挿入します
リボンには7つの負荷分散アルゴリズムが付属しています

1. RoundRobinRuleの
デフォルトのポーリングルールは、多くの高度なルールを回避するための戦略でもあります。

2. AvailabilityFilteringRule
は、ヒューズに対して開いているサービス、または同時接続の数が多いサービスを除外します

3. WeightedResponseTimeRule
は、サービスの平均応答時間に基づいて各サービスに重みを与えます。応答時間が長いほど、重みは小さくなります。統計が不十分な場合は、ポーリング戦略が適用されます。

4. RetryRuleは
最初にポーリング戦略に従いますリクエストサービスが失敗した場合、指定された時間内に再試行します。

5. BestAvailableRuleは、
最初に回路ブレーカーのサービスをフィルターで除外し、次に同時実行性が最も低いサービスを選択します

6.RandomRuleは
ランダムにサービスを取得します

7. ZoneAvoidanceRuleは
、パフォーマンスと可用性に応じて選択されます。

リボンはデフォルトでポーリングアルゴリズムを使用しますが、それを変更するにはどうすればよいですか?

最初のステップは
カスタムクラス追加することですが、保存場所に注意してください。このカスタムクラスは、@ ComponentScanによってスキャンされる現在のパッケージおよびサブパッケージの下に配置できません。そうしないと、カスタム構成クラスがすべてのリボンクライアントで使用されます。つまり、スペシャライゼーションの目的を達成できません。つまり、メインスタートアップと同じパッケージおよびサブパッケージに配置することはできません。
ここに画像の説明を挿入します
ステップ2
クライアントにコントローラを追加して、サービスを呼び出します。

@GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id")Long id){
    
    
        return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
    }

3番目のステップは
、3つのサービス追加し、それらをサービスセンターに登録することです。
ここに画像の説明を挿入します
コンシューマーモジュールの/ Consumer / Payment / get / {id}を呼び出すと、ランダムなサービスが正常に表示されます。
ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します

独自の負荷分散アルゴリズムを使用したい場合はどうなりますか?

リボンの負荷分散アルゴリズムの構造を見てみましょう。それはどのように書かれていますか?

最初にこのインターフェースを見てみましょう。このインターフェースはリボン独自の負荷分散アルゴリズムによって実装されています。
ここに画像の説明を挿入します
彼の実装クラスを見てください。この抽象クラスはIRuleを実装しています。抽象クラスはインターフェースを実装して、サブクラスがこのインターフェースを簡単に実装できるようにします。この抽象クラスは、IRuleインターフェイスにsetLoadBalancerとgetLoadBalancerのみを実装し、取得した値を割り当てて保存し、サブクラスの使用を容易にします。これら2つのメソッドは、サービスセンターのサービス情報を取得し、
ここに画像の説明を挿入します
次に確認するものと見なすことができます。リボン。彼自身の負荷分散アルゴリズムはどのように実装されていますか?上記の抽象クラスを実装するとき、彼
ここに画像の説明を挿入します
は最初にこの選択を呼び出します。この選択は、親クラスAbstractLoadBalancerRuleのgetLoadBalancerを呼び出します。前述のように、これは次のサービスと見なすことができます。サービスセンターの情報
ここに画像の説明を挿入します
を取得し、chooseのオーバーロードメソッドを
ここに画像の説明を挿入します
呼び出します。lbを使用してライブ(アクティブ)サービスを取得し、すべてのサービスを取得してサービス数の長さ取得してから、chooseRandomInt()を呼び出してランダムな添え字を取得します。サービスの数の範囲内で、[存続するサービスのリストを保存する]を呼び出して、添え字を使用して存続するサービスを
ここに画像の説明を挿入します
取得し、取得したサービスが有効であるかどうかを判断します。有効な場合は、呼び出し元にサービスを返して呼び出します。が生きていない場合は、Thread.yield()を実行します。スレッドは降伏し、次のサイクルが次のサービスを受けるのを待ちます
ここに画像の説明を挿入します

Thread.yield()の説明

JavaスレッドのThread.yield()メソッドは、スレッドの譲歩として変換されます。名前が示すように、スレッドがこのメソッドを使用すると、CPUの実行時間が放棄され、
自分自身または他のスレッドが実行されます。単に他のスレッドに渡すのではなく、自分自身または他のスレッドを実行するように注意してください。
yield()の機能は譲歩することです。これにより、現在のスレッドの「実行状態」を「準備完了状態」にすることができるため、同じ優先順位を持つ他の待機中のスレッドが実行権限にアクセスできます。ただし、
現在のスレッドの証明書がyield()を呼び出した後、他のスレッドは同じ優先順位第1レベルのスレッドは確実に実行権を取得します。また、現在のスレッドが「実行状態」に入って実行を継続する可能性もあります。

カスタム負荷分散を開始します

上記のソースコード分析から、負荷分散アルゴリズムをカスタマイズするには、 IRuleの抽象実装クラスAbstractLoadBalancerRuleを継承する必要があることわかります。したがって、それを実装するクラスを記述して、サーバーchoose(ILoadBalancer)でルール設定することもできます。 lb、オブジェクトキー)

付属のリボンを使用する代わりに、最初にポーリングをカスタマイズしましょう

AbstractLoadBalancerRuleを継承するクラスを作成してから、組み込みのリボンを模倣し、選択のオーバーロードメソッドを作成します。注:** @ Component **アノテーションを追加することを忘れないでください。そうしないと、SpringBootはスキャンしません。

@Component
public class MyRoundRobinRule extends AbstractLoadBalancerRule {
    
    
    //定义一个原子类,以保证原子性
    private AtomicInteger atomicInteger =new AtomicInteger(0);

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {
    
    
    }

    public Server choose(ILoadBalancer lb, Object key) {
    
    
        if (lb == null){
    
    
            return null;
        }
        //用于统计获取次数,当达到一定数量就不再去尝试
        int count = 0;
        Server server = null;
        //当服务还没获取到,并且尝试没有超过8次
        while (server == null && count++ < 8){
    
    
            //获取服务
            List<Server> allServers = lb.getAllServers();
            List<Server> reachableServers = lb.getReachableServers();
            int allServersSize = allServers.size();
            int reachableServersSize = reachableServers.size();
            //如果获取的服务list都为0就返回null
            if(allServersSize == 0 || reachableServersSize == 0){
    
    
                return  null;
            }
            //获取服务下标
            int next = getServerIndex(allServersSize);

            //获取服务
            server = reachableServers.get(next);

            //如果服务为空直接跳过下面的
            if (server == null){
    
    
                continue;
            }

            //如果获取到的这个服务是活着的就返回
            if (server.isAlive()){
    
    
                return server;
            }

            //如果获取到的服务不是空,但是不是存活状态,需要重新获取
            server=null;
        }

        //最后这里可能会返回null
        return  server;
    }

    //获取服务下标,为了保证原子性,使用了CAS
    public int getServerIndex(int allServersSize){
    
    
        //自旋锁
        for (;;) {
    
    
            //获取当前值
            int current = this.atomicInteger.get();
            //设置期望值
            int next = (current + 1) % allServersSize;
            //调用Native方法compareAndSet,执行CAS操作
            if (this.atomicInteger.compareAndSet(current, next))
                //成功后才会返回期望值,否则无线循环
                return next;
        }
    }
    @Override
    public Server choose(Object key) {
    
    

        return choose(getLoadBalancer(),key);
    }
}


@RibbonClient(name = "CLOUD-PAYMENT-SERVICE"、configuration = MyRoundRobinRule.class)
構成をメインのスタートアップクラスに

追加して、独自の負荷分散戦略指定します@LoadBalancedをRestTemplateに追加します
ここに画像の説明を挿入します

テストを開始すると、ポーリングが正常に表示されます。

ここに画像の説明を挿入しますここに画像の説明を挿入します
ここに画像の説明を挿入します

おすすめ

転載: blog.csdn.net/weixin_46334920/article/details/114867996