二刷Ribbon源碼

說的多,也要作的多,這樣才踏實多。java

1.基礎知識2.Netflix Ribbon主要組件2.1 ServerList 服務列表2.2 ServerListFilter服務篩選2.3 ServerListUpdater 服務列表的更新2.4 IPing 服務存活檢查2.5 IRule 根據算法選擇一個服務2.6 ILoadBalancer:統籌者,總管2.7 配置組件組合方式3.SC-Ribbon主要組件(脫離Eureka)3.1 (配置)默認的配置RibbonClientConfiguration3.2 (配置)自定義配置3.2.1 註解式配置類3.2.1 配置文件裏配置:3.3 (使用) SpringClientFactory3.4 (使用) LoadBalancerClient3.SC-Ribbon與eureka一塊兒使用又發生了什麼4.Resttemplate 如何具備負載均衡能力5.總結git

1.基礎知識

首先要明白一個基礎知識點:github

  • Netfix公司開源了一系列微服務組件。項目地址github.com/Netflix。裏面有eureka,Ribbon等等web

  • SpringCloud 集成了Netfix開源的套件的幾個套件,也組成了一個項目,項目地址github.com/spring-clou…。咱們熟悉 eureka,ribbon就在這裏。算法

  • Ribbon是Netflix發佈的開源項目,主要功能是提供客戶端的軟件負載均衡算法spring

  • Spring Cloud Ribbon模塊是封裝了Netflix公司開發的這個Ribbon項目。api

也就是說緩存

  • Spring Cloud Ribbon是基於Netflix Ribbon實現的一套客戶端負載均衡工具。
  • 核心功能是在Netflix Ribbon項目中

本文基於Brixton版本,springboot環境下直接看的源碼springboot

<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Brixton.SR4</version>
        <type>pom</type>
        <scope>import</scope>
</dependency>ml
複製代碼

2.Netflix Ribbon主要組件

先從Netflix Ribbon項目的角度去看服務器

2.1 ServerList 服務列表

存儲服務列表,也就是被負載均衡的對象

主要實現

  • ConfigurationBasedServerList: 靜態服務列表,也就是咱們一般所說的寫死,項目啓動前配置好。
  • DiscoveryEnabledNIWSServerList:動態服務列表,從Eureka Client中獲取服務列表。

2.2 ServerListFilter服務篩選

篩選特定條件的服務列表,對ServerList服務器列表進行二次過濾

主要實現

  • AbstractServerListFilter
    負責從負載均衡器中過濾出當前可用的服務器列表的類
  • ZoneAffinityServerListFilter: 過濾掉全部的不和客戶端在相同zone的服務(注意:若是和客戶端相同的zone不存在,纔不過濾不一樣zone有服務)
  • ZonePreferenceServerListFilter
  • ServerListSubsetFilter

2.3 ServerListUpdater 服務列表的更新

定義關於更新動做,根據策略執行更新操做,更新ServerList列表,通常用於動態ServerList

主要實現

  • PollingServerListUpdater
    默認的實現策略。此對象會啓動一個定時線程池,定時執行更新策略

  • EurekaNotificationServerListUpdater
    當收到緩存刷新的通知,會更新服務列表。

2.4 IPing 服務存活檢查

檢查一個服務是否存活,

主要實現

  • DummyPing : 老是認爲存活
  • NoOpPing:什麼都不作
  • PingConstant:經過配置參數設置指定服務器存活狀態
  • NIWSDiscoveryPing: 若是服務實例在本地的Eureka緩存中存在,則返回true(默認配置)。ribbon-eureka包中提供的類,結合eureka使用時,若是Discovery Client在線,則認爲心跳檢測經過
  • PingUrl:用HttpClient調用服務的一個URL,若是調用成功,則認爲本次心跳成功,表示此服務活着

IPing檢查的是當個服務的存活性。

IPingStrategy:服務列表檢查策略接口,
惟一實現:SerialPingStrategy:採用線性遍歷的策略方式,使用IPing檢查每一個服務的存活性。

2.5 IRule 根據算法選擇一個服務

根據算法中從服務列表中選取一個要訪問的服務

主要實現

  • RandomRule:隨機算法
  • RoundRobinRule : 輪訓策略(`默認策略`
  • RetryRule: 輪詢 + 重試,在RoundRobinRule基礎上,增長重試功能
  • WeightedResponseTimeRule: (很是好) 剛啓動時,統計數據不足,先使用RoundRobinRule策略。有個DynamicServerWeightTask定時任務,默認每隔30秒會計算一次各個服務實例響應時間,等統計信息足夠,切換到WeightedResponseTimeRule.
  • BestAvailableRule: 先過濾到斷路器處於打開的服務,而後選擇併發請求最小的服務實例來使用。剛啓動時,若是統計信息不足,則使用RoundRobinRule策略,等統計信息足夠,會切換到BestAvailableRule。
  • PredicateBasedRule: 過濾+輪訓
  • AvailabilityFilteringRule: (1)因爲屢次訪問故障而處於斷路器跳閘狀態;(2)併發的鏈接數量超過閾值. (3)對剩餘的服務列表,進行輪詢

小結:咱們能夠看出上述組件是對 服務列表(待負載對象)的定義,與操做。可是要想讓他們協同起來幹活。這就須要ILoadBalancer

2.6 ILoadBalancer:統籌者,總管

負載均衡器,負責負載均衡調度的管理,總管級別。調度其餘組件,完成從服務列表裏獲取一個服務的目標

對外提供一個方法,選擇出一個Server來。

public Server chooseServer(Object key);
複製代碼

抽象類:AbstractLoadBalancer 主要定義了服務的分組

主要實現

1.BaseLoadBalancer: 基本負載均衡器實現.維護
(1)維護一個全部服務列表+當前存活服務列表
(2)默認使用輪訓選擇一個服務
(3)定義一個定時器,根據SerialPingStrategy策略定時檢活

2.DynamicServerListLoadBalancer動態升級版
(1)DomainExtractingServerList 動態從EurekaClient獲取UP狀態服務列表
(2)ZoneAffinityServerListFilter對列表進行過濾
(3)PollingServerListUpdater定時對服務進行更新
3.ZoneAwareLoadBalancer: 在DynamicServerListLoadBalancer基礎上增長了zone策略。
(1)此類使用ZoneStats存儲每一個Zone的狀態和平均請求狀況。區域指標做爲選擇服務的影響因素

2.7 配置組件組合方式

組件有了,到底採用哪一種組合方式呢?Ribbon爲使用者提供了可配置化。

經過不一樣的組件組合,配置不一樣的負載均衡效果。

配置的方式有哪些呢:

  • IClientConfig 接口:默認實現DefaultClientConfigImpl。你不配置任何屬性,則默認會使用這裏面定義的組件。固然這裏還有其餘各類屬性的配置
默認配置的組件有:
複製代碼
  • 文件配置: 當咱們更換默認的配置時,咱們能夠在配置文件裏配置各類屬性
    屬性格式配置以下
<clientName>.<nameSpace>.<propertyName>=<value>
api-user.ribbon.listOfServers=localhost:8099
複製代碼

這樣:Netfix-Ribbon的大概組件,以及工做原理也就一目瞭然了
項目地址github.com/Netflix/rib…

3.SC-Ribbon主要組件(脫離Eureka)

SpringCloud-Ribbon集成了Netflix Ribbon,使其融入springcloud體系。

核心的功能都在Netflix Ribbon項目中了。SC-Ribbon封裝Netflix Ribbon,主要作了一些集成工做。

爲此,SpringCloud-Ribbon又增長了哪些組件,幹了哪些事呢?

思考:既然核心功能在Netflix Ribbon項目中了。此項目乾的事,無非就是從如下幾方面入手

  1. 配置層面:符合Spring體系的配置習慣
  2. 使用層面: 如何使用Ribbon的負載均衡

!!!!!注意注意: 此處說的是脫離Eureka使用SpringCloud-Ribbon

下面咱們來看爲此作出的工做:

配置層面的工做

3.1 (配置)默認的配置RibbonClientConfiguration

此配置類也是做爲一個默認配置類存在,也就是在你不配置任何東西是SpringCloud-Ribbon默認裝載的核心組件的組合,
相比於Netflix Ribbon項目DefaultClientConfigImpl 的提供的默認組件爲

在這裏插入圖片描述
在這裏插入圖片描述

3.2 (配置)自定義配置

本着可擴展原則: SpringCloudRibbon也要提供個性化配置功能。

3.2.1 註解式配置類

這裏涉及兩個註解:@RibbonClient@RibbonClients (注意有個s

  • 這兩註解類功能是,被他們註解的類會經過某種機制RibbonClientConfiguration替換默認的配置
  • 這兩個註解的區別是:一個覆蓋的是單個服務的某個組件,一個針對多個服務

使用方式以下:我把user服務負載均衡的IPing 組件替換爲個人MyPingUrl

Configuration
@RibbonClient(name = "user", configuration = UserConfiguration.class)
public class UserRibbonConfiguration {
}
@Configuration
protected static class UserConfiguration{
    @Bean
    public IPing ribbonPing() {
        return new MyPingUrl();
    }
}
複製代碼

注意 添加UserConfiguration中的配置。UserConfiguration必須使用@Configuration進行聲明,並且不能放在能夠被main application context的@ComponentScan或@SpringBootApplication掃描到的地方,防止該配置被全部的@RibbonClient共享

若是想把全部服務的負載均衡的的IPing 組件替換爲個人MyPingUrl

@RibbonClient(defaultConfiguration = UserConfiguration.class)
public class UserRibbonConfiguration {
}
複製代碼

工做原理

這兩個註解的工做原理是:經過引入RibbonClientConfigurationRegistrar註冊器,將配置類註冊爲一個RibbonClientSpecification

@Configuration
@Import(RibbonClientConfigurationRegistrar.class)//這裏
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RibbonClient {
}
//註冊器
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
        private void registerClientConfiguration(BeanDefinitionRegistry registry,
            Object name, Object configuration)
 
{
            //註冊爲一個RibbonClientSpecification
        BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(RibbonClientSpecification.class);
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        registry.registerBeanDefinition(name + ".RibbonClientSpecification",
                builder.getBeanDefinition());
    }
}
複製代碼

RibbonClientSpecification: 直譯爲客戶端規範,對,你的個性化配置被解析爲一個客戶端規範

舉例說明爲:

  • A服務,B服務使用同一個個性化配置,A,B服務就有一個RibbonClientSpecification規範
  • C服務使用一個個性化配置,C服務就有一個RibbonClientSpecification規範

這個規範是如何生效的呢?見下文

3.2.1 配置文件裏配置:

另外一種就是直接在spring體系內的配置文件中配置
application.yml

api-user:
  ribbon:
    NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
複製代碼

在這些屬性中定義的類優先於使用@RibbonClient(configuration=MyRibbonConfig.class)定義的bean和由Spring Cloud Netflix提供的默認值。

說了配置,在講講使用層面的工做

使用層面的工做
負載均衡功能的使用其實就在ILoadBalancer這個統籌組件上。所謂使用,其實就是就在這個ILoadBalancer上。

3.3 (使用) SpringClientFactory

SpringClientFactory使用工廠模式對外提供:根據serviceId獲取IClient, ILoadBalance, ILoadBalanceContext等對象的服務。

這裏咱們只關注ILoadBalance的獲取。

SpringClientFactory 爲每一個服務建立一個獨立的上下文,並在其中加載對應的配置及Ribbon核心接口的實現類。建立邏輯在其父類NamedContextFactory

public abstract class NamedContextFactory{
    private Map<String, C> configurations = new ConcurrentHashMap<>();
    private Class<?> defaultConfigType;
    //建立上下文
    protected AnnotationConfigApplicationContext createContext(String name) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        if (this.configurations.containsKey(name)) {
            for (Class<?> configuration : this.configurations.get(name)
                    .getConfiguration()) {
                context.register(configuration);
            }
        }
        for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
            if (entry.getKey().startsWith("default.")) {
                for (Class<?> configuration : entry.getValue().getConfiguration()) {
                    context.register(configuration);
                }
            }
        }
        context.register(PropertyPlaceholderAutoConfiguration.class,
                this.defaultConfigType);
        context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
                this.propertySourceName,
                Collections.<String, Object> singletonMap(this.propertyName, name)));
        if (this.parent != null) {
            // Uses Environment from parent as well as beans
            context.setParent(this.parent);
        }
        context.refresh();
        return context;
    }
}
複製代碼

這裏有兩個點:

  1. 根據傳入服務serverId 建立一個AnnotationConfigApplicationContext,裝載屬於他本身的負載均衡套件。不一樣的服務有不一樣的上下文,裏面存儲的是屬於他的負載均衡套件組合

  2. 服務的負載均衡套件組合方式。是上文提到的兩種配置方式決定的。也就是configurationsdefaultConfigType兩個屬性所表明的規範。

    private Map<String, C> configurations = new ConcurrentHashMap<>();
    private Class<?> defaultConfigType;//表明默認的組件組合
複製代碼
  • configurations :表明的是個性化配置的套件規範。上文提到的RibbonClientSpecification
  • defaultConfigType :表明的就是sc-ribbon的默認配置類RibbonClientConfiguration

當咱們調用SpringClientFactory.getLoadBalancer方法獲取一個負載均衡器實例時, 本質就是在這個服務對應的上下文中取出屬於他的ILoadBalancer實現。

public <T> getInstance(String name, Class<T> type) {
        AnnotationConfigApplicationContext context = getContext(name);
        if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
                type).length > 0) {
            return context.getBean(type);返回bean
        }
        return null;
    }
複製代碼

獲取到ILoadBalancer實現,咱們就能夠調用其chooseServer得到一個服務實例,進行請求了。

ILoadBalancer userLoadBalancer = clientFactory.getLoadBalancer("user");
Server server = userLoadBalancer.chooseServer("default")
複製代碼

3.4 (使用) LoadBalancerClient

LoadBalancerClient封裝了SpringClientFactory, 做爲負載均衡器客戶端,被普遍使用。這個類纔是SC-Ribbon的真正負載均衡客戶端的入口。

提供了三個方法:

  • 選擇服務實例方法;
  • 執行請求方法
  • 重構uri方法。

每一個方法都是與負載均衡有關

    ServiceInstance choose(String serviceId);

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

    URI reconstructURI(ServiceInstance instance, URI original);
複製代碼

LoadBalancerClient做爲負載均衡客戶端。他的能力徹底是創建在以上全部組件的共同努力下實現的。

public class MyClass {
    @Autowired
    private LoadBalancerClient loadBalancer;

    public void doStuff() {
        ServiceInstance instance = loadBalancer.choose("stores");
        URI storesUri = URI.create(String.format("http://%s:%s", instance.getHost(), instance.getPort()));
        // ... do something with the URI
    }
}
複製代碼

3.SC-Ribbon與eureka一塊兒使用又發生了什麼

1.配置層面:

SpringCloudRibbonEureka一塊兒使用時,會使用@RibbonClients爲全部的服務的負載均衡替換一些默認組件(更換組合套件)

@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(DiscoveryEnabledNIWSServerList.class)
@ConditionalOnBean(SpringClientFactory.class)
@ConditionalOnProperty(value = "ribbon.eureka.enabled", matchIfMissing = true)
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
public class RibbonEurekaAutoConfiguration {
}
----------------------------
@Configuration
@CommonsLog
public class EurekaRibbonClientConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public IPing ribbonPing(IClientConfig config) {
        NIWSDiscoveryPing ping = new NIWSDiscoveryPing();//
        ping.initWithNiwsConfig(config);
        return ping;
    }

    @Bean
    @ConditionalOnMissingBean
    public ServerList<?> ribbonServerList(IClientConfig config) {
        DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(
                config);
        DomainExtractingServerList serverList = new DomainExtractingServerList(
                discoveryServerList, config, this.approximateZoneFromHostname);
        return serverList;
    }
}    
複製代碼
  • IPing: 使用NIWSDiscoveryPing這個實現
  • ServerList:使用了DomainExtractingServerListDomainExtractingServerList代理了DiscoveryEnabledNIWSServerListDiscoveryEnabledNIWSServerList存儲的服務列表都從EurekaClient本地緩存裏取到的。(取操做是PollingServerListUpdater,定時執行的)
public class DiscoveryEnabledNIWSServerList extends AbstractServerList<DiscoveryEnabledServer>{
private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
    EurekaClient eurekaClient = eurekaClientProvider.get();
    List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
    ....

}
}
複製代碼

2.使用層面:
沒啥變化,就是使用LoadBalancerClient來使用負載均衡獲取一個Server。

4.Resttemplate 如何具備負載均衡能力

Resttemplate 具備負載均衡能力,其本質仍是經過使用LoadBalancerClient來實現。

如何實現呢?

這就涉及到兩個知識點:

  • Resttemplate相關體系:
  • @LoadBalanced 註解。

Resttemplate 是有攔截器的概念的 。也就是說在真正發起請求以前,會走一些攔截器。這就給負載均衡選擇一個Server提供了機會。

@LoadBalanced 註解就是給其修飾的Resttemplate實例,注入LoadBalancerInterceptor攔截器。此攔截器就是經過LoadBalancerClient來實現了負載均衡

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

    private LoadBalancerClient loadBalancer;
    ...
}
複製代碼

具體可閱讀個人其餘文章
發送http請求(1):發送http請求的幾種方式
發送http請求(2):RestTemplate發送http請求
@LoadBalanced註解的RestTemplate擁有負載均衡的能力

5.總結

文本從介紹負載均衡的幾個組件開始,由底層講到了Resttemplate原理。打開了Resttemplate發起負載均衡請求的黑盒。順着這條線,只要仔細研讀各個部分的知識點。

你會感嘆開源框架的大廈的創建,爲咱們省去了多少開發工做。

咱們不該該是停留在使用的階段,還應該深刻去了解底層框架的原理,這樣你纔會在大廈之上更進一層。

參考連接:
https://www.jianshu.com/p/73c117fbfe10
https://blog.csdn.net/hry2015/article/details/78357990


若是本文任何錯誤,請批評指教,不勝感激 !
若是文章哪些點不懂,能夠聯繫我交流學習!

微信公衆號:源碼行動
享學源碼,行動起來,來源碼行動

相關文章
相關標籤/搜索