spring cloud ribbon學習二:Ribbon輪詢策略源碼分析

看spring cloud源碼分析好繞,還是堅持看完了。

先總結一下Ribbon的運行流程薛闪,可以跳過總結看下面迁霎,然后重新看總結吱抚。

  • 項目啟動的時候會自動的為我們加載LoadBalancerAutoConfiguration自動配置類,該自動配置類初始化條件是要求classpath必須要有RestTemplate這個類考廉,必須要有LoadBalancerClient實現(xiàn)類秘豹。
  • LoadBalancerAutoConfiguration為我們干了二件事,第一件是創(chuàng)建了LoadBalancerInterceptor攔截器bean昌粤,用于實現(xiàn)對客戶端發(fā)起請求時進行攔截既绕,以實現(xiàn)客戶端負載均衡。創(chuàng)建了一個
    RestTemplateCustomizer的bean涮坐,用于給RestTemplate增加LoadBalancerInterceptor攔截器凄贩。
  • 每次請求的時候都會執(zhí)行org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptorintercept方法,而LoadBalancerInterceptor具有LoadBalancerClient(客戶端負載客戶端)實例的一個引用袱讹,
    在攔截器中通過方法獲取服務名的請求url(比如http://user-service/user)疲扎,及服務名(比如user-service),然后調(diào)用負載均衡客戶端的execute方法。
  • 執(zhí)行負載客戶端RibbonLoadBalancerClient(LoadBalancerClient的實現(xiàn))的execute方法评肆,得到ILoadBalancer(負載均衡器)的實現(xiàn)ZoneAwareLoadBalancer债查,并且通過調(diào)用其chooseServer方法獲得服務列表中的一個實例,比如說user-service列表注冊到eureka中一個實例瓜挽。然后向其中的一個具體實例發(fā)起請求盹廷,得到結果。

源碼分析

之前我們實現(xiàn)負載均衡是在消費端的RestTemplate加上注解@LoadBalanced久橙,便可以實現(xiàn)負載均衡了

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

查看注解內(nèi)容:

/**
 * 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 {
}

這個注解給RestTemplate做標記俄占,標記為LoadBalancerClient

查看LoadBalancerClient源碼:

/**
 * Represents a client side load balancer
 * @author Spencer Gibb
 */
public interface LoadBalancerClient extends ServiceInstanceChooser {

    /**
     * 通過LoadBalancer的ServiceInstance對指定的服務執(zhí)行請求操作
     */
    <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

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

    /**
     * 為系統(tǒng)構建一個合適的host:port形式的url淆衷。在分布式系統(tǒng)中缸榄,我們使用邏輯上的服務名稱作為host來構建URI
     * (替代服務實例的host:port形式)進行請求,比如說myservice/path/to/service祝拯。
     */
    URI reconstructURI(ServiceInstance instance, URI original);
}

繼承自接口ServiceInstanceChooser

/**
 * Implemented by classes which use a load balancer to choose a server to
 * send a request to.
 *
 * @author Ryan Baxter
 */
public interface ServiceInstanceChooser {

    /**
     * Choose a ServiceInstance from the LoadBalancer for the specified service
     * @param serviceId the service id to look up the LoadBalancer
     * @return a ServiceInstance that matches the serviceId
     */
    ServiceInstance choose(String serviceId);
}
  • ServiceInstance choose(String serviceId):根據(jù)傳入的服務名serviceId甚带,從負載均衡器中挑選一個對應服務的實例。

  • T execute(String serviceId, LoadBalancerRequest<T> request):使用從負載均衡器中挑選出來的服務實例來執(zhí)行請求內(nèi)容佳头。

  • T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request):使用從負載均衡器中挑選出來的服務實例來執(zhí)行請求內(nèi)容鹰贵。

  • URI reconstructURI(ServiceInstance instance, URI original):為系統(tǒng)構建一個合適的host:port形式的url。在分布式系統(tǒng)中康嘉,我們使用邏輯上的服務名稱作為host來構建URI(替代服務實例的host:port形式)進行請求碉输,比如說myservice/path/to/service。

順著LoadBalancerClient接口的所屬包org.springframework.cloud.client.loadbalancer,我們對內(nèi)容進行整理亭珍,可以得到下面的關系:

LoadBalancerAutoConfiguration為客戶端Ribbon負載均衡的自動化配置類敷钾,

@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 loadBalancedRestTemplateInitializer(
            final List<RestTemplateCustomizer> customizers) {
        return new SmartInitializingSingleton() {
            @Override
            public void afterSingletonsInstantiated() {
                for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                    for (RestTemplateCustomizer customizer : customizers) {
                    //通過調(diào)用RestTemplateCustomizer的實例來給需要的客戶端負載均衡的RestTemplate增加LoadBalancerInterceptor攔截器。
                        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 {
       //創(chuàng)建了一個LoadBalancerInterceptor的bean肄梨,用于實現(xiàn)對客戶端發(fā)起請求時進行攔截阻荒,以實現(xiàn)客戶端負載均衡。
        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(
                LoadBalancerClient loadBalancerClient,
                LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }

        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final LoadBalancerInterceptor loadBalancerInterceptor) {
            //創(chuàng)建了一個RestTemplateCustomizer的bean峭范,用于給RestTemplate增加LoadBalancerInterceptor攔截器财松。
            return new RestTemplateCustomizer() {
                @Override
                public void customize(RestTemplate restTemplate) {
                    List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                            restTemplate.getInterceptors());
                    list.add(loadBalancerInterceptor);
                    restTemplate.setInterceptors(list);
                }
            };
        }
}

@ConditionalOnClass(RestTemplate.class):當前項目的classpath路徑下有RestTemplate這個類。

@ConditionalOnBean(LoadBalancerClient.class):spring容器中必須有LoadBalancerClient的實現(xiàn)bean

該自動化配置主要完成了三件事

  • 創(chuàng)建了一個LoadBalancerInterceptor的bean纱控,用于實現(xiàn)對客戶端發(fā)起請求時進行攔截辆毡,以實現(xiàn)客戶端負載均衡。
  • 創(chuàng)建了一個RestTemplateCustomizer的bean甜害,用于給RestTemplate增加LoadBalancerInterceptor攔截器舶掖。
  • 維護了一個被@LoadBalanced注解修飾的RestTemplate對象列表,并在這里進行維護尔店,通過調(diào)用RestTemplateCustomizer的實例來給需要的客戶端負載均衡的RestTemplate增加LoadBalancerInterceptor攔截器眨攘。

看看LoadBalancerInterceptor攔截器是如何讓一個普通的RestTemplate變成負載均衡的:

LoadBalancerInterceptor攔截器

LoadBalancerClient是一個抽象的接口主慰,originalUri.getHost()獲取到的是服務名,execute函數(shù)去根據(jù)服務名來選擇實例并發(fā)起實際的請求鲫售。

org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient是實現(xiàn)LoadBalancerClient接口共螺,看其實現(xiàn):
execute方法,使用從負載均衡器中挑選出來的服務實例來執(zhí)行請求內(nèi)容情竹。

execute方法

getServer方法:

getServer方法

去調(diào)用ILoadBalancer實例的chooseServer方法

認識一下com.netflix.loadbalancer.ILoadBalancer接口:
ILoadBalancer負載均衡器

Interface that defines the operations for a software loadbalancer. A typical
loadbalancer minimally need a set of servers to loadbalance for, a method to
mark a particular server to be out of rotation and a call that will choose a
server from the existing list of server.
定義軟件負載平衡器操作的接口藐不。 一個典型的負載均衡器最低限度地需要一組服務器來負載平衡,一種方法標記一個特定的服務器秦效,以避免旋轉和蔥已有的服務列表中選擇一個實例進行調(diào)用雏蛮。

public interface ILoadBalancer {

    //向負載均衡器中維護的實例列表增加服務實例
    public void addServers(List<Server> newServers);
    
    //從負載均衡器中挑選出一個具體的服務實例
    public Server chooseServer(Object key);
    
    //用來通知和標記負載均衡器中的某個具體實例已經(jīng)停止服務,不然負載均衡器在下一次獲取服務實例清單前都會認為服務實例均是正常服務的阱州。
    public void markServerDown(Server server);
    
    /**
     * @deprecated 2016-01-20 This method is deprecated in favor of the
     * cleaner {@link #getReachableServers} (equivalent to availableOnly=true)
     * and {@link #getAllServers} API (equivalent to availableOnly=false).
     *
     * Get the current list of servers.
     *
     * @param availableOnly if true, only live and available servers should be returned
     */
    @Deprecated
    public List<Server> getServerList(boolean availableOnly);

    //獲取當前正常服務的實例列表
    public List<Server> getReachableServers();

   //獲取所有已知的服務實例列表挑秉,包括正常服務和停止服務實例。
    public List<Server> getAllServers();
}

com.netflix.loadbalancer.Server對象定義是一個傳統(tǒng)的服務端節(jié)點苔货,在該類中存儲了服務節(jié)點的一些元數(shù)據(jù)信息犀概,包括host,post以及一些部署信息等。

com.netflix.loadbalancer.ILoadBalancer接口的一些實現(xiàn)蒲赂,

ILoadBalancer的一些實現(xiàn)類

springcloud整合Ribbon的時候選擇采用的是com.netflix.loadbalancer.ZoneAwareLoadBalancer負載均衡器阱冶。調(diào)用它的chooseServer方法。

ZoneAwareLoadBalancer的chooseServer方法

回到org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClientexecute方法滥嘴,

public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
        //從上面跟過來我們知道這邊的serviceId其實就是服務名
        ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
        //通過ZoneAwareLoadBalancer的chooseServer函數(shù)獲取了負載均衡策略分配的服務實例對象Server之后,將其包裝成RibbonServer(增加了服務名serverid至耻,是否需要使用Https等其他信息)
        Server server = this.getServer(loadBalancer); 
        if(server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        } 
       RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
    serviceId), serverIntrospector(serviceId).getMetadata(server));

   return execute(serviceId, ribbonServer, request);
}

ILoadBalancer的實現(xiàn)com.netflix.loadbalancer.ZoneAwareLoadBalancer若皱,將其包裝成RibbonServer,調(diào)用ZoneAwareLoadBalancerchooseServer函數(shù)尘颓。

回到org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClientexecute方法走触,調(diào)用LoadBalancerRequestapply方法,向一個實際的具體服務實例發(fā)起請求疤苹,從而實現(xiàn)一開始以服務名為host的URI請求到host:port形式的實際訪問地址的轉換

圖片.png

apply方法參數(shù)ServiceInstance實例互广,ServiceInstance

ServiceInstance接口

上面說到的RibbonServer對象就是ServiceInstance接口的實現(xiàn)

RibbonServer實現(xiàn)

我們已經(jīng)可以大概理清了Spring Cloud Ribbon中實現(xiàn)客戶端負載均衡的基本脈絡,了解它是如何通過LoadBalancerInterceptor攔截器對RestTemplate的請求進行攔截卧土,并利用Spring Cloud的負載均衡器LoadBalancerClient將以邏輯服務名為host的URI轉換成具體的服務實例的過程惫皱。同時通過分析LoadBalancerClient的Ribbon實現(xiàn)RibbonLoadBalancerClient,可以知道在使用Ribbon實現(xiàn)負載均衡器的實現(xiàn)尤莺,實際使用的還是Ribbon中定義的ILoadBalancer接口的實現(xiàn)旅敷,自動化配置會采用ZoneAwareLoadBalancer的實例來實現(xiàn)客戶端負載均衡。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颤霎,一起剝皮案震驚了整個濱河市媳谁,隨后出現(xiàn)的幾起案子涂滴,更是在濱河造成了極大的恐慌,老刑警劉巖晴音,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柔纵,死亡現(xiàn)場離奇詭異,居然都是意外死亡锤躁,警方通過查閱死者的電腦和手機搁料,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來进苍,“玉大人加缘,你說我怎么就攤上這事【醢。” “怎么了拣宏?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長杠人。 經(jīng)常有香客問我勋乾,道長,這世上最難降的妖魔是什么嗡善? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任辑莫,我火速辦了婚禮,結果婚禮上罩引,老公的妹妹穿的比我還像新娘各吨。我一直安慰自己,他們只是感情好袁铐,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布揭蜒。 她就那樣靜靜地躺著,像睡著了一般剔桨。 火紅的嫁衣襯著肌膚如雪屉更。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天洒缀,我揣著相機與錄音瑰谜,去河邊找鬼。 笑死树绩,一個胖子當著我的面吹牛萨脑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播葱峡,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼砚哗,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了砰奕?” 一聲冷哼從身側響起蛛芥,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤提鸟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后仅淑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體称勋,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年涯竟,在試婚紗的時候發(fā)現(xiàn)自己被綠了赡鲜。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡庐船,死狀恐怖银酬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情筐钟,我是刑警寧澤揩瞪,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站篓冲,受9級特大地震影響李破,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜壹将,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一嗤攻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧诽俯,春花似錦妇菱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至颜启,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浪讳,已是汗流浹背缰盏。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留淹遵,地道東北人口猜。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像透揣,于是被迫代替她去往敵國和親济炎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容