這里使用的ribbon的版本是:ribbon-loadbalancer-2.2.2.jar。
一,IRule接口
IRule接口定義了選擇負載均衡策略的基本操作。通過調(diào)用choose()方法澳叉,就可以選擇具體的負載均衡策略。
// 選擇目標服務(wù)節(jié)點
Server choose(Object var1);
// 設(shè)置負載均衡策略
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實現(xiàn)了IRule接口和IClientConfigAware接口,主要對IRule接口的2個方法進行了簡單封裝瓶殃。
private ILoadBalancer ib;
// 設(shè)置負載均衡策略
public void setLoadBalancer(ILoadBalancer ib){
? ? this.ib = ib;
}
// 獲取負載均衡策略
public ILoadBalancer getLoadBalancer(){
? ? return this.ib;
}
AbstractLoadBalancerRule是每個負載均衡策略需要直接繼承的類充包,Ribbon提供的幾個負載均衡策略,都繼承了這個抽象類遥椿。同理基矮,我們?nèi)绻枰远x負載均衡策略,也要繼承這個抽象類冠场。
四家浇,AbstractLoadBalancerRule的實現(xiàn)類
AbstractLoadBalancerRule的實現(xiàn)類就是ribbon的具體負載均衡策略,首先來看默認的輪詢策略碴裙。
1钢悲,輪詢策略(RoundRobinRule)
輪詢策略理解起來比較簡單,就是拿到所有的server集合青团,然后根據(jù)id進行遍歷譬巫。這里的id是ip+端口咖楣,Server實體類中定義的id屬性如下:
this.id = host + ":" + port
這里還有一點需要注意督笆,輪詢策略有一個上限,當(dāng)輪詢了10個服務(wù)端節(jié)點還沒有找到可用服務(wù)的話诱贿,輪詢結(jié)束娃肿。
2,隨機策略(RandomRule)
隨機策略:使用jdk自帶的隨機數(shù)生成工具珠十,生成一個隨機數(shù)料扰,然后去可用服務(wù)列表中拉取服務(wù)節(jié)點Server。如果當(dāng)前節(jié)點不可用焙蹭,則進入下一輪隨機策略晒杈,直到選到可用服務(wù)節(jié)點為止。
這里在while循環(huán)中孔厉,使用了Thread#interrupted()方法和Thread#yield()方法拯钻,使用的很巧妙,可以參考一下撰豺。
while(server == null) {
? ? if (Thread.interrupted()) {
? ? ? ? return null;
? ? }
……
? ? // 如果隨機打到的節(jié)點為null粪般,則再次循環(huán)隨機
? ? if (server == null) {
? ? ? ? Thread.yield();
? ? } else {
? ? ? ? if (server.isAlive()) {
? ? ? ? ? ? return server;
? ? ? ? }
? ? ? ? // 如果節(jié)點不是存活狀態(tài),則再次循環(huán)隨機
? ? ? ? server = null;
? ? ? ? Thread.yield();
? ? }
}
3污桦,可用過濾策略(AvailabilityFilteringRule)
策略描述:過濾掉連接失敗的服務(wù)節(jié)點亩歹,并且過濾掉高并發(fā)的服務(wù)節(jié)點,然后從健康的服務(wù)節(jié)點中,使用輪詢策略選出一個節(jié)點返回小作。
AvailabilityFilteringRule#choose()方法實現(xiàn)如下:
// 記錄輪詢次數(shù)亭姥,最多輪詢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,則執(zhí)行父類中的篩選邏輯
return super.choose(key);
4顾稀,響應(yīng)時間權(quán)重策略
(WeightedResponseTimeRule)
策略描述:根據(jù)響應(yīng)時間致份,分配一個權(quán)重weight,響應(yīng)時間越長础拨,weight越小氮块,被選中的可能性越低。
如何計算權(quán)重呢诡宗?代碼邏輯位于WeightedResponseTimeRule$$ServerWeight#maintainWeights()滔蝉,判斷邏輯還是挺復(fù)雜的,暫時沒時間看塔沃,先留著蝠引,以后有機會再研究。
注意蛀柴,服務(wù)剛啟動時螃概,由于統(tǒng)計信息不足,先使用輪詢策略鸽疾。等到信息足夠了吊洼,切換到WeightedResponseTimeRule策略。
5制肮,輪詢失敗重試策略(RetryRule)
輪詢失敗重試策略(RetryRule)是這樣工作的冒窍,首先使用輪詢策略進行負載均衡,如果輪詢失敗豺鼻,則再使用輪詢策略進行一次重試综液,相當(dāng)于重試下一個節(jié)點,看下一個節(jié)點是否可用儒飒,如果再失敗谬莹,則直接返回失敗。
這里還有一個點要注意桩了,重試的時間間隔附帽,默認是500毫秒,我們可以自定義這個重試時間間隔圣猎。
this.maxRetryMillis = 500L;
另外士葫,失敗重試策略的源碼中,RetryRule#choose()方法很有參考價值送悔,如果我們需要自己實現(xiàn)調(diào)用接口的失敗重試功能的話慢显,可以參考這個方法爪模。這個方法中給我們展示了如何使用Thread#interrupted()和Thread#yield()。
6荚藻,并發(fā)量最小可用策略(BestAvailableRule)
策略描述:選擇一個并發(fā)量最小的server返回屋灌。如何判斷并發(fā)量最小呢?ServerStats有個屬性activeRequestCount应狱,這個屬性記錄的就是server的并發(fā)量共郭。輪詢所有的server,選擇其中activeRequestCount最小的那個server疾呻,就是并發(fā)量最小的服務(wù)節(jié)點除嘹。
接下來我們看源碼:
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);
? ? ? ? // 判斷斷路器是否跳閘,如果沒有跳閘岸蜗,繼續(xù)往下走尉咕。
? ? ? ? if (!serverStats.isCircuitBreakerTripped(currentTime)) {
? ? ? ? ? ? int concurrentConnections = serverStats.getActiveRequestCount(currentTime);
? ? ? ? ? ? if (concurrentConnections < minimalConcurrentConnections) {
? ? ? ? ? ? ? ? minimalConcurrentConnections = concurrentConnections;
? ? ? ? ? ? ? ? chosen = server;
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? // 如果沒有找到并發(fā)量最小的服務(wù)節(jié)點,則使用父類的策略
? ? if (chosen == null) {
? ? ? ? return super.choose(key);
? ? } else {
? ? ? return chosen;
? ? }
}
并發(fā)量最小可用策略(BestAvailableRule)的優(yōu)點是:可以充分考慮每臺服務(wù)節(jié)點的負載璃岳,把請求打到負載壓力最小的服務(wù)節(jié)點上年缎。但是缺點是:因為需要輪詢所有的服務(wù)節(jié)點,如果集群數(shù)量太大铃慷,那么就會比較耗時单芜。當(dāng)然一般來說,幾十臺犁柜,幾百臺的集群數(shù)量是不用考慮這個問題的洲鸠。因此對于大部分的項目而言,是一個不錯的選擇赁温。
7坛怪,ZoneAvoidanceRule
策略描述:復(fù)合判斷server所在區(qū)域的性能和server的可用性淤齐,來選擇server返回股囊。
四,BaseLoadBalancer
BaseLoadBalancer是一個負載均衡器更啄,是ribbon框架提供的負載均衡器稚疹。Spring Cloud對ribbon封裝以后,直接調(diào)用ribbon的負載均衡器來實現(xiàn)微服務(wù)客戶端的負載均衡祭务。
這里需要注意内狗,ribbon框架本身提供了幾個負載均衡器,BaseLoadBalancer只是其中之一义锥。
Spring Cloud是如何封裝ribbon框架的呢柳沙?Spring Cloud提供了2個接口:ServiceInstanceChooser和LoadBalancerClient,這2個接口就是客戶端負載均衡的定義拌倍。具體實現(xiàn)類是RibbonLoadBalancerClient赂鲤。RibbonLoadBalancerClient#choose()方法根據(jù)微服務(wù)實例的serviceId噪径,然后使用配置的負載均衡策略,打到對于的微服務(wù)實例節(jié)點上数初。
OK找爱,到這里,我們簡單梳理一下Spring Cloud集成ribbon后泡孩,負載均衡的執(zhí)行邏輯车摄。
1,Spring Cloud RibbonLoadBalancerClient#choose()調(diào)用ribbon框架的BaseLoadBalancer仑鸥。
2吮播,BaseLoadBalancer#chooseServer()選擇具體的負載均衡策略(RoundRibonRule),然后執(zhí)行眼俊。
但是薄料,RibbonLoadBalancerClient#choose()是在哪里調(diào)用的呢?這里用到了攔截器泵琳,@RibbonClient注解自動化配置類LoadBalancerAutoConfiguration.class中有兩個注解:
@ConditionalOnClass({RestTemplate.class})
@ConditionalOnClass({LoadBalancerClient.class})
也就是說摄职,在RestTemplate.class和LoadBalancerClient.class存在的情況下,LoadBalancerInterceptor.class會攔截RestTemplate.class上的@LoadBalanced注解获列,然后將請求中的微服務(wù)實例名serviceId轉(zhuǎn)化為具體的ip+端口谷市,然后去請求目標服務(wù)節(jié)點。
OK击孩,有點亂迫悠,我們再來梳理一下調(diào)用關(guān)系:
1,@LoadBalanced注解
2巩梢,org.springframework.web.client.RestTemplate
3创泄,LoadBalancerAutoConfiguration.class
4,LoadBalancerInterceptor.class攔截org.springframework.web.client.RestTemplate的請求括蝠,注入客戶端負載均衡功能鞠抑,發(fā)送請求到目標服務(wù)節(jié)點。
這就是Spring Cloud 集成的ribbon客戶端負載均衡忌警。
五搁拙,如何自定義負載均衡策略
Ribbon不僅實現(xiàn)了幾種負載均衡策略,也為開發(fā)者提供了自定義負載均衡策略的支持法绵。
自定義負載均衡策略有3個關(guān)鍵點:
1箕速,繼承抽象類AbstractLoadBalancerRule
2,自定義的負載均衡策略類朋譬,不能放在@ComponentScan所掃描的當(dāng)前包和子包下盐茎。
3,在主啟動類上添加@RibbonClient注解徙赢,或者在配置文件中指定哪個微服務(wù)使用自定義負載均衡策略字柠。
@RibbonClient注解的使用方法如下:
@RibbonClient(name="微服務(wù)名稱", configuration="自定義配置類.class")
配置文件中配置項如下:
springboot.微服務(wù)名稱.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
或者
springboot.微服務(wù)名稱.NFLoadBalancerRuleClassName=com.xxx.xxx.xxx.自定義負載均衡策略實現(xiàn)類
這里需要注意滑进,Spring Cloud微服務(wù)啟動類上有個注解@SpringBootApplication,這個注解的源碼上標注了@ScanComponent注解募谎,也就是說扶关,我們的微服務(wù)啟動類其實間接引入了@ComponentScan注解。
@ComponentScan注解的作用就是掃描當(dāng)前包及其子包下的標有指定注解的bean数冬,然后把它們注入到IOC容器节槐。
@Component會掃描哪些注解呢?有4個注解拐纱,分別是:
@Component
@Service
@Controller
@Repository
另外铜异,所有間接引入了上面4個注解的注解,最終也會被@ComponentScan掃描秸架,比如我們常用的@Configuration揍庄,也會被@ComponentScan掃描。