Ribbon-負載均衡

已Ribbon為例了解負載均衡

什么是負載均衡

負載均衡建立在現(xiàn)有網(wǎng)絡(luò)結(jié)構(gòu)之上漾肮,它提供了一種廉價有效透明的方法擴展網(wǎng)絡(luò)設(shè)備和服務(wù)器的帶寬犯祠、增加吞吐量竿报、加強網(wǎng)絡(luò)數(shù)據(jù)處理能力俄删、提高網(wǎng)絡(luò)的靈活性和可用性忆某。

簡單來說就是當一個服務(wù)部署在了多個節(jié)點時点待,客戶端可以通過負載均衡來獲取單個可以請求的節(jié)點。

為什么需要負載均衡

一臺服務(wù)器所能承受的請求有限弃舒,并發(fā)場景下癞埠,為了能夠承載更多請求,滿足更多的用戶棒坏,需要對服務(wù)器節(jié)點進行擴容燕差,將用戶流量分攤到多個節(jié)點遭笋,從而達到承接更大流量的目的坝冕。

常見的負載均衡算法

  1. 隨機
  2. 輪詢
  3. 權(quán)重 (響應(yīng)時間、同機房策略瓦呼、灰度策略)

怎么實現(xiàn)負載均衡

引入Spring Cloud Ribbon jar包喂窟,可以單獨使用,需要在application.yml中配置服務(wù)節(jié)點央串。然后通過在RestTemplate實例上增加注解的方式磨澡,攔截RestTemplate發(fā)出的http請求,通過Ribbon內(nèi)置的負載均衡算法质和,選擇需要請求的服務(wù)節(jié)點稳摄,向選中的節(jié)點發(fā)送請求。

@Configuration
public class RestTemplateConfig {

    @LoadBalanced
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){
        return restTemplateBuilder.build();
    }

}
@RestController
public class LoadBlanceTest {

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/getUser")
    public String getUser(){
        return restTemplate.getForObject("http://spring-cloud-test-service/users", String.class);
    }


}
# 應(yīng)用名稱
spring.application.name=spring-cloud-ribbon
# 應(yīng)用服務(wù) WEB 訪問端口
server.port=8080

spring-cloud-test-service.ribbon.listOfServers=\
  http://localhost:8081,http://localhost:8082,http://localhost:8083

自定義負載均衡策略

image.png

可以通過實現(xiàn)IRule接口來自定義負載均衡算法實現(xiàn)饲宿。上圖右側(cè)是Spring Cloud目前以支持的實現(xiàn)算法厦酬。

spring-cloud-ribbon-service.ribbon.NFLoadBalancerRuleClassName=\
            com.fishbone.example.springcloudprovidercosumer.FishBoneLoadBalance
public class FishBoneLoadBalance extends AbstractLoadBalancerRule {

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }

    @Override
    public Server choose(Object key) {
        return chooseServer(getLoadBalancer(),key);
    }

    public Server chooseServer(ILoadBalancer lb, Object key){
        if (lb == null) {
            return null;
        }
        Server server = null;
        List<Server> upList = lb.getReachableServers();
        List<Server> allList = lb.getAllServers();

        int serverCount = allList.size();
        if (serverCount == 0) {
            /*
             * No servers. End regardless of pass, because subsequent passes
             * only get more restrictive.
             */
            return null;
        }

        int index = chooseIpInt(serverCount);
        server = upList.get(index);
        return server;
    }

    private int chooseIpInt(int serverCount) {
        //獲取請求IP地址 
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        String remoteAddre = requestAttributes.getRequest().getRemoteAddr();
        int code = Math.abs(remoteAddre.hashCode());
        return code%serverCount;
    }

}

負載均衡原理

通過上方的內(nèi)容,我們已經(jīng)對負載均有了一個大概的理解瘫想。要實現(xiàn)負載均衡有以下幾點需要考慮仗阅。

  1. 如何攔截客戶端請求?
  2. 如何獲取服務(wù)列表国夜?
  3. 如何選擇負載均衡規(guī)則减噪?
  4. 如何請求服務(wù)端并拿到結(jié)果給到客戶端?

以上只是一些基礎(chǔ)的問題车吹,肯定還會有很多細節(jié)需要考慮筹裕,比如:

http請求攔截到之后,只需要修改地址窄驹、端口么朝卒?
獲取服務(wù)列表后,如果服務(wù)掛了怎么辦馒吴?
如果調(diào)用不成功扎运,比如超時要怎么解決瑟曲?
如何根據(jù)業(yè)務(wù)選擇最優(yōu)的節(jié)點?

攔截請求

可以這么說豪治,Spring Cloud 相關(guān)的starter 組件都用到了 Spring Boot 的自動裝配機制洞拨。Ribbon 也不例外。所以我們需要找到 Ribbon 相關(guān)的自動裝配入口负拟。

在第一時間搜索 RibbonAutoConfiguration 后發(fā)現(xiàn) 一個類

@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(
        name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
// 在看這里依賴 LoadBalancerAutoConfiguration 我們前邊 就是通過 @LoadBalanced 注解實現(xiàn)負載
// 所以先去看一下 LoadBalancerAutoConfiguration
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
        AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
        ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {
    // ...
}

@Conditional 注解

看源碼之前先簡單介紹幾個 @Conditional 注解烦衣。源碼中大量使用,如果搞不清楚掩浙,會容易暈~

@ConditionalOnBean 
// 當給定的在 bean 存在時,則實例化當前Bean花吟,這個bean可能由于某種原因而沒有注冊到ioc里。
// 這時 @ConditionalOnBean 可以讓當前bean也不進行注冊厨姚,當前bean 指使用了這個注解的 bean 
@ConditionalOnMissingBean 
// 當給定的在bean不存在時,則實例化當前Bean衅澈,感覺這個是在多態(tài)環(huán)境下使用,
// 當一個接口有多個實現(xiàn)類時谬墙,如果只希望它有一個實現(xiàn)類今布,那就在各個實現(xiàn)類上加上這個注解
@ConditionalOnClass // 當給定的類名在類路徑上存在,則實例化當前Bean
@ConditionalOnMissingClass // 當給定的類名在類路徑上不存在拭抬,則實例化當前Bean

這幾個注解部默,包括其他類似的注解使用時后邊一般會跟上條件類。比如:

@Configuration
public class ConditioanTeset {
    
    // 對于這種 后邊加了注釋的 條件意思為 如果 CTest 不存在 則創(chuàng)建 ATest  
    @Bean
    @ConditionalOnMissingBean(CTest.class)
    public ATest getATest(){
        return new ATest();
    }

    // 對于這種釋沒有跟條件值的看返回值類型造虎,這里表示:如果BTest不存在則創(chuàng)建 BTest 
    @Bean
    @ConditionalOnMissingBean
    public BTest getBTest(){
        System.out.println("getCTest");
        return new BTest();
    }

    // 對于這種有參數(shù)條件的 條件意思為 如果 ATest 已存在 則用此方法 創(chuàng)建 ATest  但是 BTest 必須要存在
    // 如果BTest 不存在 會查看是否有 BTest 實例化方法(本類或者其他 Spring 托管類)傅蹂,
    // 如果沒有則不滿足條件
    // 注意 如果方法名字一樣 只會走一個,無參的優(yōu)先級最低 (只有當有參的不滿足條件時 才會走無參)
    // 可能有點繞算凿》莺可以理解一下,因為Spring 默認是單例, 所以 在初始化一個 bean 時 如果 一個bean 有多種初始化方式澎媒, 那就會選用其中的一種搞乏。
    // 比如這里 getAtest 方法有兩個,所以只會走有參數(shù)的(@ConditionalOnBean 滿足條件戒努,如果刪除上邊的無參请敦,不滿足條件就不會走)。 
    // 如果方法名字 不一樣储玫,則兩個方法都會走侍筛。  
    @Bean
    @ConditionalOnBean
    public ATest getATest(BTest bTest) {
        System.out.println(bTest);
        return new ATest();
    }

}

記住幾點:

  1. 對于這種釋沒有跟條件值的看返回值類型,返回值類型就是條件類型撒穷。
  2. 如果方法名字一樣 只會走一個匣椰,無參的優(yōu)先級最低。
  3. 如果定義了參數(shù)端礼,則參數(shù)必須要先實例化禽笑。會從 IOC容器中找參數(shù)對應(yīng)的實例化類入录,先完成初始化,依賴注入佳镜。
  4. 在使用過程中僚稿,一定要謹慎,避免寫一些不會觸發(fā)的定義蟀伸。比如:如果 A不存在蚀同,實例化A,并且需要一個參數(shù)A啊掏。

好了下面繼續(xù)看 LoadBalancerAutoConfiguration 方法

LoadBalancerAutoConfiguration

這里需要先看類上的定義注解蠢络, LoadBalancerAutoConfiguration 依賴與 RestTemplate 和 LoadBlancerClient 兩個類。

  1. 其實這里就是一個簡單的 @Bean 對象的配置迟蜜,可以按照順序看一下刹孔,注意根據(jù)依賴關(guān)系決定加載的先后順序,比如第一個方法小泉,參數(shù)類型是 RestTemplateCustomizer 所以可以直接去看返回值為 RestTemplateCustomizer 的類芦疏。

  2. 這時會發(fā)現(xiàn)有兩個方法 兩個方法的區(qū)別就是參數(shù)不同冕杠,以及 方法所屬的類加載條件不同 (RetryTemplate 是否存在)微姊。 這里 我們并沒有使用 RetryTemplate,因此看 LoadBalancerInterceptorConfig#restTemplateCustomizer 方法即可分预。

  3. 這時發(fā)現(xiàn)該方法又依賴 LoadBalancerInterceptor 參數(shù)兢交, 接著找,又發(fā)現(xiàn) LoadBalancerInterceptorConfig#loadBalancerInterceptor 方法笼痹,然后又有參數(shù)配喳,繼續(xù)找...

  4. 其實在 LoadBalancerInterceptor 這里就不難發(fā)現(xiàn),這里注冊了一個 LoadBalancerInterceptor 攔截器凳干,同時用 SmartInitializingSingleton 做了一層封裝晴裹。

  5. 簡單來說,這個類就是用來 添加攔截器救赐。 需要注意的就是 兩個函數(shù)式 接口涧团,在客戶端初始化對象時(SmartInitializingSingleton )將會用到。

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

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

    @Autowired(required = false)
    private List<LoadBalancerRequestTransformer> transformers = 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);
                }
            }
        });
    }

    @Bean
    // 當 bean 不存在時 則實例化當前 bean  對于這里來說這個 bean 就是 LoadBalancerRequestFactory 
    @ConditionalOnMissingBean
    public LoadBalancerRequestFactory loadBalancerRequestFactory(
            LoadBalancerClient loadBalancerClient) {
        return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
    }

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

        @Bean
        public LoadBalancerInterceptor loadBalancerInterceptor(
                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);
            };
        }

    }

    /**
     * Auto configuration for retry mechanism.
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RetryTemplate.class)
    public static class RetryAutoConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public LoadBalancedRetryFactory loadBalancedRetryFactory() {
            return new LoadBalancedRetryFactory() {
            };
        }

    }

    /**
     * Auto configuration for retry intercepting mechanism.
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RetryTemplate.class)
    public static class RetryInterceptorAutoConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public RetryLoadBalancerInterceptor loadBalancerRetryInterceptor(
                LoadBalancerClient loadBalancerClient,
                LoadBalancerRetryProperties properties,
                LoadBalancerRequestFactory requestFactory,
                List<LoadBalancedRetryFactory> loadBalancedRetryFactories) {
            AnnotationAwareOrderComparator.sort(loadBalancedRetryFactories);
            return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
                    requestFactory, loadBalancedRetryFactories.get(0));
        }

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

    }

}

來一波分析:

首先经磅,起點就是 各個 AutoConfiguration 泌绣,需要關(guān)注的就是兩個,第一個预厌,LoadBalancerAutoConfiguration 這個的主要功能就是向 RestTemplate 注冊攔截器阿迈。第二個,

負載均衡算法解析

自定義健康檢查

負載均衡原理

怎么實現(xiàn)嵌入http請求

獲取服務(wù)列表

負載均衡算法配置

調(diào)用以及結(jié)果獲取

負載均衡算法解析

Ribbon 服務(wù)感知

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末轧叽,一起剝皮案震驚了整個濱河市苗沧,隨后出現(xiàn)的幾起案子刊棕,更是在濱河造成了極大的恐慌,老刑警劉巖待逞,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鞠绰,死亡現(xiàn)場離奇詭異,居然都是意外死亡飒焦,警方通過查閱死者的電腦和手機蜈膨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來牺荠,“玉大人翁巍,你說我怎么就攤上這事⌒荽疲” “怎么了灶壶?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長杈曲。 經(jīng)常有香客問我驰凛,道長,這世上最難降的妖魔是什么担扑? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任恰响,我火速辦了婚禮,結(jié)果婚禮上涌献,老公的妹妹穿的比我還像新娘胚宦。我一直安慰自己,他們只是感情好燕垃,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布枢劝。 她就那樣靜靜地躺著,像睡著了一般卜壕。 火紅的嫁衣襯著肌膚如雪您旁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天轴捎,我揣著相機與錄音鹤盒,去河邊找鬼。 笑死轮蜕,一個胖子當著我的面吹牛昨悼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播跃洛,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼率触,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了汇竭?” 一聲冷哼從身側(cè)響起葱蝗,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤穴张,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后两曼,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體皂甘,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年悼凑,在試婚紗的時候發(fā)現(xiàn)自己被綠了偿枕。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡户辫,死狀恐怖渐夸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情渔欢,我是刑警寧澤墓塌,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站奥额,受9級特大地震影響苫幢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜垫挨,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一韩肝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧棒拂,春花似錦伞梯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽漾峡。三九已至攻旦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間生逸,已是汗流浹背牢屋。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留槽袄,地道東北人烙无。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像遍尺,于是被迫代替她去往敵國和親截酷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

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