【一起學原始碼-微服務】Ribbon 原始碼四:進一步探究Ribbon的IRule和IPing
前言
前情回顧
上一講深入的講解了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 和公眾號:壹枝花算不算浪漫,如若轉載請標明來源!
感興趣的小夥伴可關注個人公眾號:壹枝花算不算浪漫
相關推薦
【一起學原始碼-微服務】Ribbon 原始碼一:Ribbon概念理解及Demo除錯
前言 前情回顧 前面文章已經梳理清楚了Eureka相關的概念及原始碼,接下來開始研究下Ribbon的實現原理。 我們都知道Ribbon在spring cloud中擔當負載均衡的角色, 當兩個Eureka Client互相呼叫的時候,Ribbon能夠做到呼叫時的負載,保證多節點的客戶端均勻接收請求。(這個有點類
【一起學原始碼-微服務】Ribbon 原始碼二:通過Debug找出Ribbon初始化流程及ILoadBalancer原理分析
前言 前情回顧 上一講講了Ribbon的基礎知識,通過一個簡單的demo看了下Ribbon的負載均衡,我們在RestTemplate上加了@LoadBalanced註解後,就能夠自動的負載均衡了。 本講目錄 這一講主要是繼續深入RibbonLoadBalancerClient和Ribbon+Eureka整合的
【一起學原始碼-微服務】Ribbon 原始碼三:Ribbon與Eureka整合原理分析
前言 前情回顧 上一篇講了Ribbon的初始化過程,從LoadBalancerAutoConfiguration 到RibbonAutoConfiguration 再到RibbonClientConfiguration,我們找到了ILoadBalancer預設初始化的物件等。 本講目錄 這一講我們會進一步往下
【一起學原始碼-微服務】Ribbon 原始碼四:進一步探究Ribbon的IRule和IPing
前言 前情回顧 上一講深入的講解了Ribbon的初始化過程及Ribbon與Eureka的整合程式碼,與Eureka整合的類就是DiscoveryEnableNIWSServerList,同時在DynamicServerListLoadBalancer中會呼叫PollingServerListUpdater 進
【一起學原始碼-微服務】Ribbon原始碼五:Ribbon原始碼解讀彙總篇~
前言 想說的話 【一起學原始碼-微服務-Ribbon】專欄到這裡就已經全部結束了,共更新四篇文章。 Ribbon比較小巧,這裡是直接 讀的spring cloud 內嵌封裝的版本,裡面的各種configuration確實有點繞,不過看看第三講Ribbon初始化的過程總結圖就會清晰很多。 緊接著會繼續整理學習F
【一起學原始碼-微服務】Feign 原始碼三:Feign結合Ribbon實現負載均衡的原理分析
前言 前情回顧 上一講我們已經知道了Feign的工作原理其實是在專案啟動的時候,通過JDK動態代理為每個FeignClinent生成一個動態代理。 動態代理的資料結構是:ReflectiveFeign.FeignInvocationHandler。其中包含target(裡面是serviceName等資訊)和d
【一起學原始碼-微服務】Feign 原始碼一:原始碼初探,通過Demo Debug Feign原始碼
前言 前情回顧 上一講深入的講解了Ribbon的初始化過程及Ribbon與Eureka的整合程式碼,與Eureka整合的類就是DiscoveryEnableNIWSServerList,同時在DynamicServerListLoadBalancer中會呼叫PollingServerListUpdater 進
【一起學原始碼-微服務】Feign 原始碼二:Feign動態代理構造過程
前言 前情回顧 上一講主要看了@EnableFeignClients中的registerBeanDefinitions()方法,這裡面主要是 將EnableFeignClients註解對應的配置屬性注入,將FeignClient註解對應的屬性注入。 最後是生成FeignClient對應的bean,注入到Spr
【一起學原始碼-微服務】Hystrix 原始碼一:Hystrix基礎原理與Demo搭建
說明 原創不易,如若轉載 請標明來源! 歡迎關注本人微信公眾號:壹枝花算不算浪漫 更多內容也可檢視本人部落格:一枝花算不算浪漫 前言 前情回顧 上一個系列文章講解了Feign的原始碼,主要是Feign動態代理實現的原理,及配合Ribbon實現負載均衡的機制。 這裡我們講解一個新的元件Hystrix,也是和Fe
【一起學原始碼-微服務】Hystrix 原始碼二:Hystrix核心流程:Hystix非降級邏輯流程梳理
說明 原創不易,如若轉載 請標明來源! 歡迎關注本人微信公眾號:壹枝花算不算浪漫 更多內容也可檢視本人部落格:一枝花算不算浪漫 前言 前情回顧 上一講我們講了配置了feign.hystrix.enabled=true之後,預設的Targeter就會構建成HystrixTargter, 然後通過對應的Hystr
【一起學原始碼-微服務】Hystrix 原始碼三:Hystrix核心流程:Hystix降級、熔斷等原理剖析
說明 原創不易,如若轉載 請標明來源! 歡迎關注本人微信公眾號:壹枝花算不算浪漫 更多內容也可檢視本人部落格:一枝花算不算浪漫 前言 前情回顧 上一講我們講解了Hystrix在配合feign的過程中,一個正常的請求邏輯該怎樣處理,這裡涉及到執行緒池的建立、HystrixCommand的執行等邏輯。 如圖所示:
【一起學原始碼-微服務】Nexflix Eureka 原始碼二:EurekaServer啟動之配置檔案載入以及面向介面的配置項讀取
前言 上篇文章已經介紹了 為何要讀netflix eureka原始碼了,這裡就不再概述,下面開始正式原始碼解讀的內容。 如若轉載 請標明來源:一枝花算不算浪漫 程式碼總覽 還記得上文中,我們通過web.xml找到了eureka server入口的類EurekaBootStrap,這裡我們就先來簡單地看下: /
【一起學原始碼-微服務】Nexflix Eureka 原始碼三:EurekaServer啟動之EurekaServer上下文EurekaClient建立
前言 上篇文章已經介紹了 Eureka Server 環境和上下文初始化的一些程式碼,其中重點講解了environment初始化使用的單例模式,以及EurekaServerConfigure基於介面對外暴露配置方法的設計方式。這一講就是講解Eureka Server上下文初始化剩下的內容:Eureka Cli
【一起學原始碼-微服務】Nexflix Eureka 原始碼六:在眼花繚亂的程式碼中,EurekaClient是如何註冊的?
前言 上一講已經講解了EurekaClient的啟動流程,到了這裡已經有6篇Eureka原始碼分析的文章了,看了下之前的文章,感覺程式碼成分太多,會影響閱讀,後面會只擷取主要的程式碼,加上註釋講解。 這一講看的是EurekaClient註冊的流程,當然也是一塊核心,標題為什麼會寫上眼花繚亂呢?關於Eureka
【一起學原始碼-微服務】Nexflix Eureka 原始碼七:通過單元測試來Debug Eureka註冊過程
前言 上一講eureka client是如何註冊的,一直跟到原始碼傳送http請求為止,當時看eureka client註冊時如此費盡,光是找一個regiter的地方就找了半天,那麼client端傳送了http請求給server端,server端是如何處理的呢? 帶著這麼一個疑問 就開始今天原始碼的解讀了。
【一起學原始碼-微服務】Nexflix Eureka 原始碼八:EurekaClient登錄檔抓取 精妙設計分析!
前言 前情回顧 上一講 我們通過單元測試 來梳理了EurekaClient是如何註冊到server端,以及server端接收到請求是如何處理的,這裡最重要的關注點是登錄檔的一個數據結構:ConcurrentHashMap<String, Map<String, Lease<InstanceI
【一起學原始碼-微服務】Nexflix Eureka 原始碼九:服務續約原始碼分析
前言 前情回顧 上一講 我們講解了服務發現的相關邏輯,所謂服務發現 其實就是登錄檔抓取,服務例項預設每隔30s去註冊中心抓取一下注冊表增量資料,然後合併本地登錄檔資料,最後有個hash對比的操作。 本講目錄 今天主要是看下服務續約的邏輯,服務續約就是client端給server端傳送心跳檢測,告訴對方我還活著
【一起學原始碼-微服務】Nexflix Eureka 原始碼十:服務下線及例項摘除,一個client下線到底多久才會被其他例項感知?
前言 前情回顧 上一講我們講了 client端向server端傳送心跳檢查,也是預設每30鍾傳送一次,server端接收後會更新登錄檔的一個時間戳屬性,然後一次心跳(續約)也就完成了。 本講目錄 這一篇有兩個知識點及一個疑問,這個疑問是在工作中真真實實遇到過的。 例如我有服務A、服務B,A、B都註冊在同一個註
【一起學原始碼-微服務】Nexflix Eureka 原始碼十一:EurekaServer自我保護機制竟然有這麼多Bug?
前言 前情回顧 上一講主要講了服務下線,已經註冊中心自動感知宕機的服務。 其實上一講已經包含了很多EurekaServer自我保護的程式碼,其中還發現了1.7.x(1.9.x)包含的一些bug,但這些問題在master分支都已修復了。 服務下線會將服務例項從登錄檔中刪除,然後放入到recentQueue中,下
【一起學原始碼-微服務】Nexflix Eureka 原始碼十二:EurekaServer叢集模式原始碼分析
前言 前情回顧 上一講看了Eureka 註冊中心的自我保護機制,以及裡面提到的bug問題。 哈哈 轉眼間都2020年了,這個系列的文章從12.17 一直寫到現在,也是不容易哈,每天持續不斷學習,輸出部落格,這一段時間確實收穫很多。 今天在公司給組內成員分享了Eureka原始碼剖析,反響效果還可以,也算是感覺收