1. 程式人生 > >[親測]SpringCloud之均衡Ribbon

[親測]SpringCloud之均衡Ribbon

一.負載均衡Ribbon

什麼是Ribbon:
在這裡插入圖片描述

接下來,我們就來使用Ribbon實現負載均衡。

1.1.啟動兩個服務例項

首先我們啟動兩個user-service例項,一個8090,一個8092。

在這裡插入圖片描述

Eureka監控面板:

在這裡插入圖片描述

1.2.開啟負載均衡

因為Eureka中已經集成了Ribbon,所以我們無需引入新的依賴。直接修改程式碼:

在RestTemplate的配置方法上新增@LoadBalanced註解:

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

修改呼叫方式,不再手動獲取ip和埠,而是直接通過服務名稱呼叫:

@Service
public class UserService {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    public List<User> queryUserByIds(List<Long> ids) {
        List<User>
users = new ArrayList<>(); // 地址直接寫服務名稱即可 String baseUrl = "http://user-service/user/"; ids.forEach(id -> { // 我們測試多次查詢, users.add(this.restTemplate.getForObject(baseUrl + id, User.class)); // 每次間隔500毫秒 try { Thread.
sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } }); return users; } }

訪問頁面,檢視結果:

在這裡插入圖片描述

完美!

1.3.原始碼跟蹤

為什麼我們只輸入了service名稱就可以訪問了呢?之前還要獲取ip和埠。

顯然有人幫我們根據service名稱,獲取到了服務例項的ip和埠。它就是LoadBalancerInterceptor

我們進行原始碼跟蹤:(Debug執行user-consumer-demo專案)

在這裡插入圖片描述

繼續跟入execute方法:發現獲取了8082埠的服務

在這裡插入圖片描述

再跟下一次,發現獲取的是8081:

在這裡插入圖片描述

1.4.負載均衡策略

Ribbon預設的負載均衡策略是簡單的輪詢,我們可以測試一下:

編寫測試類,在剛才的原始碼中我們看到攔截中是使用RibbonLoadBalanceClient來進行負載均衡的,其中有一個choose方法,是這樣介紹的:

在這裡插入圖片描述

現在這個就是負載均衡獲取例項的方法。

我們對注入這個類的物件,然後對其測試:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = UserConsumerDemoApplication.class)
public class LoadBalanceTest {

    @Autowired
    RibbonLoadBalancerClient client;

    @Test
    public void test(){
        for (int i = 0; i < 100; i++) {
            ServiceInstance instance = this.client.choose("user-service");
            System.out.println(instance.getHost() + ":" + instance.getPort());
        }
    }
}

結果:

在這裡插入圖片描述

符合了我們的預期推測,確實是輪詢方式。

我們是否可以修改負載均衡的策略呢?

繼續跟蹤原始碼,發現這麼一段程式碼:

在這裡插入圖片描述

我們看看這個rule是誰:

在這裡插入圖片描述

這裡的rule預設值是一個RoundRobinRule,看類的介紹:
在這裡插入圖片描述

這不就是輪詢的意思嘛。

我們注意到,這個類其實是實現了介面IRule的,檢視一下:

1525622817451

定義負載均衡的規則介面。

它有以下實現:

1525622876842

SpringBoot也幫我們提供了修改負載均衡規則的配置入口:

在這裡插入圖片描述

user-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

格式是:{服務名稱}.ribbon.NFLoadBalancerRuleClassName,值就是IRule的實現類。

再次測試,發現結果變成了隨機:

在這裡插入圖片描述

1.5.CAP

CAP原則:CAP原則又稱CAP定理,指的是在一個分散式系統中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分割槽容錯性),三者不可兼得

1.5.1 分割槽容錯性

大多數分散式系統都分佈在多個子網路。每個子網路就叫做一個區(partition)。分割槽容錯的意思是,區間通訊可能失敗。比如,一臺伺服器放在中國,另一臺伺服器放在美國,這就是兩個區,它們之間可能無法通訊。

在這裡插入圖片描述

上圖中,G1 和 G2 是兩臺跨區的伺服器。G1 向 G2 傳送一條訊息,G2 可能無法收到。系統設計的時候,必須考慮到這種情況。

一般來說,分割槽容錯無法避免,因此可以認為 CAP 的 P 總是成立。CAP 定理告訴我們,剩下的 C 和 A 無法同時做到。

1.5.2 一致性

Consistency 中文叫做"一致性"。意思是,寫操作之後的讀操作,必須返回該值。舉例來說,某條記錄是 v0,使用者向 G1 發起一個寫操作,將其改為 v1。

在這裡插入圖片描述

接下來,使用者的讀操作就會得到 v1。這就叫一致性。

在這裡插入圖片描述

問題是,使用者有可能向 G2 發起讀操作,由於 G2 的值沒有來得及發生變化,因此返回的是 v0。G1 和 G2 讀操作的結果不一致,這就不滿足一致性了。

在這裡插入圖片描述

為了讓 G2 也能變為 v1,就要在 G1 寫操作的時候,讓 G1 向 G2 傳送一條訊息,要求 G2 也改成 v1。

1543983040542

這樣的話,使用者向 G2 發起讀操作,也能得到 v1。

在這裡插入圖片描述

1.5.3 可用性

Availability 中文叫做"可用性",意思是隻要收到使用者的請求,伺服器就必須給出迴應。

使用者可以選擇向 G1 或 G2 發起讀操作。不管是哪臺伺服器,只要收到請求,就必須告訴使用者,到底是 v0 還是 v1,否則就不滿足可用性。

1.5.4 一致性和可用性的矛盾

一致性和可用性,為什麼不可能同時成立?答案很簡單,因為可能通訊失敗(即出現分割槽容錯)。

如果保證 G2 的一致性,那麼 G1 必須在寫操作時,鎖定 G2 的讀操作和寫操作。只有資料同步後,才能重新開放讀寫。鎖定期間,G2 不能讀寫,沒有可用性。

如果保證 G2 的可用性,那麼勢必不能鎖定 G2,所以一致性不成立。

綜上所述,G2 無法同時做到一致性和可用性。系統設計時只能選擇一個目標。如果追求一致性,那麼無法保證所有節點的可用性;如果追求所有節點的可用性,那就沒法做到一致性。

1.6.重試機制

Eureka的服務治理強調了CAP原則中的AP,即可用性和可靠性。它與Zookeeper這一類強調CP(一致性,分割槽容錯)的服務治理框架最大的區別在於:Eureka為了實現更高的服務可用性,犧牲了一定的一致性,極端情況下它寧願接收故障例項也不願丟掉健康例項,正如我們上面所說的自我保護機制。

但是,此時如果我們呼叫了這些不正常的服務,呼叫就會失敗,從而導致其它服務不能正常工作!這顯然不是我們願意看到的。

我們現在關閉一個user-service例項:

在這裡插入圖片描述

因為服務剔除的延遲,consumer並不會立即得到最新的服務列表,此時再次訪問你會得到錯誤提示:

在這裡插入圖片描述

但是此時,8081服務其實是正常的。

因此Spring Cloud 整合了Spring Retry 來增強RestTemplate的重試能力,當一次服務呼叫失敗後,不會立即丟擲異常,而是再次重試另一個服務。

只需要(user-consumer)簡單配置即可實現Ribbon的重試:

spring:
  cloud:
    loadbalancer:
      retry:
        enabled: true # 開啟Spring Cloud的重試功能
user-service:
  ribbon:
    ConnectTimeout: 250 # Ribbon的連線超時時間
    ReadTimeout: 1000 # Ribbon的資料讀取超時時間
    OkToRetryOnAllOperations: true # 是否對所有操作都進行重試
    MaxAutoRetriesNextServer: 1 # 切換例項的重試次數
    MaxAutoRetries: 1 # 對當前例項的重試次數

根據如上配置,當訪問到某個服務超時後,它會再次嘗試訪問下一個服務例項,如果不行就再換一個例項,如果不行,則返回失敗。切換次數取決於MaxAutoRetriesNextServer引數的值

引入spring-retry依賴

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

我們重啟user-consumer-demo,測試,發現即使user-service2宕機,也能通過另一臺服務例項獲取到結果!

在這裡插入圖片描述

再給大家推薦本人的另外一篇文章:
SpringCloud之熔斷器Hystrix+實踐