客戶(hù)端負(fù)載均衡Ribbon之源碼解析

文章首發(fā)于微信公眾號(hào)《程序員果果》
地址:https://mp.weixin.qq.com/s/seYEMxztE2ZqfmaI8pO-7w
本篇源碼:https://github.com/gf-huanchupk/SpringBootLearning

什么是負(fù)載均衡器?

假設(shè)有一個(gè)分布式系統(tǒng),該系統(tǒng)由在不同計(jì)算機(jī)上運(yùn)行的許多服務(wù)組成。但是,當(dāng)用戶(hù)數(shù)量很大時(shí),通常會(huì)為服務(wù)創(chuàng)建多個(gè)副本。每個(gè)副本都在另一臺(tái)計(jì)算機(jī)上運(yùn)行。此時(shí),出現(xiàn) “Load Balancer(負(fù)載均衡器)”。它有助于在服務(wù)器之間平均分配傳入流量。

服務(wù)器端負(fù)載均衡器

傳統(tǒng)上,Load Balancers(例如Nginx、F5)是放置在服務(wù)器端的組件。當(dāng)請(qǐng)求來(lái)自 客戶(hù)端 時(shí),它們將轉(zhuǎn)到負(fù)載均衡器,負(fù)載均衡器將為請(qǐng)求指定 服務(wù)器。負(fù)載均衡器使用的最簡(jiǎn)單的算法是隨機(jī)指定。在這種情況下,大多數(shù)負(fù)載平衡器是用于控制負(fù)載平衡的硬件集成軟件。

重點(diǎn):

  • 對(duì)客戶(hù)端不透明,客戶(hù)端不知道服務(wù)器端的服務(wù)列表,甚至不知道自己發(fā)送請(qǐng)求的目標(biāo)地址存在負(fù)載均衡器。
  • 服務(wù)器端維護(hù)負(fù)載均衡服務(wù)器,控制負(fù)載均衡策略和算法。

客戶(hù)端負(fù)載均衡器

當(dāng)負(fù)載均衡器位于 客戶(hù)端 時(shí),客戶(hù)端得到可用的服務(wù)器列表然后按照特定的負(fù)載均衡策略,分發(fā)請(qǐng)求到不同的 服務(wù)器

重點(diǎn):

  • 對(duì)客戶(hù)端透明,客戶(hù)端需要知道服務(wù)器端的服務(wù)列表,需要自行決定請(qǐng)求要發(fā)送的目標(biāo)地址。
  • 客戶(hù)端維護(hù)負(fù)載均衡服務(wù)器,控制負(fù)載均衡策略和算法。
  • 目前單獨(dú)提供的客戶(hù)端實(shí)現(xiàn)比較少( 我用過(guò)的只有Ribbon),大部分都是在框架內(nèi)部自行實(shí)現(xiàn)。

Ribbon

簡(jiǎn)介

Ribbon是Netflix公司開(kāi)源的一個(gè)客戶(hù)單負(fù)載均衡的項(xiàng)目,可以自動(dòng)與 Eureka 進(jìn)行交互。它提供下列特性:

  • 負(fù)載均衡
  • 容錯(cuò)
  • 以異步和反應(yīng)式模型執(zhí)行多協(xié)議 (HTTP, TCP, UDP)
  • 緩存和批量

Ribbon中的關(guān)鍵組件

  • ServerList:可以響應(yīng)客戶(hù)端的特定服務(wù)的服務(wù)器列表。
  • ServerListFilter:可以動(dòng)態(tài)獲得的具有所需特征的候選服務(wù)器列表的過(guò)濾器。
  • ServerListUpdater:用于執(zhí)行動(dòng)態(tài)服務(wù)器列表更新。
  • Rule:負(fù)載均衡策略,用于確定從服務(wù)器列表返回哪個(gè)服務(wù)器。
  • Ping:客戶(hù)端用于快速檢查服務(wù)器當(dāng)時(shí)是否處于活動(dòng)狀態(tài)。
  • LoadBalancer:負(fù)載均衡器,負(fù)責(zé)負(fù)載均衡調(diào)度的管理。

源碼分析

LoadBalancerClient

實(shí)際應(yīng)用中,通常將 RestTemplate 和 Ribbon 結(jié)合使用,例如:

@Configuration
public class RibbonConfig {
    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

消費(fèi)者調(diào)用服務(wù)接口:

@Service
public class RibbonService {
    @Autowired
    private RestTemplate restTemplate;
    public String hi(String name) {
        return restTemplate.getForObject("http://service-hi/hi?name="+name,String.class);
    }
}

@LoadBalanced,通過(guò)源碼可以發(fā)現(xiàn)這是一個(gè)標(biāo)記注解:

/**
 * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
 * @author Spencer Gibb
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}

通過(guò)注釋可以知道@LoadBalanced注解是用來(lái)給RestTemplate做標(biāo)記,方便我們對(duì)RestTemplate添加一個(gè)LoadBalancerClient,以實(shí)現(xiàn)客戶(hù)端負(fù)載均衡。

根據(jù)spring boot的自動(dòng)配置原理,可以知道同包下的LoadBalancerAutoConfiguration,應(yīng)該是實(shí)現(xiàn)客戶(hù)端負(fù)載均衡器的自動(dòng)化配置類(lèi)。代碼如下:

@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

    @LoadBalanced
    @Autowired(required = false)
    private List<RestTemplate> restTemplates = Collections.emptyList();

    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
            final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
        return () -> restTemplateCustomizers.ifAvailable(customizers -> {
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for (RestTemplateCustomizer customizer : customizers) {
                    customizer.customize(restTemplate);
                }
            }
        });
    }

    @Autowired(required = false)
    private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

    @Bean
    @ConditionalOnMissingBean
    public LoadBalancerRequestFactory loadBalancerRequestFactory(
            LoadBalancerClient loadBalancerClient) {
        return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
    }

    @Configuration
    @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
    static class LoadBalancerInterceptorConfig {
        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(
                LoadBalancerClient loadBalancerClient,
                LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }
    
        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final LoadBalancerInterceptor loadBalancerInterceptor) {
            return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }
    }
    
    @Configuration
    @ConditionalOnClass(RetryTemplate.class)
    public static class RetryAutoConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public LoadBalancedRetryFactory loadBalancedRetryFactory() {
            return new LoadBalancedRetryFactory() {};
        }
    }
    
    @Configuration
    @ConditionalOnClass(RetryTemplate.class)
    public static class RetryInterceptorAutoConfiguration {
        @Bean
        @ConditionalOnMissingBean
        public RetryLoadBalancerInterceptor ribbonInterceptor(
                LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
                LoadBalancerRequestFactory requestFactory,
                LoadBalancedRetryFactory loadBalancedRetryFactory) {
            return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
                    requestFactory, loadBalancedRetryFactory);
        }

        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
            return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }
    }
}

從代碼可以看出,這個(gè)類(lèi)作用主要是使用RestTemplateCustomizer對(duì)所有標(biāo)注了@LoadBalanced的RestTemplate Bean添加了一個(gè)LoadBalancerInterceptor攔截器,而這個(gè)攔截器的作用就是對(duì)請(qǐng)求的URI進(jìn)行轉(zhuǎn)換獲取到具體應(yīng)該請(qǐng)求哪個(gè)服務(wù)實(shí)例。

那再看看添加的攔截器LoadBalancerInterceptor的代碼,如下:

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

    private LoadBalancerClient loadBalancer;
    private LoadBalancerRequestFactory requestFactory;

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
        this.loadBalancer = loadBalancer;
        this.requestFactory = requestFactory;
    }

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
        // for backwards compatibility
        this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
    }

    @Override
    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
            final ClientHttpRequestExecution execution) throws IOException {
        final URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
        return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
    }
}

從代碼可以看出 LoadBalancerInterceptor 攔截了請(qǐng)求后,通過(guò)LoadBalancerClient執(zhí)行具體的請(qǐng)求發(fā)送。

打開(kāi)LoadBalancerClient,發(fā)現(xiàn)它是一個(gè)接口:

public interface LoadBalancerClient {

    ServiceInstance choose(String serviceId);

    <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

    <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;

    URI reconstructURI(ServiceInstance instance, URI original);
}

接口說(shuō)明:

  • ServiceInstance choose(String serviceId):根據(jù)傳入的服務(wù)id,從負(fù)載均衡器中為指定的服務(wù)選擇一個(gè)服務(wù)實(shí)例。
  • <T> T execute(String serviceId, LoadBalancerRequest<T> request):根據(jù)傳入的服務(wù)id,指定的負(fù)載均衡器中的服務(wù)實(shí)例執(zhí)行請(qǐng)求。
  • <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request):根據(jù)傳入的服務(wù)實(shí)例,執(zhí)行請(qǐng)求。

LoadBalancerClient 有一個(gè)唯一的實(shí)現(xiàn)類(lèi) RibbonLoadBalancerClient,關(guān)鍵代碼如下:

public class RibbonLoadBalancerClient implements LoadBalancerClient {

    public ServiceInstance choose(String serviceId) {
        Server server = this.getServer(serviceId);
        return server == null ? null : new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
    }
    
    public <T> T execute(String serviceId, LoadBalancerRequest<T> 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);
        }
    }
    
    protected Server getServer(String serviceId) {
        return this.getServer(this.getLoadBalancer(serviceId));
    }
    
    protected Server getServer(ILoadBalancer loadBalancer) {
        return loadBalancer == null ? null : loadBalancer.chooseServer("default");
    }
    
    protected ILoadBalancer getLoadBalancer(String serviceId) {
        return this.clientFactory.getLoadBalancer(serviceId);
    }
    
    //省略...

}

負(fù)載均衡器

從 RibbonLoadBalancerClient 代碼可以看出,實(shí)際負(fù)載均衡的是通過(guò) ILoadBalancer 來(lái)實(shí)現(xiàn)的。

ILoadBalancer 接口代碼如下:

public interface ILoadBalancer {

    public void addServers(List<Server> newServers);

    public Server chooseServer(Object key);

    public void markServerDown(Server server);

    public List<Server> getReachableServers();

    public List<Server> getAllServers();
}

接口說(shuō)明:

  • addServers:向負(fù)載均衡器中添加一個(gè)服務(wù)實(shí)例集合。
  • chooseServer:跟據(jù)key,從負(fù)載均衡器獲取服務(wù)實(shí)例。
  • markServerDown:用來(lái)標(biāo)記某個(gè)服務(wù)實(shí)例下線。
  • getReachableServers:獲取可用的服務(wù)實(shí)例集合。
  • getAllServers():獲取所有服務(wù)實(shí)例集合,包括下線的服務(wù)實(shí)例。

ILoadBalancer 的實(shí)現(xiàn) 依賴(lài)關(guān)系示意圖如下:

  • NoOpLoadBalancer:啥都不做
  • BaseLoadBalancer:
  • 一個(gè)負(fù)載均衡器的基本實(shí)現(xiàn),其中有一個(gè)任意列表,可以將服務(wù)器設(shè)置為服務(wù)器池。
  • 可以設(shè)置一個(gè)ping來(lái)確定服務(wù)器的活力。
  • 在內(nèi)部,該類(lèi)維護(hù)一個(gè)“all”服務(wù)器列表,以及一個(gè)“up”服務(wù)器列表,并根據(jù)調(diào)用者的要求使用它們。
  • DynamicServerListLoadBalancer:
  • 通過(guò)動(dòng)態(tài)的獲取服務(wù)器的候選列表的負(fù)載平衡器。
  • 可以通過(guò)篩選標(biāo)準(zhǔn)來(lái)傳遞服務(wù)器列表,以過(guò)濾不符合所需條件的服務(wù)器。
  • ZoneAwareLoadBalancer:
  • 用于測(cè)量區(qū)域條件的關(guān)鍵指標(biāo)是平均活動(dòng)請(qǐng)求,它根據(jù)每個(gè)rest客戶(hù)機(jī)和每個(gè)區(qū)域聚合。這是區(qū)域內(nèi)未完成的請(qǐng)求總數(shù)除以可用目標(biāo)實(shí)例的數(shù)量(不包括斷路器跳閘實(shí)例)。當(dāng)在壞區(qū)上緩慢發(fā)生超時(shí)時(shí),此度量非常有效。
  • 該負(fù)載均衡器將計(jì)算并檢查所有可用區(qū)域的區(qū)域狀態(tài)。如果任何區(qū)域的平均活動(dòng)請(qǐng)求已達(dá)到配置的閾值,則該區(qū)域?qū)幕顒?dòng)服務(wù)器列表中刪除。如果超過(guò)一個(gè)區(qū)域達(dá)到閾值,則將刪除每個(gè)服務(wù)器上活動(dòng)請(qǐng)求最多的區(qū)域。一旦去掉最壞的區(qū)域,將在其余區(qū)域中選擇一個(gè)區(qū)域,其概率與其實(shí)例數(shù)成正比。服務(wù)器將使用給定的規(guī)則從所選區(qū)域返回。對(duì)于每個(gè)請(qǐng)求,將重復(fù)上述步驟。也就是說(shuō),每個(gè)與區(qū)域相關(guān)的負(fù)載平衡決策都是實(shí)時(shí)做出的,最新的統(tǒng)計(jì)數(shù)據(jù)可以幫助進(jìn)行選擇。

那么在整合Ribbon的時(shí)候Spring Cloud默認(rèn)采用了哪個(gè)具體實(shí)現(xiàn)呢?我們通過(guò)RibbonClientConfiguration配置類(lèi),可以知道在整合時(shí)默認(rèn)采用了ZoneAwareLoadBalancer來(lái)實(shí)現(xiàn)負(fù)載均衡器。

@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
    return (ILoadBalancer)(this.propertiesFactory
    .isSet(ILoadBalancer.class, this.name) ? (ILoadBalancer)this.propertiesFactory
    .get(ILoadBalancer.class, config, this.name) : new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater));
}

從這段代碼 ,也可以看出,負(fù)載均衡器所需的主要配置項(xiàng)是IClientConfig, ServerList, ServerListFilter, IRule, IPing, ServerListUpdater。下面逐一分析他們。

IClientConfig

IClientConfig 用于對(duì)客戶(hù)端或者負(fù)載均衡的配置,它的默認(rèn)實(shí)現(xiàn)類(lèi)為 DefaultClientConfigImpl。

IRule

為L(zhǎng)oadBalancer定義“負(fù)載均衡策略”的接口。

public interface IRule{

    public Server choose(Object key);
    
    public void setLoadBalancer(ILoadBalancer lb);
    
    public ILoadBalancer getLoadBalancer();    
}

IRule 的實(shí)現(xiàn) 依賴(lài)關(guān)系示意圖如下:

  • BestAvailableRule:選擇具有最低并發(fā)請(qǐng)求的服務(wù)器。
  • ClientConfigEnabledRoundRobinRule:輪詢(xún)。
  • RandomRule:隨機(jī)選擇一個(gè)服務(wù)器。
  • RoundRobinRule:輪詢(xún)選擇服務(wù)器。
  • RetryRule:具備重試機(jī)制的輪詢(xún)。
  • WeightedResponseTimeRule:根據(jù)使用平均響應(yīng)時(shí)間去分配一個(gè)weight(權(quán)重) ,weight越低,被選擇的可能性就越低。
  • ZoneAvoidanceRule:根據(jù)區(qū)域和可用性篩選,再輪詢(xún)選擇服務(wù)器。

IPing

定義如何 “ping” 服務(wù)器以檢查其是否存活。

public interface IPing {
    public boolean isAlive(Server server);
}

IPing 的實(shí)現(xiàn) 依賴(lài)關(guān)系示意圖如下:

  • PingUrl:真實(shí)的去ping 某個(gè)url,判斷其是否alive。
  • PingConstant:固定返回某服務(wù)是否可用,默認(rèn)返回true,即可用
  • NoOpPing:不去ping,直接返回true,即可用。
  • DummyPing:繼承抽象類(lèi)AbstractLoadBalancerPing,認(rèn)為所以服務(wù)都是存活狀態(tài),返回true,即可用。
  • NIWSDiscoveryPing:結(jié)合eureka使用時(shí),如果Discovery Client在線,則認(rèn)為心跳檢測(cè)通過(guò)。

ServerList

定義獲取所有的服務(wù)實(shí)例清單。

public interface ServerList<T extends Server> {
    public List<T> getInitialListOfServers();
    public List<T> getUpdatedListOfServers();   
}

ServerList 的實(shí)現(xiàn) 依賴(lài)關(guān)系示意圖如下:

  • DomainExtractingServerList:代理類(lèi),根據(jù)傳入的ServerList的值,實(shí)現(xiàn)具體的邏輯。
  • ConfigurationBasedServerList:從配置文件中加載服務(wù)器列表。
  • DiscoveryEnabledNIWSServerList:從Eureka注冊(cè)中心中獲取服務(wù)器列表。
  • StaticServerList:通過(guò)靜態(tài)配置來(lái)維護(hù)服務(wù)器列表。

ServerListFilter

允許根據(jù)過(guò)濾配置動(dòng)態(tài)獲得的具有所需特性的候選服務(wù)器列表。

public interface ServerListFilter<T extends Server> {
    public List<T> getFilteredListOfServers(List<T> servers);
}

ServerListFilter 的實(shí)現(xiàn) 依賴(lài)關(guān)系示意圖如下:

  • DefaultNIWSServerListFilter:完全繼承自ZoneAffinityServerListFilter。
  • ZonePreferenceServerListFilter:EnableZoneAffinity 或 EnableZoneExclusivity 開(kāi)啟狀態(tài)使用,默認(rèn)關(guān)閉。處理基于區(qū)域感知的過(guò)濾服務(wù)器,過(guò)濾掉不和客戶(hù)端在相同zone的服務(wù),若不存在相同zone,則不進(jìn)行過(guò)濾。
  • ServerListSubsetFilter:服務(wù)器列表篩選器,它將負(fù)載平衡器使用的服務(wù)器數(shù)量限制為所有服務(wù)器的子集。如果服務(wù)器機(jī)群很大(例如數(shù)百個(gè)),并且不需要使用每一個(gè)機(jī)群并將連接保存在http客戶(hù)機(jī)的連接池中,那么這是非常有用的。它還可以通過(guò)比較總的網(wǎng)絡(luò)故障和并發(fā)連接來(lái)驅(qū)逐相對(duì)不健康的服務(wù)器。

ServerListUpdater

用于執(zhí)行動(dòng)態(tài)服務(wù)器列表更新。

public interface ServerListUpdater {

    public interface UpdateAction {
        void doUpdate();
    }

    void start(UpdateAction updateAction);

    void stop();

    String getLastUpdate();

    long getDurationSinceLastUpdateMs();

    int getNumberMissedCycles();

    int getCoreThreads();
}

ServerListUpdater 的實(shí)現(xiàn) 依賴(lài)關(guān)系示意圖如下:

  • PollingServerListUpdater:默認(rèn)的實(shí)現(xiàn)策略,會(huì)啟動(dòng)一個(gè)定時(shí)線程池,定時(shí)執(zhí)行更新策略。
  • EurekaNotificationServerListUpdater:利用Eureka的事件監(jiān)聽(tīng)器來(lái)驅(qū)動(dòng)服務(wù)列表的更新操作。

參考資料

https://github.com/Netflix/ribbon/wiki

http://tech.lede.com/2018/01/11/rd/server/NetflixRibbon/

http://blog.didispace.com/springcloud-sourcecode-ribbon/

https://www.fangzhipeng.com/springcloud/2017/08/11/Ribbon-resources.html

https://blog.csdn.net/Tincox/article/details/79210309

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容