通訊元件主要包括編碼&解碼器,清單過濾,調用代理用戶端,本地優先政策,容錯,重試以及逾時等相關内容。
LoadBalancerClient接口的所屬包org.springframework.cloud.client.loadbalancer,對其内容進行整理,可以得出如下圖的關系:
IRule有很多預設的實作類,這些實作類根據不同的算法和邏輯來處理負載均衡。在大多數情況下,這些預設的實作類是可以滿足需求的,如果有特性的需求,可以自己實作。
- BestAvailableRule 選擇最小請求數
- ClientConfigEnabledRoundRobinRule 輪詢
- RandomRule 随機選擇一個server
- RoundRobinRule 輪詢選擇server
- RetryRule 根據輪詢的方式重試
- WeightedResponseTimeRule 根據響應時間去配置設定一個weight ,weight越低,被選擇的可能性就越低
- ZoneAvoidanceRule 根據server的zone區域和可用性來輪詢選擇
過濾器ZoneAvoidanceRule源碼解讀
ZoneAvoidanceRule源碼解讀
public class ZoneAvoidanceRule extends PredicateBasedRule {
private static final Random random = new Random();
//使用CompositePredicate來進行服務執行個體清單過濾。
//組合過濾條件
private CompositePredicate compositePredicate;
public ZoneAvoidanceRule() {
super();
//主過濾條件
ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
//次過濾條件
AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
}
......
}
CompositePredicate源碼解讀
public class ZoneAvoidanceRule extends PredicateBasedRule {
private static final Random random = new Random();
//使用CompositePredicate來進行服務執行個體清單過濾。
//組合過濾條件
private CompositePredicate compositePredicate;
public ZoneAvoidanceRule() {
super();
//主過濾條件
ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
//次過濾條件
AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
}
......
}
ZoneAvoidancePredicate源碼解讀
//服務執行個體所在的Zone必須可用
public class ZoneAvoidancePredicate extends AbstractServerPredicate {
......
@Override
public boolean apply(@Nullable PredicateKey input) {
if (!ENABLED.get()) {
return true;
}
String serverZone = input.getServer().getZone();
if (serverZone == null) {
// there is no zone information from the server, we do not want to filter
// out this server
return true;
}
LoadBalancerStats lbStats = getLBStats();
if (lbStats == null) {
// no stats available, do not filter
return true;
}
if (lbStats.getAvailableZones().size() <= 1) {
// only one zone is available, do not filter
return true;
}
Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
if (!zoneSnapshot.keySet().contains(serverZone)) {
// The server zone is unknown to the load balancer, do not filter it out
return true;
}
logger.debug("Zone snapshots: {}", zoneSnapshot);
Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
logger.debug("Available zones: {}", availableZones);
//服務執行個體所在的Zone必須可用
if (availableZones != null) {
return availableZones.contains(input.getServer().getZone());
} else {
return false;
}
}
}
清單過濾
大概的實作類流程如下:
ServerList就是server的清單,IRule做負載均衡的關鍵參數就是server清單,就是從這裡擷取的。
LoadBalancerStats内部有兩個成員變量,一是upServerListZoneMap,二是serverStatsCache。
upServerListZoneMap定義為volatile
Map<String, List<? extends Server>>,其中存儲了zone和server清單的對應關系。
serverStatsCache定義為LoadingCache<Server,
ServerStats>,存儲了server和ServerStats對象的對應關系,其使用了guaua的cache。
根據以上兩種結構,能夠從每個server的狀态統計到整個zone的狀态。
Affinity即密切關系,這個filter是ribbon的區域親和性過濾。預設情況下,單獨使用ribbon,區域親和的過濾器預設是關閉的,可以通過修改配置打開。
ZoneAffinityServerListFilter先進行親和性過濾,之後再進行zone健康檢查,如果zone認為不健康,則不再執行過濾,認為zone不健康的條件如下:
- zone中斷路器打開的server比例達到了整個叢集中的80%
- server平均負載(即存活的請求數)達到60%
- server存活數小于2,即0或1
DefaultNIWSServerListFilter繼承自ZoneAffinityServerListFilter,預設沒有任何實作。
ZonePreferenceServerListFilter,spring-cloud自己實作的過濾器,即可以通過配置,強制使用配置的zone來過濾。
其實作也比較簡單,即先用ZoneAffinityServerListFilter來過濾,如果父類沒有啟用過濾則再用配置的zone來強制過濾。
如果沒有配置zone,則該filter退化為ZoneAffinityServerListFilter。
ServerListSubsetFilter這個類用于限制傳回server清單的數量,特别适用于叢集中有成百上千的server。因為讓用戶端http連接配接池跟每個server保持連結其實用處不大,它同樣會剔除不健康的server。
調用代理用戶端
WeightedResponseTimeRule,擷取平均響應時間作為權重計算的政策。
AvailabilityFilteringRule,判斷目前斷路器是否打開作為是否可用的政策。使用活躍請求數量選擇最小的server。
ZoneAvoidanceRule,擷取可用的所有zone,擷取zone狀态資訊。
ZoneAwareLoadBalancer,它有如下功能
- 當選擇server時能夠避免某個不健康的zone。
-
衡量zone健康狀況的關鍵名額是平均活躍請求,即某個zone的平均活躍請求=這個zone中,還未完成的請求/存活的server
當某個狀況不好的zone慢慢的發生逾時的時候(即大量的請求被卡住),這個名額是非常有效的。
- 此均衡器将會計算所有的存活的zone的狀況:當任何某個zone平均活躍請求達到配置的門檻值時,這個zone将會從被從存活清單中移除;如果多個zone達到門檻值,那麼具有平均server請求量最大的zone将會被移除;
- 一旦狀況最壞的zone被移除,那麼将會按照zone具有的執行個體數量作為機率來選擇某個zone。
- 選擇完zone,将會使用IRule來選擇一個server,預設的實作為ZoneAvoidanceRule(由于此時隻有一個zone,故ZoneAvoidanceRule退化為AvailabilityFilteringRule)。
來看一下核心方法chooseServer是如何實作的,我們已經知道了最終負載均衡的實作是ZoneAwareLoadBalancer,而它也重寫了chooseServer,它的choose過程如下:
單個zone的choose過程說明:
- 從LoadBalancerStats查詢存活的zone數量。
- 調用BaseLoadBalancer的chooseServer
- BaseLoadBalancer的實作很簡單,直接委托給具體的Rule來選擇
- 最終會使用ZoneAvoidanceRule,而此時zone的數量為1,ZoneAvoidanceRule的zone過濾選擇功能不起作用,退化為AvailabilityFilteringRule,即按照server斷路器打開或者并發量過大過濾掉一些server,再輪詢選擇一個。
多個zone的choose過程說明
- 從LoadBalancerStats查詢存活的zone數量。
- 發現>1,則調用ZoneAvoidanceRule進行zone的過濾和挑選。
- 挑選好zone之後,跟 單個zone的choose過程一樣了,委托給BaseLoadBalancer來進行具體的server挑選。
本中心優先政策
利用Ribbon的Zone-Affinity(區域親和性)來解決。Ribbon的Zone-Affinity的原理是:服務消費者在擷取注冊服務的清單時,添加一個filter來過濾注冊服務,根據配置選擇跟服務消費者在同一個區的服務,這樣一般可以降低延遲;如果過濾後的服務正忙或者異常,則停止區域感覺。進而可以做到基于實時的服務運作狀況,決定是否隻選擇本區域的服務,當本區域的服務發生故障時,可以快速的切換。
擴充IRule
可以通過以下配置項識别執行個體所在的區域:
eureka.instance.metadata-map.zone=xxx
深圳的服務全部加上:
eureka.instance.metadata-map.zone=sz
成都的服務全部加上:
eureka.instance.metadata-map.zone=cd
以達到本地優先效果。
重試
負載均衡的重試機制
ribbon:
OkToRetryOnAllOperations: true # 是否對所有請求都進行重試
MaxAutoRetries: 0 #重試次數
MaxAutoRetriesNextServer: 2 #重試切換執行個體次數
ConnectTimeout: 250
ReadTimeout: 300
retryableStatusCodes: 503,500 # 這裡不配置其實也可以,不配置的時候,僅僅當請求服務執行個體報錯的時候重試,配置了的時候,當請求服務執行個體出現這裡指定的狀态,也會重試
逾時
#ribbon請求連接配接的逾時時間,限制3秒内必須請求到服務,并不限制服務處理的傳回時間
ribbon.ConnectTimeout=3000
ribbon.SocketTimeout=5000
#請求處理的逾時時間 下級服務響應最大時間,超出時間消費方(路由也是消費方)傳回timeout
ribbon.ReadTimeout=5000
# 單獨設定某個服務的逾時時間,會覆寫其他的逾時時間限制,服務的名稱以注冊中心頁面顯示的名稱為準,逾時時間不可大于斷路器的逾時時間
#service-a.ribbon.ReadTimeout=50000
#service-a.ribbon.ConnectTimeout=50000
關注公衆号 soft張三豐