前言
前情回顧
上一講深入的講解了Ribbon的初始化過程及Ribbon與Eureka的整合代碼,與Eureka整合的類就是DiscoveryEnableNIWSServerList
,同時在DynamicServerListLoadBalancer
中會調用PollingServerListUpdater
進行定時更新Eureka注冊表信息到BaseLoadBalancer
中,默認30s調度一次。
本講目錄
我們知道Ribbon主要是由3個組件組成的:
- ILoadBalancer
- IRule
- IPing
其中ILoadBalancer
前面我們已經分析過了,接下來我們一起看看IRule
和IPing
中的具體實現。
目錄如下:
- 負載均衡默認Server選擇邏輯
- Ribbon實際執行http請求邏輯
- Ribbon中ping機制原理
- Ribbon中其他IRule負載算法初探
說明
原創不易,如若轉載 請標明來源!
博客地址:一枝花算不算浪漫
微信公眾號:壹枝花算不算浪漫
源碼分析
負載均衡默認Server選擇邏輯
還記得我們上一講說過,在Ribbon初始化過程中,默認的IRule
為ZoneAvoidanceRule
,這里我們可以通過debug看看,從RibbonLoadBalancerClient.getServer()
一路往下跟,這里直接看debug結果:
然后我們繼續跟ZoneAvoidanceRule.choose()
方法:
public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {
/**
* Method that provides an instance of {@link AbstractServerPredicate} to be used by this class.
*
*/
public abstract AbstractServerPredicate getPredicate();
/**
* Get a server by calling {@link AbstractServerPredicate#chooseRandomlyAfterFiltering(java.util.List, Object)}.
* The performance for this method is O(n) where n is number of servers to be filtered.
*/
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
}
這里是調用的ZoneAvoidanceRule
的父類中的choose()
方法,首先是拿到對應的ILoadBalancer
,然后拿到對應的serverList數據,接着調用chooseRoundRobinAfterFiltering()
方法,繼續往后跟:
public abstract class AbstractServerPredicate implements Predicate<PredicateKey> {
public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
if (eligible.size() == 0) {
return Optional.absent();
}
return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
}
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextIndex.get();
int next = (current + 1) % modulo;
if (nextIndex.compareAndSet(current, next) && current < modulo)
return current;
}
}
}
到了這里可以看到incrementAndGetModulo()
方法就是處理serverList輪詢的算法,這個和RoudRobinRule
中采用的是一樣的算法,這個算法大家可以品一品,我這里也會畫個圖來說明下:
看了圖=中的例子估計大家都會明白了,這個算法就是依次輪詢。這個算法寫的很精簡。
Ribbon實際執行http請求邏輯
我們上面知道,我們按照輪詢的方式從serverList取到一個server后,那么怎么把之前原有的類似於:http://ServerA/sayHello/wangmeng
中的ServerA給替換成請求的ip數據呢?
接着我們繼續看RibbonLoadBalancerClient.execute()
方法,這個里面request.apply()
會做一個serverName的替換邏輯。
最后可以一步步跟到RibbonLoadBalancerClient.reconstructURI()
,這個方法是把請求自帶的getURI方法給替換了,我們最后查看context.reconstructURIWithServer()
方法,debug結果如圖,這個里面會一步步把對應的請求url給拼接起來:
Ribbon中ping機制原理
我們知道 Ribbon還有一個重要的組件就是ping機制,通過上一講Ribbon的初始化我們知道,默認的IPing實現類為:NIWSDiscoveryPing
,我們可以查看其中的isAlive()
方法:
public class NIWSDiscoveryPing extends AbstractLoadBalancerPing {
BaseLoadBalancer lb = null;
public NIWSDiscoveryPing() {
}
public BaseLoadBalancer getLb() {
return lb;
}
/**
* Non IPing interface method - only set this if you care about the "newServers Feature"
* @param lb
*/
public void setLb(BaseLoadBalancer lb) {
this.lb = lb;
}
public boolean isAlive(Server server) {
boolean isAlive = true;
if (server!=null && server instanceof DiscoveryEnabledServer){
DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server;
InstanceInfo instanceInfo = dServer.getInstanceInfo();
if (instanceInfo!=null){
InstanceStatus status = instanceInfo.getStatus();
if (status!=null){
isAlive = status.equals(InstanceStatus.UP);
}
}
}
return isAlive;
}
@Override
public void initWithNiwsConfig(
IClientConfig clientConfig) {
}
}
這里就是獲取到DiscoveryEnabledServer
對應的注冊信息是否為UP
狀態。那么 既然有個ping的方法,肯定會有方法進行調度的。
我們可以查看isAlive()
調用即可以找到調度的地方。
在BaseLoadBalancer
構造函數中會調用setupPingTask()
方法,進行調度:
protected int pingIntervalSeconds = 10;
void setupPingTask() {
if (canSkipPing()) {
return;
}
if (lbTimer != null) {
lbTimer.cancel();
}
lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
true);
lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
forceQuickPing();
}
這里pingIntervalSeconds
在BaseLoadBalancer
中定義的是10s,但是在initWithConfig()
方法中,通過傳入的時間覆蓋了原本的10s,這里實際的默認時間是30s。如下代碼:
我們也可以通過debug來看看:
可能大家好奇為什么要單獨截圖來說這個事,其實是因為網上好多博客講解都是錯的,都寫的是ping默認調度時間為10s,想必他們都是沒有真正debug過吧。
還是那句話,源碼出真知。
Ribbon中其他IRule負載算法初探
-
RoundRobinRule:系統內置的默認負載均衡規范,直接round robin輪詢,從一堆server list中,不斷的輪詢選擇出來一個server,每個server平攤到的這個請求,基本上是平均的
這個算法,說白了是輪詢,就是一台接着一台去請求,是按照順序來的
-
AvailabilityFilteringRule:這個rule就是會考察服務器的可用性
如果3次連接失敗,就會等待30秒后再次訪問;如果不斷失敗,那么等待時間會不斷變長
如果某個服務器的並發請求太高了,那么會繞過去,不再訪問這里先用round robin算法,輪詢依次選擇一台server,如果判斷這個server是存活的可用的,如果這台server是不可以訪問的,那么就用round robin算法再次選擇下一台server,依次循環往復,10次。
-
WeightedResponseTimeRule:帶着權重的,每個服務器可以有權重,權重越高優先訪問,如果某個服務器響應時間比較長,那么權重就會降低,減少訪問
-
ZoneAvoidanceRule:根據機房和服務器來進行負載均衡,說白了,就是機房的意思,看了源碼就是知道了,這個就是所謂的spring cloud ribbon環境中的默認的Rule
-
BestAvailableRule:忽略那些請求失敗的服務器,然后盡量找並發比較低的服務器來請求
總結
到了這里 Ribbon相關的就結束了,對於Ribbon注冊表拉取及更新邏輯這里也梳理下,這里如果Ribbon保存的注冊表信息有宕機的情況,這里最多4分鍾才能感知到,所以spring cloud還有一個服務熔斷的機制,這個后面也會講到。
申明
本文章首發自本人博客:https://www.cnblogs.com/wang-meng 和公眾號:壹枝花算不算浪漫,如若轉載請標明來源!
感興趣的小伙伴可關注個人公眾號:壹枝花算不算浪漫