SpringCloud学習(二)-クライアント負荷等化Ribbon(上)

23655 ワード

今日はこのシリーズの第2編で、クライアント負荷等化Ribbonは、登録センターが配置する必要はありません.Spring Cloudごとに構築されたマイクロサービスとインフラストラクチャがほとんど存在し、マイクロサービス間の呼び出し、APIゲートウェイのリクエスト転送などはRibbonによって実現されています.サービス・エンドのロード・バランシングとは異なり、Ribbonはクライアント・ロード・バランシング・ツールであり、クライアントごとにアクセスするサービス・エンド・リストを維持しています.これらのリストはサービス・センターから来ています.まず、Ribbonというクライアント負荷等化ツールの使い方を説明します.
その使用は本当に簡単で、Spring bootの約束が構成より優れているルールのおかげで、Ribbonを使うには2ステップしかかかりません.
1:サービスプロバイダは、複数のサービスインスタンスを起動し、1つまたは複数の関連するサービス登録センターに登録します(これはくだらない話ではありませんか.1つのサービスインスタンスだけが負荷の均衡を話している場合).
二:私たちが要求したRestTemplateオブジェクトに@LoadBalanced注釈を付けるだけでokになり、その後の呼び出しはRestTemplateが提供した方法を正常に使用することで自動的にクライアント負荷の均衡を実現します.
この章の使用部分はあまりにも簡単なので、私は以下に直接Ribbonのソースコードを分析して、どうしてRestTemplateというspringが自分で提供した要求クラスに1つの注釈を加えてクライアントの負荷の均衡を実現することができます
@LoadBalanced注釈ソースコードでは、この注釈は主にRestTemplateにタグ付けされ、LoadBanancerClientで注釈のオブジェクトを構成します.LoadBalancerClientはSpring Cloudで定義されたインタフェースで、次の3つの方法があります(2つのリロード方法があります):
public interface LoadBalancerClient extends ServiceInstanceChooser {
     T execute(String var1, LoadBalancerRequest var2) throws IOException;

     T execute(String var1, ServiceInstance var2, LoadBalancerRequest var3) throws IOException;

    URI reconstructURI(ServiceInstance var1, URI var2);
}
public interface ServiceInstanceChooser {
    ServiceInstance choose(String var1);
}

文字通り、この3つの方法が何に使われているのか推測できます.choose方法は、入力されたサービス名に基づいて負荷イコライザから対応するサービスインスタンスを選択し、execute方法は選択されたサービスインスタンスから要求内容を実行し、reconstructURI方式はサービス名から構築されたURIからhost:portというフォーマットのURIに変換します.
経在org.springframework.cloud.client.loadbalancerパッケージを観察したところ、LoadBalancerAutoConfigurationというクラスはクライアント負荷イコライザを実現する自動化構成クラスであり、ソースコードを表示することができます.
@Configuration
@ConditionalOnClass({RestTemplate.class})
@ConditionalOnBean({LoadBalancerClient.class})
@EnableConfigurationProperties({LoadBalancerRetryProperties.class})
public class LoadBalancerAutoConfiguration {
    @LoadBalanced
    @Autowired(
        required = false
    )
    private List restTemplates = Collections.emptyList();
    @Autowired(
        required = false
    )
    private List transformers = Collections.emptyList();

    public LoadBalancerAutoConfiguration() {
    }

    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializer(final List customizers) {
        return new SmartInitializingSingleton() {
            public void afterSingletonsInstantiated() {
                Iterator var1 = LoadBalancerAutoConfiguration.this.restTemplates.iterator();

                while(var1.hasNext()) {
                    RestTemplate restTemplate = (RestTemplate)var1.next();
                    Iterator var3 = customizers.iterator();

                    while(var3.hasNext()) {
                        RestTemplateCustomizer customizer = (RestTemplateCustomizer)var3.next();
                        customizer.customize(restTemplate);
                    }
                }

            }
        };
    }
       

    @Configuration
    @ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"})
    static class LoadBalancerInterceptorConfig {
        LoadBalancerInterceptorConfig() {
        }

        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }

        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
            return new RestTemplateCustomizer() {
                public void customize(RestTemplate restTemplate) {
                    List list = new ArrayList(restTemplate.getInterceptors());
                    list.add(loadBalancerInterceptor);
                    restTemplate.setInterceptors(list);
                }
            };
        }
    }
}

このクラスの注釈から,Ribbonが実現する負荷等化自動化構成は,RestTemplateクラスが現在のエンジニアリング環境に存在することと,LoadBalancerClientがbeanを実現しなければならないこととの連続条件を満たす必要があることがわかる.
自動化構成クラスでは、主に3つのことを完了しました.1つは、クライアントに要求を開始するときにブロックし、クライアントの負荷の均衡を実現するためにLoadBalancerInterceptorのbeanを作成しました.二つ目はRestTemplate Customizerを作成したBeanであり、RestTemplateにブロッキングを追加するために使用される.三つ目は@LoadBalanced注記で修飾されたRestTemplateリストを維持し、初期化し、RestTemplateインスタンスごとにブロッキングを追加することです.
では、現在のポイントは、LoadBalancerInterceptorブロッカーが通常のRestTemplateをクライアント負荷等化能力を持つようにする方法です.このクラスのソースコードを見てみましょう.
  
public class RetryLoadBalancerInterceptor implements ClientHttpRequestInterceptor {
    private LoadBalancedRetryPolicyFactory lbRetryPolicyFactory;
    private RetryTemplate retryTemplate;
    private LoadBalancerClient loadBalancer;
    private LoadBalancerRetryProperties lbProperties;
    private LoadBalancerRequestFactory requestFactory;

    /** @deprecated */
    @Deprecated
    public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer, RetryTemplate retryTemplate, LoadBalancerRetryProperties lbProperties, LoadBalancedRetryPolicyFactory lbRetryPolicyFactory, LoadBalancerRequestFactory requestFactory) {
        this.loadBalancer = loadBalancer;
        this.lbRetryPolicyFactory = lbRetryPolicyFactory;
        this.retryTemplate = retryTemplate;
        this.lbProperties = lbProperties;
        this.requestFactory = requestFactory;
    }

    public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRetryProperties lbProperties, LoadBalancedRetryPolicyFactory lbRetryPolicyFactory, LoadBalancerRequestFactory requestFactory) {
        this.loadBalancer = loadBalancer;
        this.lbRetryPolicyFactory = lbRetryPolicyFactory;
        this.lbProperties = lbProperties;
        this.requestFactory = requestFactory;
    }

    /** @deprecated */
    @Deprecated
    public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer, RetryTemplate retryTemplate, LoadBalancerRetryProperties lbProperties, LoadBalancedRetryPolicyFactory lbRetryPolicyFactory) {
        this(loadBalancer, retryTemplate, lbProperties, lbRetryPolicyFactory, new LoadBalancerRequestFactory(loadBalancer));
    }

    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
        URI originalUri = request.getURI();
        final String serviceName = originalUri.getHost();
        Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
        final LoadBalancedRetryPolicy retryPolicy = this.lbRetryPolicyFactory.create(serviceName, this.loadBalancer);
        RetryTemplate template = this.retryTemplate == null ? new RetryTemplate() : this.retryTemplate;
        template.setThrowLastExceptionOnExhausted(true);
        template.setRetryPolicy((RetryPolicy)(this.lbProperties.isEnabled() && retryPolicy != null ? new InterceptorRetryPolicy(request, retryPolicy, this.loadBalancer, serviceName) : new NeverRetryPolicy()));
        return (ClientHttpResponse)template.execute(new RetryCallback, IOException>() {
            public ClientHttpResponse doWithRetry(RetryContext context) throws IOException {
                ServiceInstance serviceInstance = null;
                if (context instanceof LoadBalancedRetryContext) {
                    LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext)context;
                    serviceInstance = lbContext.getServiceInstance();
                }

                if (serviceInstance == null) {
                    serviceInstance = RetryLoadBalancerInterceptor.this.loadBalancer.choose(serviceName);
                }

                ClientHttpResponse response = (ClientHttpResponse)RetryLoadBalancerInterceptor.this.loadBalancer.execute(serviceName, serviceInstance, RetryLoadBalancerInterceptor.this.requestFactory.createRequest(request, body, execution));
                int statusCode = response.getRawStatusCode();
                if (retryPolicy != null && retryPolicy.retryableStatusCode(statusCode)) {
                    response.close();
                    throw new RetryableStatusCodeException(serviceName, statusCode);
                } else {
                    return response;
                }
            }
        });
    }
}

ここでは主にブロックにLoadBalancerClientが注入された実装を見ています.ブロックのintercept方法を見ています.実際にはloadBalancerです.executeメソッドの呼び出しは、サービス名に基づいてインスタンスを選択し、実際のリクエストを送信する役割を果たしています.では、次の問題はまた移行します.LoadBalancerClientというインタフェースの実現はいったい何なのでしょうか.クライアントの負荷等化はどのように行われていますか.ソースコードの検索を続け、やっとorg.springframework.cloud.netflix.ribbonこのパッケージで重要な実装クラスが見つかりました:RibbonLoadBalancerClient、以下に示します:(このクラスは比較的膨大なので、その中の重要なexecuteメソッドの実装を切り取ります)
public  T execute(String serviceId, LoadBalancerRequest request) throws IOException {
    ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
    Server server = this.getServer(loadBalancer);
    if (server == null) {
        throw new IllegalStateException("No instances available for " + serviceId);
    } else {
        RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
        return this.execute(serviceId, ribbonServer, request);
    }
}
public  T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request) throws IOException {
    Server server = null;
    if (serviceInstance instanceof RibbonLoadBalancerClient.RibbonServer) {
        server = ((RibbonLoadBalancerClient.RibbonServer)serviceInstance).getServer();
    }

    if (server == null) {
        throw new IllegalStateException("No instances available for " + serviceId);
    } else {
        RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
        RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

        try {
            T returnVal = request.apply(serviceInstance);
            statsRecorder.recordStats(returnVal);
            return returnVal;
        } catch (IOException var8) {
            statsRecorder.recordStats(var8);
            throw var8;
        } catch (Exception var9) {
            statsRecorder.recordStats(var9);
            ReflectionUtils.rethrowRuntimeException(var9);
            return null;
        }
    }
}

この2つのコードを結合することは本当のexecute方法の実現であり,最も主要なコードはこの2つの文であることがわかる.
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
    Server server = this.getServer(loadBalancer);

getServerメソッドをもう一度見てみましょう.
protected Server getServer(ILoadBalancer loadBalancer) {
    return loadBalancer == null ? null : loadBalancer.chooseServer("default");
}

主にILoadBalancerというインタフェースのchooseServerメソッドが呼び出され、LoadBalancerClientのchooseメソッドが使用されていないことがわかります.このような問題が再び移行しました.このILoadBalancerインタフェースを見てみましょう.
public interface ILoadBalancer {
    void addServers(List var1);

    Server chooseServer(Object var1);

    void markServerDown(Server var1);

    /** @deprecated */
    @Deprecated
    List getServerList(boolean var1);

    List getReachableServers();

    List getAllServers();
}

このインタフェースは明らかに1つのクライアント負荷イコライザに必要な一連の抽象的な方法であり、addServerメソッドは負荷イコライザで維持したい実力リストにサービスインスタンスを追加することであり、chooseメソッドはあるポリシー(後述)を通じてインスタンスリストから具体的なサービスインスタンスを選択することである.
このインタフェースにはサーバクラスがあります.このクラスは従来のサービスエンドノードであり、host、portなどのサービスエンドノードのメタ情報が格納されています.次の問題は,このインタフェースの具体的な実装クラスがどれらであるかであり,Spring Cloudのデフォルト実装ではZoneAwareLoadBalancerというクラスを用いて負荷等化を実現していることがソースコードを調べることで分かった.
次に、先ほどのexecuteメソッドに戻ります.サービスインスタンスオブジェクトを取得した後、ribbonServerオブジェクトにパッケージし、apply関数をコールバックして、特定のサービスインスタンスにリクエストを開始します.これにより、サービス名hostというURIでhost:portという形式の変換を実現します.この変換の過程はReconstructURI法の具体的な実装であり,ここでは後述しない.
本編はこれで終わり、次は各種負荷イコライザの実現と各種負荷イコライザの実現について説明し、最後にribbonの構成についても説明します
注:本編内容参考《Spring Cloud微服実務戦》翟永超著