Spring Cloud Ribbon負載均衡策略詳解。
Spring Cloud Ribbon是一個基於HTTP和TCP的客戶端負載均衡工具,它基於Netflix Ribbon實現,Spring Cloud集成了Netflix Ribbon,只是對Netflix Ribbon進行了一次封裝。
這裏使用的ribbon的版本是:ribbon-loadbalancer-2.2.2.jar。
一,IRule接口
IRule接口定義了選擇負載均衡策略的基本操作。通過調用choose()方法,就可以選擇具體的負載均衡策略。
// 選擇目標服務節點
Server choose(Object var1);
// 設置負載均衡策略
void setLoadBalancer(ILoadBalancer var1);
// 獲取負載均衡策略
ILoadBalancer getLoadBalancer();
二,ILoadBalancer接口
ILoadBalancer接口定義了ribbon負載均衡的常用操作,有以下幾個方法:
void addServers(List<Server> var1);
Server chooseServer(Object var1);
void markServerDown(Server var1);
List<Server> getReachableServers();
List<Server> getAllServers();
三,AbstractLoadBalancerRule抽象類
AbstractLoadBalancerRule實現了IRule接口和IClientConfigAware接口,主要對IRule接口的2個方法進行了簡單封裝。
private ILoadBalancer ib;
// 設置負載均衡策略
public void setLoadBalancer(ILoadBalancer ib){
this.ib = ib;
}
// 獲取負載均衡策略
public ILoadBalancer getLoadBalancer(){
return this.ib;
}
AbstractLoadBalancerRule是每個負載均衡策略需要直接繼承的類,Ribbon提供的幾個負載均衡策略,都繼承了這個抽象類。同理,我們如果需要自定義負載均衡策略,也要繼承這個抽象類。
四,AbstractLoadBalancerRule的實現類
AbstractLoadBalancerRule的實現類就是ribbon的具體負載均衡策略,首先來看默認的輪詢策略。
1,輪詢策略(RoundRobinRule)
輪詢策略理解起來比較簡單,就是拿到所有的server集合,然後根據id進行遍歷。這裏的id是ip+端口,Server實體類中定義的id屬性如下:
this.id = host + ":" + port
這裏還有一點需要注意,輪詢策略有一個上限,當輪詢了10個服務端節點還沒有找到可用服務的話,輪詢結束。
2,隨機策略(RandomRule)
隨機策略:使用jdk自帶的隨機數生成工具,生成一個隨機數,然後去可用服務列表中拉取服務節點Server。如果當前節點不可用,則進入下一輪隨機策略,直到選到可用服務節點爲止。
這裏在while循環中,使用了Thread#interrupted()方法和Thread#yield()方法,使用的很巧妙,可以參考一下。
while(server == null) {
if (Thread.interrupted()) {
return null;
}
……
// 如果隨機打到的節點爲null,則再次循環隨機
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
// 如果節點不是存活狀態,則再次循環隨機
server = null;
Thread.yield();
}
}
3,可用過濾策略(AvailabilityFilteringRule)
策略描述:過濾掉連接失敗的服務節點,並且過濾掉高併發的服務節點,然後從健康的服務節點中,使用輪詢策略選出一個節點返回。
AvailabilityFilteringRule#choose()方法實現如下:
// 記錄輪詢次數,最多輪詢10次
int count = 0;
// 輪詢10次
for(Server server = roundRobinRule.choose(key); count++ <= 10; server = roundRobinRule.choose(key)) {
if (this.predicate.apply(new PredicateKey(server))) {
return server;
}
}
// 如果輪詢10次還沒有找到可用server,則執行父類中的篩選邏輯
return super.choose(key);
4,響應時間權重策略(WeightedResponseTimeRule)
策略描述:根據響應時間,分配一個權重weight,響應時間越長,weight越小,被選中的可能性越低。
如何計算權重呢?代碼邏輯位於WeightedResponseTimeRule$$ServerWeight#maintainWeights(),判斷邏輯還是挺複雜的,暫時沒時間看,先留着,以後有機會再研究。
注意,服務剛啓動時,由於統計信息不足,先使用輪詢策略。等到信息足夠了,切換到WeightedResponseTimeRule策略。
5,輪詢失敗重試策略(RetryRule)
輪詢失敗重試策略(RetryRule)是這樣工作的,首先使用輪詢策略進行負載均衡,如果輪詢失敗,則再使用輪詢策略進行一次重試,相當於重試下一個節點,看下一個節點是否可用,如果再失敗,則直接返回失敗。
這裏還有一個點要注意,重試的時間間隔,默認是500毫秒,我們可以自定義這個重試時間間隔。
this.maxRetryMillis = 500L;
另外,失敗重試策略的源碼中,RetryRule#choose()方法很有參考價值,如果我們需要自己實現調用接口的失敗重試功能的話,可以參考這個方法。這個方法中給我們展示瞭如何使用Thread#interrupted()和Thread#yield()。
6,併發量最小可用策略(BestAvailableRule)
策略描述:選擇一個併發量最小的server返回。如何判斷併發量最小呢?ServerStats有個屬性activeRequestCount,這個屬性記錄的就是server的併發量。輪詢所有的server,選擇其中activeRequestCount最小的那個server,就是併發量最小的服務節點。
接下來我們看源碼:
if (this.loadBalancerStats == null) {
return super.choose(key);
} else {
// 獲取所有的server
List<Server> serverList = this.getLoadBalancer().getAllServers();
int minimalConcurrentConnections = 2147483647;
long currentTime = System.currentTimeMillis();
Server chosen = null;
Iterator var7 = serverList.iterator();
while(var7.hasNext()) {
Server server = (Server)var7.next();
ServerStats serverStats = this.loadBalancerStats.getSingleServerStats(server);
// 判斷斷路器是否跳閘,如果沒有跳閘,繼續往下走。
if (!serverStats.isCircuitBreakerTripped(currentTime)) {
int concurrentConnections = serverStats.getActiveRequestCount(currentTime);
if (concurrentConnections < minimalConcurrentConnections) {
minimalConcurrentConnections = concurrentConnections;
chosen = server;
}
}
}
// 如果沒有找到併發量最小的服務節點,則使用父類的策略
if (chosen == null) {
return super.choose(key);
} else {
return chosen;
}
}
併發量最小可用策略(BestAvailableRule)的優點是:可以充分考慮每臺服務節點的負載,把請求打到負載壓力最小的服務節點上。但是缺點是:因爲需要輪詢所有的服務節點,如果集羣數量太大,那麼就會比較耗時。當然一般來說,幾十臺,幾百臺的集羣數量是不用考慮這個問題的。因此對於大部分的項目而言,是一個不錯的選擇。
7,ZoneAvoidanceRule
策略描述:複合判斷server所在區域的性能和server的可用性,來選擇server返回。
四,BaseLoadBalancer
BaseLoadBalancer是一個負載均衡器,是ribbon框架提供的負載均衡器。Spring Cloud對ribbon封裝以後,直接調用ribbon的負載均衡器來實現微服務客戶端的負載均衡。
這裏需要注意,ribbon框架本身提供了幾個負載均衡器,BaseLoadBalancer只是其中之一。
Spring Cloud是如何封裝ribbon框架的呢?Spring Cloud提供了2個接口:ServiceInstanceChooser和LoadBalancerClient,這2個接口就是客戶端負載均衡的定義。具體實現類是RibbonLoadBalancerClient。RibbonLoadBalancerClient#choose()方法根據微服務實例的serviceId,然後使用配置的負載均衡策略,打到對於的微服務實例節點上。
OK,到這裏,我們簡單梳理一下Spring Cloud集成ribbon後,負載均衡的執行邏輯。
1,Spring Cloud RibbonLoadBalancerClient#choose()調用ribbon框架的BaseLoadBalancer。
2,BaseLoadBalancer#chooseServer()選擇具體的負載均衡策略(RoundRibonRule),然後執行。
但是,RibbonLoadBalancerClient#choose()是在哪裏調用的呢?這裏用到了攔截器,@RibbonClient註解自動化配置類LoadBalancerAutoConfiguration.class中有兩個註解:
@ConditionalOnClass({RestTemplate.class})
@ConditionalOnClass({LoadBalancerClient.class})
也就是說,在RestTemplate.class和LoadBalancerClient.class存在的情況下,LoadBalancerInterceptor.class會攔截RestTemplate.class上的@LoadBalanced註解,然後將請求中的微服務實例名serviceId轉化爲具體的ip+端口,然後去請求目標服務節點。
OK,有點亂,我們再來梳理一下調用關係:
1,@LoadBalanced註解
2,org.springframework.web.client.RestTemplate
3,LoadBalancerAutoConfiguration.class
4,LoadBalancerInterceptor.class攔截org.springframework.web.client.RestTemplate的請求,注入客戶端負載均衡功能,發送請求到目標服務節點。
這就是Spring Cloud 集成的ribbon客戶端負載均衡。
五,如何自定義負載均衡策略
Ribbon不僅實現了幾種負載均衡策略,也爲開發者提供了自定義負載均衡策略的支持。
自定義負載均衡策略有3個關鍵點:
1,繼承抽象類AbstractLoadBalancerRule
2,自定義的負載均衡策略類,不能放在@ComponentScan所掃描的當前包和子包下。
3,在主啓動類上添加@RibbonClient註解,或者在配置文件中指定哪個微服務使用自定義負載均衡策略。
@RibbonClient註解的使用方法如下:
@RibbonClient(name="微服務名稱", configuration="自定義配置類.class")
配置文件中配置項如下:
springboot.微服務名稱.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
或者
springboot.微服務名稱.NFLoadBalancerRuleClassName=com.xxx.xxx.xxx.自定義負載均衡策略實現類
這裏需要注意,Spring Cloud微服務啓動類上有個註解@SpringBootApplication,這個註解的源碼上標註了@ScanComponent註解,也就是說,我們的微服務啓動類其實間接引入了@ComponentScan註解。
@ComponentScan註解的作用就是掃描當前包及其子包下的標有指定註解的bean,然後把它們注入到IOC容器。
@Component會掃描哪些註解呢?有4個註解,分別是:
@Component
@Service
@Controller
@Repository
另外,所有間接引入了上面4個註解的註解,最終也會被@ComponentScan掃描,比如我們常用的@Configuration,也會被@ComponentScan掃描。