1.負載均衡Robbin
在剛才的案例中,我們啟動了一個user-service,然後通過DiscoveryClient來獲取服務實例信息,然後獲取ip和端口來訪問。
但是實際環境中,我們往往會開啟很多個user-service的集群。此時我們獲取的服務列表中就會有多個,到底該訪問哪一個呢?
一般這種情況下我們就需要編寫負載均衡算法,在多個實例列表中進行選擇。
不過Eureka中已經幫我們集成了負載均衡組件:Ribbon,簡單修改代碼即可使用。
什麼是Ribbon:
接下來,我們就來使用Ribbon實現負載均衡。
1.1.啟動兩個服務實例
首先我們啟動兩個user-service實例,一個8081,一個8082。
Eureka監控面板:
1.2.開啟負載均衡
因為Eureka中已經集成了Ribbon,所以我們無需引入新的依賴。直接修改代碼:
在RestTemplate的配置方法上添加@LoadBalanced註解:
@Bean @LoadBalanced publicRestTemplaterestTemplate() { returnnewRestTemplate(newOkHttp3ClientHttpRequestFactory()); }
修改調用方式,不再手動獲取id和端口
修改調用方式,不再手動獲取ip和端口,而是直接通過服務名稱調用: @Service publicclassUserService{ @Autowired privateRestTemplaterestTemplate; @Autowired privateDiscoveryClientdiscoveryClient; publicListqueryUserByIds(Listids) { Listusers=newArrayList<>(); // 地址直接寫服務名稱即可 StringbaseUrl="http://user-service/user/"; ids.forEach(id->{ // 我們測試多次查詢, users.add(this.restTemplate.getForObject(baseUrl+id, User.class)); // 每次間隔500毫秒 try{ Thread.sleep(500); } catch(InterruptedExceptione) { e.printStackTrace(); } }); return users; } }
訪問頁面查看結果。
1.3.源碼跟蹤
為什麼我們只輸入了service名稱就可以訪問了呢?之前還要獲取ip和端口。
顯然有人幫我們根據service名稱,獲取到了服務實例的ip和端口。它就是LoadBalancerInterceptor
我們進行源碼跟蹤:
繼續跟入execute方法:發現獲取了8081端口的服務
再跟下一次,發現獲取的是8082:
1.4.負載均衡策略
Ribbon默認的負載均衡策略是簡單的輪詢,我們可以測試一下:
編寫測試類,在剛才的源碼中我們看到攔截中是使用RibbonLoadBalanceClient來進行負載均衡的,其中有一個choose方法,是這樣介紹的:
現在這個就是負載均衡獲取實例的方法。
我們對注入這個類的對象,然後對其測試
@RunWith(SpringRunner.class)
@SpringBootTest(classes=UserConsumerDemoApplication.class)
public class LoadBalanceTest{
@Autowired
RibbonLoadBalancerClientclient;
@Test
public void test(){
for(inti=0; i<100; i++) {
ServiceInstanceinstance=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依賴
org.springframework.retry spring-retry
我們重啟user-consumer-demo,測試,發現即使user-service2宕機,也能通過另一臺服務實例獲取到結果!
鳴謝
感謝各位的拜讀,關於微服務,我寫了很多篇文章,我也會持續的寫下去,這是第五篇,如果你覺得還可以,請您就關注我。你的支持,是我創作的動力。