1.負載均衡Robbin
Eureka中已經幫我們集成了負載均衡組件:Ribbon,簡單修改代碼即可使用。
什麼是Ribbon:
接下來,我們就來使用Ribbon實現負載均衡。
1.1.啓動兩個服務實例
首先我們啓動兩個user-service實例,一個8081,一個8082。
Eureka監控面板:
1.2.開啓負載均衡
因爲Eureka中已經集成了Ribbon,所以我們無需引入新的依賴。直接修改代碼:
在RestTemplate的配置方法上添加@LoadBalanced
註解:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
修改調用方式,不再手動獲取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
我們進行源碼跟蹤:
繼續跟入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的,查看一下:
定義負載均衡的規則接口。
它有以下實現:
SpringBoot也幫我們提供了修改負載均衡規則的配置入口:
user-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
格式是:{服務名稱}.ribbon.NFLoadBalancerRuleClassName
,值就是IRule的實現類。
再次測試,發現結果變成了隨機:
1.5.重試機制
Eureka的服務治理強調了CAP原則中的AP,即可用性和可靠性。它與Zookeeper這一類強調CP(一致性,可靠性)的服務治理框架最大的區別在於:Eureka爲了實現更高的服務可用性,犧牲了一定的一致性,極端情況下它寧願接收故障實例也不願丟掉健康實例,正如我們上面所說的自我保護機制。
但是,此時如果我們調用了這些不正常的服務,調用就會失敗,從而導致其它服務不能正常工作!這顯然不是我們願意看到的。
我們現在關閉一個user-service實例:
因爲服務剔除的延遲,consumer並不會立即得到最新的服務列表,此時再次訪問你會得到錯誤提示:
但是此時,8081服務其實是正常的。
因此Spring Cloud 整合了Spring Retry 來增強RestTemplate的重試能力,當一次服務調用失敗後,不會立即拋出一次,而是再次重試另一個服務。
只需要簡單配置即可實現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宕機,也能通過另一臺服務實例獲取到結果!