1. 程式人生 > >SpringCloud-Ribbon

SpringCloud-Ribbon

什麼是Ribbon

Ribbon 是 Netflix 公司開發的一個用於負載均衡的元件.

什麼是負載均衡

負載均衡就是利用特定的方式,將流量(簡單理解為客戶端請求)分攤到多個操作單元上的一種手段.負載均衡對於系統吞吐量與系統處理能力有著質的提升.比如 Nginx 就是負載均衡元件.負載均衡可以簡單的理解為有以下一些

集中式負載均衡負載均衡在元件位於客戶端與服務端之間,通過一些手段,把收到的客戶端的網路請求轉到各個服務端之間,比如 Nginx

程序內負載均衡是指從例項庫選取一個例項進行流量匯入,我個人理解就是從很多的服務提供例項當中選取一個進行呼叫

示例

1:建立Eureka Server 註冊中心 (無特殊要求,按照正常配置即可)

2:建立Eureka Client 服務提供者 (無特殊要求,按照正常配置即可,提供若干介面給消費者進行呼叫,例項需要註冊到 Eureka Server 註冊中心)


@RestController
public class DemoController {

    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public String add(Integer a, Integer b, HttpServletRequest request) {
        return " From Port: " + request.getServerPort() + ", Result: " + (a + b);
    }
}

3:建立 Eureka Client Ribbon 工程,作為服務消費者,對外提供服務 (例項需要註冊到 Eureka Server 註冊中心)

新增依賴

<dependencies>
        <!-- ribbon jar-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>

        <!-- eureka client jar-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

    </dependencies>

在程式主入口處例項化一個 RestTemplate, 並且添加註解 @loadBalanced 宣告該 RestTemplate 用於負載均衡

@SpringBootApplication
@EnableDiscoveryClient
public class Chapter5RibbonLoadBalancerApplication {
    public static void main(String[] args) {
        SpringApplication.run(Chapter5RibbonLoadBalancerApplication.class, args);
    }
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

編寫對外提供的介面並注入 RestTemplate

@RestController
public class DemoController {

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/add")
    public String add(Integer a, Integer b) {
        String result = restTemplate.getForObject("http://eureka-client-A/add?a=" + a + "&b=" + b, String.class);
        System.out.println(result);
        return result;
    }
}

然後依次將 3 個工程打成 jar 包,並執行jar, 在執行 服務提供者的 jar 的時候, 應用不同的埠,多啟動幾個例項,然後通過服務消費者提供的介面進行呼叫. 如:http://localhost:7777/add?a=10&b=20 可以看到 效果如下

可以看出,Ribbon 預設使用輪巡的方式訪問服務源,這是一種負載均衡策略.

 

Ribbon 負載均衡策略與自定義配置

Ribbon總共有7中負載均衡策略

  1. RandomRule: 隨機策略,隨機選擇 server
  2. RoundRobinRule: 輪詢策略,按順序迴圈選擇 server (這是預設策略, BaseLoadBalancer 中預設指定的)
  3. RetryRule: 重試策略, 在一個配置時間段內選擇 server不成功,則一直嘗試選擇一個可用的 server
  4. BestAvailableRule: 最低併發策略, 逐個考察server,如果server 斷路器開啟則忽略. 再選擇其中一個併發連線最低的 server
  5. AvailabilityFilteringRule: 可用過濾策略, 過濾掉一直連線失敗並且標記為 circuit tripped 的 server.過濾掉那些高併發連線的 server (active connections 超過配置的閥值)
  6. ResponseTimeWeightenRule: 響應時間加權策略.根據 server 的響應時間分配權重,響應時間越長, 權重越低, 被選擇到的機率就越小. 響應時間越短,權重越高,被選擇到的機率越高. 這個策略綜合了很多因素,如: 網路,磁碟, IO, 等等.這些因素都會影響到響應時間.
  7. ZoneAvoidanceRule: 區域權衡策略.綜合判斷 server 所在區域的效能和 server 的可用性.輪詢選擇 server. 並且判斷一個 AWS Zone 的執行效能是否可用,剔除那些不可用的 Zone 中的所有 server.

全域性策略配置

使用 Ribbon 的時候想要全域性更改負載均衡策略,只需要加一個配置類,返回一個 負載均衡策略 bean 便可以 如:修改上例為 隨機策略

/**
 * 修改Ribbon 負載均衡策略的配置類.
 */
@Configuration
public class RibbonRuleConfig {

    //返回一個 隨機策略.
    @Bean
    public RandomRule randomRule() {
        return new RandomRule();
    }
}

然後在啟動上例專案的時候, 負載均衡策略就已經變更成了隨機策略了. 只要加上這個, 後續的所有 Ribbon 請求都將使用指定的策略.

基於註解的策略配置

編寫一個空註解

public @interface AvoidScan {
}

編寫應用於特定服務例項的的策略配置類

@Configuration
@AvoidScan
public class RoundRobinRuleConfig {

    @Resource
    private IClientConfig config;

    @Bean
    public IRule roundRobinRule(IClientConfig clientConfig) {
        return new RoundRobinRule();
    }
}

在主啟動類上加上註解

/**
 * @RibbonClient(name = "eureka-client-A", configuration = RoundRobinRuleConfig.class) 表示:
 *                          對 eureka-client-A 服務使用的策略是經過 RoundRobinRuleConfig 類配置的.
 * @ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {AvoidScan.class})}) 表示:
 *                  讓spring 不去掃描被 @AvoidScan 註解註釋的類,因為不能應用於全域性,所以只對 eureka-client-A 服務例項生效 .
 *
 * 也可以通過 @RibbonClients 註解來對多個服務員進行策略指定 如:
 * @RibbonClients(value = {
 * @RibbonClient(name = "client-a, configuration = TestA.class),
 * @RibbonClient(name = "client-b, configuration = TestB.class)
 *  })
 */
@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient(name = "eureka-client-A", configuration = RoundRobinRuleConfig.class)
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {AvoidScan.class})})
public class Chapter5RibbonLoadBalancerApplication {
    public static void main(String[] args) {
        SpringApplication.run(Chapter5RibbonLoadBalancerApplication.class, args);
    }

    //使用負載均衡,(使用預設的策略 輪巡策略)
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

個人感覺這種方式比較繁瑣,要編寫很多隻針對一個服務例項的程式碼

 

基於配置檔案的策略自定義

語法是: <client name>.ribbon.*  使用這種方式基本上不需要任何註解形式的配置程式碼 (我還是比較喜歡這種方式,很方便).比如下面對 服務使用 隨機策略. 首先把之前寫的配置類全部註釋掉.確認專案使用的是預設的策略. 然後在配置檔案當中新增一下內容:

#針對 eureka-client-A 服務例項 使用 指定的隨機策略./
eureka-client-A:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

然後啟動專案 進行訪問 http://localhost:7777/add?a=10&b=20  可以看到控制檯輸出

可以看出使用的是 隨機策略. 個人感覺這種方式很方便,因為一般來說我們的配置檔案都是遠端放在 git 上面的, 如果要更改策略的話, 直接修改配置檔案的內容就好了, 不需要像使用註解的方式那樣還要修改原始碼.然後重新編譯等等一系列的事情. 還是檔案配置比較方便

 

Ribbon 超時與重試

使用 HTTP 發起請求,請求超時好像是比較常見的問題,此時對呼叫進行時限控制和超時之後的重試還是比較重要的. Ribbon 的重試機制預設是開啟的.有需要的話可以新增對超時時間與重試機制策略的配置. 如: 

#針對 eureka-client-A 服務例項 使用 指定的隨機策略./
eureka-client-A:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    ConnectTimeout: 30000   #連線超時時間
    ReadTimeout: 30000
    MaxAutoRetries: 1  #對第一次請求的服務的重試次數 比如第一次請求 A 服務, 失敗了, 然後會在重試 1 次
    MaxAutoRetriesNextServer: 1 #要重試的下一個服務的最大數量 (不包括第一個服務)
    OkToRetryOnAllOperations: true

Ribbon 的飢餓載入

Ribbon 在進行客戶端負載均衡的時候並不是在啟動的時候就載入上下文,而是在實際請求到達的時候才回去建立,因此這個特性往往會讓第一次呼叫用時比較久,有可能會超時. 因此我們可以通過指定 Ribbon 具體的客戶端來開啟飢餓載入.意思就是,除了指定的 Ribbon 客戶端是飢餓載入之外,其他的所有配置項的應用上下文都是在啟動的時候就載入.

在配置檔案當中新增:

#配置指定使用飢餓載入的上下文,所有不在配置項的上下文都會在啟動的時候載入.
ribbon:
  eager-load:
    enabled: true
    clients: eureka-client-A, eureka-client-B, eureka-client-C

在預設情況下 Ribbon 客戶端會從 Eureka 註冊中心讀取服務註冊資訊列表,來達到動態負載均衡的功能.當然也可以脫離 Eureka 使用,只需要在配置檔案當中配置禁止使用 Eureka 功能,然後手動指定源服務地址列表就可以.

ribbon:
  eureka:
    enable: false #禁用 Eureka 功能
client:
  ribbon:
    listOfServers: http://localhost:7070  #指定源服務地址列表

 

Ribbon解讀

介紹幾個 Ribbon 中的核心介面

IClientConfig:定義 Ribbon 中管理配置的介面, 預設實現 DefaultClientConfigImpl

IRule: 定義 Ribbon 中負載均衡策略的介面, 預設實現 ZoneAvoidanceRule

IPing: 定義定期 ping 服務檢查可用性的介面, 預設實現 DummyPing

ServerList<Server>: 定義獲取服務列表方法的介面, 預設實現 ConfigurationBasedServerList

ServerListFilter<Server>: 定義特定期望獲取服務列表方法的介面, 預設實現 ZonePreferenceServerListFilter

ILoadBalancer: 定義負載均衡選擇伺服器的核心方法的介面, 預設實現 ZoneAwareLoadBalancer

ServerListUpdater: 為DynamicServerListLoadBalancer 定義動態更新伺服器列表的介面. 預設實現 PollingServerListUpdater

 

如下所示

//使用負載均衡,(使用預設的策略 輪巡策略)
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

使用 @LoadBalanced 註解就可以使用負載均衡了,. 檢視該註解可以看到,

註釋中寫的很清楚, , 然後接著看 LoadBalancerClient 類,這個來表示一個 客戶端負載均衡器,該類裡面有四個很重要的方法,具體可以檢視方法上面的註釋. 

不過根據書上說的,我倒是沒有追蹤到自動配置類(也就是為該類的實現做初始化自動配置) LoadBalancerAutoConfiguration 類中去(能力有限,正在學習當中). 所以直接看 LoadBalancerAutoConfiguration  的程式碼吧.

類上的註釋表名, 只有在類中包含 RestTemplate 類的例項,並且包含LOadBalancerClient 例項,才會初始化這個 LoadBalancerAutoConfiguration, 我個人理解的是,正因為這個條件, 所以我們在主配置程式當中要手動初始化一個 RestTemplate 例項, 掃描到 Spring 當中.

在這個類中還有一個方法 loadBalancerRequestFactory, 這個方法用於建立 LoadBalancerRequest 例項, 供 LoadBalancerInterceptor 使用.

而在  LosdBalancerInterceptorConfig 靜態類中則是維護了 2 個例項, LoadBalancerInterceptor 與 RestTemplateCustomizer 例項.

RestTemplateCustomizer: 為每個 RestTemplate 繫結 LoadBalancerInterceptor 攔截器.可以看到在方法的最後面為 restTemplate 設定了一個攔截器進去.

LoadBalancerInterceptor: 攔截每一次 HTTP 請求, 將請求繫結到 Ribbon 負載均衡的宣告週期.然後繼續檢視該例項所在的類的實現,

發現這個類是實現了 Spring 維護的請求攔截器 ClientHttpRequestInterceptor, 而實現該 ClientHttpRequestInterceptor 攔截器, 並重寫其中的 interceptor 方法, 這個時候收到的所有請求就會進入 interceptor 方法體. 而在 interceptor 方法體當中,確實用傳入的LoadBalancerClient 的例項 loadBalancer 執行 execute 方法的. 而 LoadBalancerClient 只有一個實現類 RibbonLoadBalancerClient .然後追進去, 檢視 execute 方法.

首先獲取一個 ILoadBalancer 例項.,然後獲取 server.前面說過 ILoadBalancer 介面是定義負載均衡選擇伺服器的核心方法介面.所以 getServer() 方法應該就是負載均衡選擇具體服務例項的方法了.接著往裡面看

方法中呼叫了 chooseServer 方法, 該方法的作用是 從負載均衡器當中選擇一個伺服器, 使用了 key 值來獲取具體的服務例項.如果不指定 key, 則返回的是 null. 每個負載均衡器選擇介面(ILoadBalancer )的實現類的方法實現都不一樣, 具體可以檢視原始碼.這樣就將攔截到的 HTTP 請求 與負載均衡策略關聯起來了. 

現在回想一下, 好像這裡的這個負載均衡就是利用 RestTemplate 發起請求,  然後在用一個靜態內部類去實現 Spring 維護的 ClientHttpRequestInterceptor 請求攔截器, 利用該攔截器攔截每一次的請求, 然後強行將 請求 與負載均衡關聯起來. 然後發起請求呼叫,拿到結果. 比起直接發起請求來說, 這裡多了一個負載均衡,相當於是 多了一個選擇伺服器的操作.

IRule 介面是定義 Ribbon 負載均衡策略的父介面,所有策略都是基於它實現的. 主要邏輯是 choose() 方法. Ribbon 是通過 ILoadBalancer 來關聯 IRule 的. ILoadBanancer 的chooseServer 會轉換成 IRule 的 choose() 方法.

 

參考 <重新定義 Spring Cloud>  一書