目錄
- Spring-Cloud 學習筆記-(4)負載均衡器Ribbon
- 1、前言
- 2、什么是負載均衡
- 2.1、問題分析
- 2.2、什么是Ribbon
- 3、快速入門
- 3.1、實現方式一
- 3.1.1、修改代碼
- 3.2、實現方式二
- 3.2.1、啟動類
- 3.2.2、調用代碼
- 3.2.3、測試
- 3.2.4、實現原理
- 3.2.5、斷點調式
- 3.3、修改輪詢策略
- 3.4、重載機制
- 3.4.1、為什么要有重載機制
- 3.4.2、實現代碼
- 3.1、實現方式一
Spring-Cloud 學習筆記-(4)負載均衡器Ribbon
1、前言
上個章節我們做了什么?
上個章節我們說了用eureka來實現服務的注冊與發現,并且用過服務的seviceId拉取了服務列表
List<ServiceInstance> instances = discoveryClient.getInstances("user-service")
從而實現服務的調用方(order-service)調用服務的提供方(user-service)本章節我們會做什么?
負載均衡
2、什么是負載均衡
2.1、問題分析
? 上個章節我們實現了服務之間的調用,把原來代碼里面寫死的ip地址,換成用serviceId拉取服務列表,然后從服務列表中獲取實例的方式,雖然代碼變得復雜了,但是思想上我們得到的升級,但是還是存在一個問題,就是ServiceInstance instance = instances.get(0);
每次我們都取同一個ip,要想每次使用不同的ip,我們自己就要寫負載均衡算法,從多個實例當中獲取某一個實例進行調用,這次我們這一章節講的就是負載均衡器Ribbon
,它里面內置了很多負載均衡的算法,幫我們從實例列表中獲取一個實例。
2.2、什么是Ribbon
3、快速入門
我們按照上節課方法啟動,一個Eureka注冊中心,兩個(方便演示負載均衡)服務的提供方(user-service),最后我們修改服務調用方(order-service)代碼
pom文件
<!-- ribbon -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
3.1、實現方式一
3.1.1、修改代碼
//注入 RibbonLoadBalancerClient@Autowiredprivate LoadBalancerClient client;
...//通過serviceId 拉取服務列表//List<ServiceInstance> instances = discoveryClient.getInstances("user-service");//ServiceInstance instance = instances.get(0);ServiceInstance instance = client.choose("user-service");
String jsonStr = restTemplate.getForObject("http://"+instance.getHost()+":"+instance.getPort()+"/api/v1/user/2", String.class);
之前我們是通過serviceId(“user-service”)獲取到的是服務列表,現在我們直接可以通過serviceId,返回的是單個實例,不是因為列表里面只有一個實例,是因為choose
方法中已經幫我們做了復雜均衡了。
3.2、實現方式二
3.2.1、啟動類
/*** 把RestTemplate注入到Spring容器中*/
@Bean
@LoadBalanced //增加注解 讓RestTemplate內置一個負載均衡器
public RestTemplate restTemplate(){return new RestTemplate();
}
3.2.2、調用代碼
String jsonStr = restTemplate.getForObject("http://user-service/api/v1/user/2", String.class);
之前我們把serviceId交出去,別人幫我們取一堆,或者取一個,現在直接把serviceId寫在url路徑中。
3.2.3、測試
啟動order-service
訪問:http://localhost:8781/api/v1/order/2
3.2.4、實現原理
其實實現方式二低層就是用的實現方式一,只不過實現方式二加了一個攔截器LoadBalancerInterceptor
對RestTemplate請求做了攔截,然后把請求路徑中的serviceId("user-service")拿到,然后通過方式一獲取某一個實例進行調用。
3.2.5、斷點調式
我們按兩下shift搜索到LoadBalancerInterceptor
這個攔截器,在intercept方法中打好斷點
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {//1.拿到請求路徑URI originalUri = request.getURI();//2.從路徑中獲取serviceIdString serviceName = originalUri.getHost();Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);//3.執行execute方法.. 我們接著看execute方法return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}// RibbonLoadBalancerClient類中
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {//4.根據serviceId獲取到負載均衡器ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);//5.通過負載均衡器拿到某一個實例..我們接著看getServer方法Server server = this.getServer(loadBalancer);if (server == null) {throw new IllegalStateException("No instances available for " + serviceId);} else {RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));return this.execute(serviceId, ribbonServer, request);}}//RibbonLoadBalancerClient類中
protected Server getServer(ILoadBalancer loadBalancer) {//6.判斷負載均衡器是不是為空,不為空調用chooseServer("default")方法,我們接著看return loadBalancer == null ? null : loadBalancer.chooseServer("default");
}//BaseLoadBalancer類中
public Server chooseServer(Object key) {//這里key是默認值“default”if (this.counter == null) {this.counter = this.createCounter();}this.counter.increment();if (this.rule == null) {return null;} else {try {//根據rule調用choose方法,其中IRule是一個接口,有很多實現類,每一個實現類對于不同的負載均衡策略,比如RandomRule隨機,RoundRobinRule輪詢等,我們BaseLoadBalancer類中有一個屬性, private static final IRule DEFAULT_RULE = new RoundRobinRule();代表默認輪詢策略,有興趣的可以看一下每一個實現的choose方法,比如輪詢策略RoundRobinRule,底層維護一個自增長的count,每調用一次count++,然后每次用count模于服務列表的長度(比如第一次:1%5=1,第二次:2%5=2)得到的值為服務列表的位置索引,從而實現輪詢。return this.rule.choose(key);} catch (Exception var3) {logger.warn("LoadBalancer [{}]: Error choosing server for key {}", new Object[]{this.name, key, var3});return null;}}}
3.3、修改輪詢策略
application.yml:
#--負載均衡輪詢策略
# serviceId
user-service:ribbon:#負載均衡策略的classNameNFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
3.4、重載機制
3.4.1、為什么要有重載機制
在上一章中我們說到,一個正常的eureka客戶端,每間隔30秒沒有給服務器發送心跳,如果90秒服務器還沒有收到心跳,服務器就會認為這個客戶端已經宕機,但是eureka不會馬上剔除,沒間隔60會同意剔除這些失效的客戶端,這樣導致,我們服務實際上已經宕機了但是服務列表里面還有,這樣服務的消費者在調用的時候就會報錯,假設我們服務的提供方有五個,雖然只宕機了一臺,但是還有四臺是正常的,在這個時候我們條用這個服務如果出現報錯信息肯定不是我們希望看到的,所有我們就有了這個重載機制,Spring Cloud 整合了Spring Retry 來增強RestTemplate的重試能力,當一次服務調用失敗后,不會立即拋出一次,而是再次重試下一個服務。
3.4.2、實現代碼
application.yml
#服務名稱
spring:application:name: order-servicecloud:loadbalancer:retry:enabled: true # 開啟Spring Cloud的重試功能#負載均衡輪詢策略
user-service:ribbon:#負載均衡策略的classNameNFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRuleConnectTimeout: 250 # Ribbon的連接超時時間ReadTimeout: 1000 # Ribbon的數據讀取超時時間OkToRetryOnAllOperations: true # 是否對所有操作都進行重試MaxAutoRetriesNextServer: 1 # 切換實例的重試次數MaxAutoRetries: 1 # 對當前實例的重試次數
我們測試發現,就算我們user-service宕機了,也能通過另一臺服務實例獲取到結果!