摘要:代碼如下定義了用來存儲(chǔ)負(fù)載均衡器各服務(wù)實(shí)例屬性和統(tǒng)計(jì)信息的對(duì)象。下面看一下負(fù)載均衡器增加了哪些內(nèi)容。
客戶端負(fù)載均衡Spring Cloud Ribbon
?Spring Cloud Ribbon是一個(gè)基于HTTP和TCP的客戶端負(fù)載均衡工具,基于Netflix Ribbon實(shí)現(xiàn)。
目錄客戶端負(fù)載均衡
源碼分析
負(fù)載均衡器(本文重點(diǎn))
負(fù)載均衡策略
配置詳解
自動(dòng)化配置
客戶端負(fù)載均衡&源碼分析?請(qǐng)?jiān)谏弦黄恼碌幕A(chǔ)上進(jìn)行下面的學(xué)習(xí),點(diǎn)擊這里閱讀上一篇
負(fù)載均衡器?下面我們看一下具體的的負(fù)載均衡器,也就是ILoadBalancer接口的實(shí)現(xiàn)類。
AbstractLoadBalancer?該類是ILoadBalancer接口的抽象實(shí)現(xiàn)類。
?在該抽象實(shí)現(xiàn)類中含有一個(gè)關(guān)于服務(wù)實(shí)例的分組枚舉類,該枚舉類主要有以下三種類型:
ALL:所有服務(wù)實(shí)例
STATUS_UP:正常服務(wù)的實(shí)例
STATUS_NOT_UP:停止服務(wù)的實(shí)例
?該抽象類下面的的函數(shù)有以下幾個(gè):
chooseServer():該函數(shù)通過調(diào)用接口中的chooseServer(Object key)實(shí)現(xiàn),其中參數(shù)key為null,表示在選擇具體服務(wù)實(shí)例時(shí)忽略key的條件判斷
List
LoadBalancerStats getLoadBalancerStats():定義了獲取LoadBalancerStats對(duì)象的方法,LoadBalancerStats對(duì)象被用來存儲(chǔ)負(fù)載均衡器中各個(gè)服務(wù)實(shí)例當(dāng)前的屬性和統(tǒng)計(jì)信息。這些信息可以用來觀察負(fù)載均衡器的運(yùn)行情況,同時(shí)也是用來制定負(fù)載均衡策略的重要依據(jù)。
BaseLoadBalancer?該類是Ribbon負(fù)載均衡器的基礎(chǔ)實(shí)現(xiàn)類,在該類中定義了很多關(guān)于負(fù)載均衡相關(guān)的基礎(chǔ)內(nèi)容。
?該類中定義并維護(hù)了兩個(gè)存儲(chǔ)服務(wù)實(shí)例Server對(duì)象的列表。一個(gè)用于存儲(chǔ)所有服務(wù)實(shí)例的清單,一個(gè)用于存儲(chǔ)正常服務(wù)的實(shí)例清單。代碼如下:
@Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL) protected volatile ListallServerList = Collections.synchronizedList(new ArrayList ()); @Monitor(name = PREFIX + "UpServerList", type = DataSourceType.INFORMATIONAL) protected volatile List upServerList = Collections.synchronizedList(new ArrayList ());
?定義了用來存儲(chǔ)負(fù)載均衡器各服務(wù)實(shí)例屬性和統(tǒng)計(jì)信息的LoadBalancerStats對(duì)象。
?定義了檢查服務(wù)實(shí)例是否正常的IPing對(duì)象,在BaseLoadBalancer中默認(rèn)為null,需要在構(gòu)造時(shí)注入它的實(shí)現(xiàn)。
?定義了檢查服務(wù)實(shí)例操作的執(zhí)行策略對(duì)象IPingStrategy,在BaseLoadBalancerz中默認(rèn)使用了該類中定義的靜態(tài)內(nèi)部類SerialPingStrategy。根據(jù)源碼,可以看到該策略采用線性遍歷ping服務(wù)實(shí)例的方式實(shí)現(xiàn)檢查。但是該策略在當(dāng)IPing的實(shí)現(xiàn)速度不理想或者Server列表過大時(shí),可能會(huì)影響系統(tǒng)性能。這時(shí)就需要自己去實(shí)現(xiàn)自己的IPing策略。
?定義了負(fù)載均衡的處理規(guī)則IRule對(duì)象,從BaseLoadBalancer中chooseServer(Object key)方法源碼中也可以看出它是將服務(wù)實(shí)例選擇的任務(wù)交給了IRule中的Server choose(Object key)方法。默認(rèn)的IRule實(shí)現(xiàn)是RoundRobinRule。
?啟動(dòng)Ping任務(wù),在BaseLoadBalancer的默認(rèn)構(gòu)造函數(shù)中,會(huì)直接啟動(dòng)一個(gè)用于定時(shí)檢查Server是否健康的任務(wù)。該任務(wù)默認(rèn)執(zhí)行的時(shí)間間隔為10s。
?實(shí)現(xiàn)了ILoadBalancer接口定義的負(fù)載均衡器應(yīng)該具備以下操作:
addServers(List
Server chooseServer(Object key):挑選一個(gè)具體的服務(wù)實(shí)例,上面介紹IRule的時(shí)候已經(jīng)說過,不再重說。
markServerDown(Server server):用來標(biāo)記某個(gè)服務(wù)實(shí)例暫停服務(wù)
List
List
?DynamicServerListLoadBalancer該類繼承于BaseLoadBalancer類,它是對(duì)基礎(chǔ)負(fù)載均衡器的擴(kuò)展。
?在該負(fù)載均衡器,實(shí)現(xiàn)了服務(wù)實(shí)例清單在運(yùn)行期的動(dòng)態(tài)更新能力;同時(shí),它還具備了對(duì)服務(wù)實(shí)例清單的過濾功能,我們可以通過過濾器來選擇性的獲取一批服務(wù)實(shí)例清單。
?下面看一下負(fù)載均衡器增加了哪些內(nèi)容。
ServerList?通過查看源碼,發(fā)現(xiàn)增加了一個(gè)關(guān)于服務(wù)列表的操作對(duì)象ServerList
public interface ServerList{ public List getInitialListOfServers(); public List getUpdatedListOfServers(); }
?該抽象接口定義了兩個(gè)抽象方法,如下:
List
List
?該抽象接口的實(shí)現(xiàn)類有很多,因?yàn)樵撠?fù)載均衡器中需要實(shí)現(xiàn)服務(wù)實(shí)例的動(dòng)態(tài)更新,那么就需要Ribbon具備訪問Eureka服務(wù)注冊(cè)中心獲取服務(wù)實(shí)例的能力,在DynamicServerListLoadBalancer默認(rèn)的ServerList是DomainExtractingServerList(默認(rèn)的實(shí)現(xiàn)是在EurekaRibbonClientConfiguration),源碼如下:
package org.springframework.cloud.netflix.ribbon.eureka; @Configuration public class EurekaRibbonClientConfiguration { @Bean @ConditionalOnMissingBean public ServerList> ribbonServerList(IClientConfig config, ProvidereurekaClientProvider) { if (this.propertiesFactory.isSet(ServerList.class, serviceId)) { return this.propertiesFactory.get(ServerList.class, config, serviceId); } DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList( config, eurekaClientProvider); DomainExtractingServerList serverList = new DomainExtractingServerList( discoveryServerList, config, this.approximateZoneFromHostname); return serverList; } }
?查看DomainExtractingServerList的源碼可以看出,該類中有一個(gè)ServerList
package org.springframework.cloud.netflix.ribbon.eureka; public class DomainExtractingServerList implements ServerList{ private ServerList list; private final RibbonProperties ribbon; private boolean approximateZoneFromHostname; public DomainExtractingServerList(ServerList list, IClientConfig clientConfig, boolean approximateZoneFromHostname) { this.list = list; this.ribbon = RibbonProperties.from(clientConfig); this.approximateZoneFromHostname = approximateZoneFromHostname; } @Override public List getInitialListOfServers() { List servers = setZones(this.list .getInitialListOfServers()); return servers; } @Override public List getUpdatedListOfServers() { List servers = setZones(this.list .getUpdatedListOfServers()); return servers; } }
?同時(shí),通過上面的源碼還可以看出,getInitialListOfServers()和getUpdatedListOfServers()方法的實(shí)現(xiàn)其實(shí)交給DiscoveryEnabledNIWSServerList來實(shí)現(xiàn)的,下面看一下DiscoveryEnabledNIWSServerList中這兩個(gè)方法的實(shí)現(xiàn)
package com.netflix.niws.loadbalancer; public class DiscoveryEnabledNIWSServerList extends AbstractServerList{ private static final Logger logger = LoggerFactory.getLogger(DiscoveryEnabledNIWSServerList.class); String clientName; String vipAddresses; boolean isSecure = false; boolean prioritizeVipAddressBasedServers = true; String datacenter; String targetRegion; int overridePort = DefaultClientConfigImpl.DEFAULT_PORT; boolean shouldUseOverridePort = false; boolean shouldUseIpAddr = false; private final Provider eurekaClientProvider; @Override public List getInitialListOfServers(){ return obtainServersViaDiscovery(); } @Override public List getUpdatedListOfServers(){ return obtainServersViaDiscovery(); } private List obtainServersViaDiscovery() { List serverList = new ArrayList (); if (eurekaClientProvider == null || eurekaClientProvider.get() == null) { logger.warn("EurekaClient has not been initialized yet, returning an empty list"); return new ArrayList (); } EurekaClient eurekaClient = eurekaClientProvider.get(); if (vipAddresses!=null){ for (String vipAddress : vipAddresses.split(",")) { // if targetRegion is null, it will be interpreted as the same region of client List listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion); for (InstanceInfo ii : listOfInstanceInfo) { if (ii.getStatus().equals(InstanceStatus.UP)) { if(shouldUseOverridePort){ if(logger.isDebugEnabled()){ logger.debug("Overriding port on client name: " + clientName + " to " + overridePort); } // copy is necessary since the InstanceInfo builder just uses the original reference, // and we don"t want to corrupt the global eureka copy of the object which may be // used by other clients in our system InstanceInfo copy = new InstanceInfo(ii); if(isSecure){ ii = new InstanceInfo.Builder(copy).setSecurePort(overridePort).build(); }else{ ii = new InstanceInfo.Builder(copy).setPort(overridePort).build(); } } DiscoveryEnabledServer des = new DiscoveryEnabledServer(ii, isSecure, shouldUseIpAddr); des.setZone(DiscoveryClient.getZone(ii)); serverList.add(des); } } if (serverList.size()>0 && prioritizeVipAddressBasedServers){ break; // if the current vipAddress has servers, we dont use subsequent vipAddress based servers } } } return serverList; } }
?上述代碼的主要邏輯是借助EurekaClient從服務(wù)注冊(cè)中心獲取到具體的服務(wù)實(shí)例(InstanceInfo)列表,首頁獲取到EurekaClient,然后更具邏輯服務(wù)名(vipAddress),獲取服務(wù)實(shí)例,將服務(wù)實(shí)例狀態(tài)為UP(正常服務(wù))的實(shí)例轉(zhuǎn)換為DiscoveryEnabledServer對(duì)象,最終放在一個(gè)列表里返回。
?在獲取到ServerList之后,DomainExtractingServerList會(huì)調(diào)用自身的setZones方法,源碼如下:
private ListsetZones(List servers) { List result = new ArrayList<>(); boolean isSecure = this.ribbon.isSecure(true); boolean shouldUseIpAddr = this.ribbon.isUseIPAddrForServer(); for (DiscoveryEnabledServer server : servers) { result.add(new DomainExtractingServer(server, isSecure, shouldUseIpAddr, this.approximateZoneFromHostname)); } return result; }
?通過源碼可以看出,該方法的主要作用是將DiscoveryEnabledNIWSServerList返回的List
?在DynamicServerListLoadBalancer類中有如下一段代碼,ServerListUpdater對(duì)象的實(shí)現(xiàn)就是對(duì)ServerList的更新
protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() { @Override public void doUpdate() { updateListOfServers(); } };
?下面看一下ServerListUpdater接口,該類內(nèi)部還定義了一個(gè)UpdateAction接口,下面看一下源碼:
package com.netflix.loadbalancer; public interface ServerListUpdater { public interface UpdateAction { void doUpdate(); } void start(UpdateAction updateAction); void stop(); String getLastUpdate(); long getDurationSinceLastUpdateMs(); int getNumberMissedCycles(); int getCoreThreads(); }
?下面是該接口方法的介紹
void doUpdate():該方法的實(shí)現(xiàn)內(nèi)容就是對(duì)ServerList的具體更新操作
void start(UpdateAction updateAction):啟動(dòng)更新服務(wù)器,傳入的UpdateAction對(duì)象為更新操作的具體實(shí)現(xiàn)
void stop():停止更新服務(wù)器
String getLastUpdate():獲取最近的更新時(shí)間戳
long getDurationSinceLastUpdateMs():獲取上一次更新到現(xiàn)在的時(shí)間間隔,單位ms
int getNumberMissedCycles():獲取錯(cuò)過的更新周期數(shù)
int getCoreThreads():獲取核心線程數(shù)
?下面看一下ServerListUpdater的具體實(shí)現(xiàn)類
PollingServerListUpdater:動(dòng)態(tài)服務(wù)列表更新的默認(rèn)策略,DynamicServerListLoadBalancer負(fù)載均衡器中的默認(rèn)實(shí)現(xiàn)就是該類,它通過定時(shí)任務(wù)的方式進(jìn)行服務(wù)列表的更新。
EurekaNotificationServerListUpdater:該更新器可以用于DynamicServerListLoadBalancer負(fù)載均衡器,但是它的觸發(fā)機(jī)制與PollingServerListUpdater不同,它需要利用Eureka的事件監(jiān)聽器來驅(qū)動(dòng)服務(wù)列表的更新操作。
?下面看一下PollingServerListUpdater的實(shí)現(xiàn),我們從start函數(shù)看起
public synchronized void start(final UpdateAction updateAction) { if (isActive.compareAndSet(false, true)) { final Runnable wrapperRunnable = new Runnable() { @Override public void run() { if (!isActive.get()) { if (scheduledFuture != null) { scheduledFuture.cancel(true); } return; } try { updateAction.doUpdate(); lastUpdated = System.currentTimeMillis(); } catch (Exception e) { logger.warn("Failed one update cycle", e); } } }; scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay( wrapperRunnable, initialDelayMs, refreshIntervalMs, TimeUnit.MILLISECONDS ); } else { logger.info("Already active, no-op"); } }
?通過上述代碼可以看出大致邏輯,創(chuàng)建了一個(gè)Runnable線程任務(wù),在線程中調(diào)用了UpdateAction的doUpdate()方法,最后再啟動(dòng)定時(shí)任務(wù),initialDelayMs默認(rèn)值1000ms,refreshIntervalMs默認(rèn)值是30*1000ms,也就是說更新服務(wù)實(shí)例在初始化之后延遲1s后開始執(zhí)行,并以30s為周期重復(fù)執(zhí)行。
ServerListFilter?下面我們回顧一下UpdateAction中doUpdate()方法的具體實(shí)現(xiàn),源碼如下:
public void updateListOfServers() { Listservers = new ArrayList (); if (serverListImpl != null) { servers = serverListImpl.getUpdatedListOfServers(); LOGGER.debug("List of Servers for {} obtained from Discovery client: {}", getIdentifier(), servers); if (filter != null) { servers = filter.getFilteredListOfServers(servers); LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", getIdentifier(), servers); } } updateAllServerList(servers); }
?在上述源碼可以看出,首先是調(diào)用了ServerList的getUpdatedListOfServers方法,這是用來從Eureka Server獲取正常的服務(wù)實(shí)例列表。在獲取完服務(wù)實(shí)例列表以后,我們會(huì)調(diào)用filter.getFilteredListOfServers(servers),此處的filter就是我們所要找的ServerListFilter。
?ServerListFilter接口非常簡單,僅僅有一個(gè)List
?在上面的圖中,ZonePreferenceServerListFilter的實(shí)現(xiàn)是Spring Cloud Ribbon中對(duì)Netflix Ribbon的擴(kuò)展實(shí)現(xiàn),其他都是Netflix Ribbon中的原生實(shí)現(xiàn)類。下面我們這些類的特點(diǎn)。
package com.netflix.loadbalancer; public abstract class AbstractServerListFilterimplements ServerListFilter { private volatile LoadBalancerStats stats; public void setLoadBalancerStats(LoadBalancerStats stats) { this.stats = stats; } public LoadBalancerStats getLoadBalancerStats() { return stats; } }
?該類是一個(gè)抽象過濾器,在這里定義了過濾時(shí)需要的一個(gè)重要依據(jù)對(duì)象LoadBalancerStats,該對(duì)象存儲(chǔ)了關(guān)于負(fù)載均衡器的一些屬性和統(tǒng)計(jì)信息等。
ZoneAffinityServerListFilter?該過濾器基于區(qū)域感知(Zone Affinity)的方式實(shí)現(xiàn)服務(wù)實(shí)例的過濾,它會(huì)根據(jù)提供服務(wù)的實(shí)例所處的區(qū)域(Zone)與消費(fèi)者自身所處區(qū)域(Zone)進(jìn)行比較,過濾掉那些不是同處一個(gè)區(qū)域的實(shí)例。
public ListgetFilteredListOfServers(List servers) { if (zone != null && (zoneAffinity || zoneExclusive) && servers !=null && servers.size() > 0){ List filteredServers = Lists.newArrayList(Iterables.filter( servers, this.zoneAffinityPredicate.getServerOnlyPredicate())); if (shouldEnableZoneAffinity(filteredServers)) { return filteredServers; } else if (zoneAffinity) { overrideCounter.increment(); } } return servers; }
?從上面的源碼可以看出,對(duì)于服務(wù)實(shí)例列表的過濾是通過Iterables.filter(servers, this.zoneAffinityPredicate.getServerOnlyPredicate())來實(shí)現(xiàn)的,其中判斷依據(jù)由ZoneAffinityPredicate實(shí)現(xiàn)服務(wù)實(shí)例與消費(fèi)者的Zone比較。
?在比較過后,并不是立即返回過濾之后的ServerList。而是通過shouldEnableZoneAffinity方法來判斷是否要啟用區(qū)域感知的功能。下面看一下shouldEnableZoneAffinity的實(shí)現(xiàn):
private boolean shouldEnableZoneAffinity(Listfiltered) { if (!zoneAffinity && !zoneExclusive) { return false; } if (zoneExclusive) { return true; } LoadBalancerStats stats = getLoadBalancerStats(); if (stats == null) { return zoneAffinity; } else { logger.debug("Determining if zone affinity should be enabled with given server list: {}", filtered); ZoneSnapshot snapshot = stats.getZoneSnapshot(filtered); double loadPerServer = snapshot.getLoadPerServer(); int instanceCount = snapshot.getInstanceCount(); int circuitBreakerTrippedCount = snapshot.getCircuitTrippedCount(); if (((double) circuitBreakerTrippedCount) / instanceCount >= blackOutServerPercentageThreshold.get() || loadPerServer >= activeReqeustsPerServerThreshold.get() || (instanceCount - circuitBreakerTrippedCount) < availableServersThreshold.get()) { logger.debug("zoneAffinity is overriden. blackOutServerPercentage: {}, activeReqeustsPerServer: {}, availableServers: {}", new Object[] {(double) circuitBreakerTrippedCount / instanceCount, loadPerServer, instanceCount - circuitBreakerTrippedCount}); return false; } else { return true; } } }
?通過查看源碼可以看出,它調(diào)用了LoadBalancerStats的getZoneSnapshot方法來獲取這些過濾后的同區(qū)域?qū)嵗幕A(chǔ)指標(biāo)(包含實(shí)例數(shù)量、斷路由器斷開數(shù)、活動(dòng)請(qǐng)求數(shù)、實(shí)例平均負(fù)載等),然后根據(jù)一系列的算法求出下面的幾個(gè)評(píng)價(jià)值并與設(shè)置的閥值進(jìn)行對(duì)比,如果有一個(gè)條件符合,就不啟用區(qū)域感知過濾的服務(wù)實(shí)例清單。
?上述算法實(shí)現(xiàn)為集群出現(xiàn)區(qū)域故障時(shí),依然可以依靠其他區(qū)域的實(shí)例進(jìn)行正常服務(wù)提供了完善的高可用保障。
blackOutServerPercentage:故障實(shí)例百分比(斷路由器斷開數(shù)/實(shí)例數(shù)量)>=0.8
activeReqeustsPerServer:實(shí)例平均負(fù)載>=0.6
availableServers:可用實(shí)例數(shù)量(實(shí)例數(shù)量-斷路器斷開數(shù))<2
DefaultNIWSServerListFilter?該過濾器完全繼承自ZoneAffinityServerListFilter,是默認(rèn)的NIWS(Netflix Internal Web Service)過濾器。
ServerListSubsetFilter?該過濾器繼承自ZoneAffinityServerListFilter,適合擁有大規(guī)模服務(wù)集群(上百或更多)的系統(tǒng)。該過濾器可以產(chǎn)生一個(gè)區(qū)域感知結(jié)果的子集列表,同時(shí)還能夠通過比較服務(wù)實(shí)例的通信失敗數(shù)量和并發(fā)連接數(shù)來判定該服務(wù)是否健康來選擇性地從服務(wù)實(shí)例列表中剔除那些相對(duì)不夠健康的實(shí)例。該過濾器的實(shí)現(xiàn)主要有以下三步:
1.獲取區(qū)域感知的過濾結(jié)果,作為候選的服務(wù)實(shí)例清單。
2.從當(dāng)前消費(fèi)者維護(hù)的服務(wù)實(shí)例子集中剔除那些相對(duì)不夠健康的實(shí)例(同時(shí)將這些實(shí)例從候選清單中剔除,防止第三步的時(shí)候又被選入),不健康的標(biāo)準(zhǔn)如下:
?a. 服務(wù)實(shí)例的并發(fā)連接數(shù)超過客戶端配置的值,默認(rèn)為0,配置參數(shù)為
?b. 服務(wù)實(shí)例的失敗數(shù)超過客戶端配置的值,默認(rèn)為0,配置參數(shù)為
?c. 如果按符合上面任一規(guī)則的服務(wù)實(shí)例剔除后,剔除比例小于客戶端默認(rèn)配置的百分比,默認(rèn)為10%,配置參數(shù)為
3.在完成剔除后,清單已經(jīng)少了至少10%的服務(wù)實(shí)例,最后通過隨機(jī)的方式從候選清單中選出一批實(shí)例加入到清單中,以保持服務(wù)實(shí)例子集與原來的數(shù)量一致,默認(rèn)的實(shí)例自己數(shù)量為20,配置參數(shù)為
?Spring Cloud整合時(shí)新增的過濾器。若使用Spring Cloud整合Eureka和Ribbon時(shí)會(huì)默認(rèn)使用該過濾器。它實(shí)現(xiàn)了通過配置或者Eureka實(shí)例元數(shù)據(jù)的所屬區(qū)域(Zone)來過濾出同區(qū)域的服務(wù)實(shí)例。下面看一下源碼:
@Override public ListgetFilteredListOfServers(List servers) { List output = super.getFilteredListOfServers(servers); if (this.zone != null && output.size() == servers.size()) { List local = new ArrayList<>(); for (Server server : output) { if (this.zone.equalsIgnoreCase(server.getZone())) { local.add(server); } } if (!local.isEmpty()) { return local; } } return output; }
?通過源碼分析可以得出以下幾個(gè)步驟:
首先通過父類的ZoneAffinityServerListFilter過濾器來獲得區(qū)域感知的服務(wù)實(shí)例列表
遍歷獲取的服務(wù)實(shí)例列表,取出根據(jù)消費(fèi)者配置預(yù)設(shè)的區(qū)域Zone來進(jìn)行過濾
過濾的結(jié)果如果是空直接返回區(qū)域感知的服務(wù)實(shí)例列表,如果不為空則返回過濾后的結(jié)果
ZoneAwareLoadBalancer?ZoneAwareLoadBalancer負(fù)載均衡器是對(duì)DynamicServerListLoadBalancer的擴(kuò)展。
?在DynamicServerListLoadBalancer中,并沒有對(duì)chooseServer函數(shù)進(jìn)行重寫,因此會(huì)采用BaseLoadBalancer中chooseServer,使用RoundRobinRule規(guī)則,以線性輪詢的方式來選擇調(diào)用的服務(wù)實(shí)例,該算法實(shí)現(xiàn)簡單并沒有區(qū)域(Zone)的概念,所以會(huì)把所有實(shí)例視為一個(gè)Zone下的節(jié)點(diǎn)看待,這樣就會(huì)周期性的產(chǎn)生跨區(qū)域(Zone)訪問的情況,由于跨區(qū)域會(huì)產(chǎn)生更高的延遲,這些跨區(qū)域的實(shí)例主要以用來防止區(qū)域性故障實(shí)現(xiàn)高可用為目的,不能作為常規(guī)的訪問實(shí)例。
?ZoneAwareLoadBalancer可以有效的避免DynamicServerListLoadBalancer的問題。下面我們來看一下是如何避免這個(gè)問題的。
?首先,在ZoneAwareLoadBalancer中并沒有重寫setServerList,說明實(shí)現(xiàn)服務(wù)實(shí)例清單的更新主邏輯沒有修改。但是ZoneAwareLoadBalancer中重寫了setServerListForZones(Map
?下面我們先看一下DynamicServerListLoadBalancer中setServerListForZones中的實(shí)現(xiàn):
@Override public void setServersList(List lsrv) { super.setServersList(lsrv); ListserverList = (List ) lsrv; Map > serversInZones = new HashMap >(); for (Server server : serverList) { // make sure ServerStats is created to avoid creating them on hot // path getLoadBalancerStats().getSingleServerStat(server); String zone = server.getZone(); if (zone != null) { zone = zone.toLowerCase(); List servers = serversInZones.get(zone); if (servers == null) { servers = new ArrayList (); serversInZones.put(zone, servers); } servers.add(server); } } setServerListForZones(serversInZones); } protected void setServerListForZones( Map > zoneServersMap) { LOGGER.debug("Setting server list for zones: {}", zoneServersMap); getLoadBalancerStats().updateZoneServerMapping(zoneServersMap); }
?通過分析源碼可以看出,setServerListForZones的調(diào)用位于更新服務(wù)實(shí)例清單setServersList函數(shù)的最后,在setServerListForZones的實(shí)現(xiàn)中,首先獲取了LoadBalancerStats對(duì)象,然后調(diào)用其updateZoneServerMapping方法,下面我們看一下該方法的具體實(shí)現(xiàn):
private ZoneStats getZoneStats(String zone) { zone = zone.toLowerCase(); ZoneStats zs = zoneStatsMap.get(zone); if (zs == null){ zoneStatsMap.put(zone, new ZoneStats(this.getName(), zone, this)); zs = zoneStatsMap.get(zone); } return zs; } public void updateZoneServerMapping(Map> map) { upServerListZoneMap = new ConcurrentHashMap >(map); // make sure ZoneStats object exist for available zones for monitoring purpose for (String zone: map.keySet()) { getZoneStats(zone); } }
?通過上述源碼可以看出,setServerListForZones方法的主要作用是根據(jù)按區(qū)域(Zone)分組的實(shí)例列表,為負(fù)載均衡器中的LoadBalancerStats對(duì)象創(chuàng)建ZoneStats并放入Map zoneStatsMap集合中,每一個(gè)區(qū)域?qū)?yīng)一個(gè)ZoneStats,它用于存儲(chǔ)每個(gè)Zone的一些狀態(tài)和統(tǒng)計(jì)信息。
?下面我們看一下ZoneAwareLoadBalancer負(fù)載均衡器中setServerListForZones方法的實(shí)現(xiàn):
@Override protected void setServerListForZones(Map> zoneServersMap) { super.setServerListForZones(zoneServersMap); if (balancers == null) { balancers = new ConcurrentHashMap (); } for (Map.Entry > entry: zoneServersMap.entrySet()) { String zone = entry.getKey().toLowerCase(); getLoadBalancer(zone).setServersList(entry.getValue()); } // check if there is any zone that no longer has a server // and set the list to empty so that the zone related metrics does not // contain stale data for (Map.Entry existingLBEntry: balancers.entrySet()) { if (!zoneServersMap.keySet().contains(existingLBEntry.getKey())) { existingLBEntry.getValue().setServersList(Collections.emptyList()); } } }
?首先創(chuàng)建了一個(gè)ConcurrentHashMap
?在創(chuàng)建完負(fù)載均衡器之后馬上調(diào)用setServersList方法為其設(shè)置對(duì)應(yīng)Zone區(qū)域的實(shí)例清單。
?第二個(gè)循環(huán)是對(duì)Zone區(qū)域中實(shí)例清單的檢查,看看是否有Zone區(qū)域下已經(jīng)沒有實(shí)例了,是的話就將balancers中對(duì)應(yīng)Zone區(qū)域的實(shí)例列表清空,該操作的作用是為了后續(xù)選擇節(jié)點(diǎn)時(shí),防止過時(shí)的Zone區(qū)域統(tǒng)計(jì)信息干擾具體實(shí)例的選擇算法。
?下面我們?cè)倏匆幌仑?fù)載均衡器是如何挑選服務(wù)實(shí)例,來實(shí)現(xiàn)對(duì)區(qū)域的識(shí)別的:
@Override public Server chooseServer(Object key) { if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) { logger.debug("Zone aware logic disabled or there is only one zone"); return super.chooseServer(key); } Server server = null; try { LoadBalancerStats lbStats = getLoadBalancerStats(); MapzoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats); logger.debug("Zone snapshots: {}", zoneSnapshot); if (triggeringLoad == null) { triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty( "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d); } if (triggeringBlackoutPercentage == null) { triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty( "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d); } Set availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get()); logger.debug("Available zones: {}", availableZones); if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) { String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones); logger.debug("Zone chosen: {}", zone); if (zone != null) { BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone); server = zoneLoadBalancer.chooseServer(key); } } } catch (Exception e) { logger.error("Error choosing server using zone aware logic for load balancer={}", name, e); } if (server != null) { return server; } else { logger.debug("Zone avoidance logic is not invoked."); return super.chooseServer(key); } }
?通過源碼可以看出,只有當(dāng)負(fù)載均衡器中維護(hù)的實(shí)例所屬的Zone區(qū)域的個(gè)數(shù)大于1的時(shí)候才會(huì)執(zhí)行這里的選擇策略,否則還是將使用父類的實(shí)現(xiàn)。當(dāng)Zone區(qū)域的個(gè)數(shù)大于1的時(shí)候,它的實(shí)現(xiàn)步驟如下:
1.調(diào)用ZoneAvoidanceRule中的靜態(tài)方法createSnapshot(lbStats),為當(dāng)前負(fù)載均衡器中所有的Zone區(qū)域分別創(chuàng)建快照,保存在在Map zoneSnapshot中,這些快照中的數(shù)據(jù)將用于后續(xù)的算法。
2.調(diào)用ZoneAvoidanceRule中的靜態(tài)方法getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get()),來獲取可用的Zone區(qū)域集合,在該函數(shù)中會(huì)通過Zone區(qū)域快照中的統(tǒng)計(jì)數(shù)據(jù)來實(shí)現(xiàn)可用區(qū)的挑選
?a.首先會(huì)剔除符合這些規(guī)則的Zone區(qū)域:所屬實(shí)例數(shù)為0的Zone區(qū)域;Zone區(qū)域內(nèi)實(shí)例的平均負(fù)載小于0,或者實(shí)例故障率(斷路由器斷開次數(shù)/實(shí)例數(shù))大于等于閥值(默認(rèn)值為0.99999)
?b.然后根據(jù)Zone區(qū)域的實(shí)例平均負(fù)載計(jì)算出最差的Zone區(qū)域,這里的最差指的是實(shí)例平均負(fù)載最高的Zone區(qū)域
?c.如果在上面的過程中沒有符合剔除要求的區(qū)域,同時(shí)實(shí)例最大平均負(fù)載小于閥值(默認(rèn)20%),就直接返回所有Zone區(qū)域?yàn)榭捎脜^(qū)域。否則,從最壞Zone區(qū)域集合中隨機(jī)選擇一個(gè),將它從可用Zone區(qū)域集合中剔除。
3.當(dāng)獲得的可用Zone區(qū)域集合不為空,并且個(gè)數(shù)小于Zone區(qū)域總數(shù),就隨機(jī)選擇一個(gè)Zone區(qū)域
4.在確定了某個(gè)Zone區(qū)域后,則獲取了對(duì)應(yīng)Zone區(qū)域的負(fù)載均衡器,并調(diào)用chooseServer來選擇具體的服務(wù)實(shí)例,而在chooseServer中將使用IRule接口的choose方法來選擇具體的服務(wù)實(shí)例。在這里,IRule接口的實(shí)現(xiàn)會(huì)采用ZoneAvoidanceRule來挑選具體的服務(wù)實(shí)例。
后續(xù)?后面會(huì)介紹負(fù)載均衡策略的源碼分析,請(qǐng)繼續(xù)關(guān)注!!!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/76735.html
摘要:概要什么是實(shí)戰(zhàn)整合實(shí)現(xiàn)負(fù)載均衡是什么是一個(gè)客戶端負(fù)載均衡的組件什么是負(fù)載均衡負(fù)載均衡就是分發(fā)請(qǐng)求流量到不同的服務(wù)器目前的實(shí)現(xiàn)有軟件和硬件負(fù)載均衡分為兩種服務(wù)器端負(fù)載均衡如上圖所示服務(wù)器端負(fù)載均衡是對(duì)客戶透明的用戶請(qǐng)求到服務(wù)器真正的服務(wù)器是由 概要 什么是Spring Cloud Netflix Ribbon? 實(shí)戰(zhàn):整合Ribbon實(shí)現(xiàn)負(fù)載均衡 Spring Cloud Netfl...
摘要:客戶端負(fù)載均衡器是一個(gè)客戶端負(fù)載均衡器,可以讓你對(duì)和客戶端的行為進(jìn)行大量控制,已經(jīng)使用了,因此,如果你使用,此部分也適用。 客戶端負(fù)載均衡器:Ribbon Ribbon是一個(gè)客戶端負(fù)載均衡器,可以讓你對(duì)HTTP和TCP客戶端的行為進(jìn)行大量控制,F(xiàn)eign已經(jīng)使用了Ribbon,因此,如果你使用@FeignClient,此部分也適用。 Ribbon中的一個(gè)核心概念是命名客戶端,每個(gè)負(fù)載均...
摘要:第篇電影微服務(wù),使用在客戶端進(jìn)行負(fù)載均衡一大致介紹是發(fā)布的云中間層服務(wù)開源項(xiàng)目,主要功能是提供客戶端負(fù)載均衡算法。而被注解后,能過用負(fù)載均衡,主要是維護(hù)了一個(gè)被注解的列表,并給列表中的添加攔截器,進(jìn)而交給負(fù)載均衡器去處理。 SpringCloud(第 006 篇)電影微服務(wù),使用 Ribbon 在客戶端進(jìn)行負(fù)載均衡 - 一、大致介紹 1、Ribbon 是 Netflix 發(fā)布的云中間層...
摘要:本例中介紹如何使用來完成服務(wù)調(diào)用并實(shí)現(xiàn)負(fù)載均衡。即,對(duì)于注冊(cè)中心而言,生產(chǎn)者和調(diào)用者都是端。文件配置如下在文件中,我們將應(yīng)用命名為,端口為,表示注冊(cè)中心地址。 前言 Ribbon是Spring Cloud體系中完成負(fù)載均衡的重要組件。Spring Cloud體系中有兩種完成服務(wù)調(diào)用的組件,一種是Ribbon+RestTemplate,另一種Feign。Feign默認(rèn)使用的也是Ribbo...
摘要:客戶端負(fù)載均衡需要客戶端自己維護(hù)自己要訪問的服務(wù)實(shí)例清單,這些服務(wù)清單來源于注冊(cè)中心在使用進(jìn)行服務(wù)治理時(shí)。使用從負(fù)載均衡器中挑選出的服務(wù)實(shí)例來執(zhí)行請(qǐng)求內(nèi)容。 客戶端負(fù)載均衡Spring Cloud Ribbon ?Spring Cloud Ribbon是一個(gè)基于HTTP和TCP的客戶端負(fù)載均衡工具,基于Netflix Ribbon實(shí)現(xiàn)。 目錄 客戶端負(fù)載均衡(本文重點(diǎn)) 源碼分析(本...
摘要:第篇電影微服務(wù),使用配置文件配置在客戶端進(jìn)行負(fù)載均衡調(diào)度算法一大致介紹通過配置來設(shè)置客戶端進(jìn)行負(fù)載均衡的調(diào)度算法通過兩種代碼調(diào)用方式來測(cè)試客戶端負(fù)載均衡算法二實(shí)現(xiàn)步驟添加引用包模塊客戶端發(fā)現(xiàn)模塊 SpringCloud(第 008 篇)電影微服務(wù),使用 application.yml 配置文件配置 Ribbon 在客戶端進(jìn)行負(fù)載均衡調(diào)度算法 - 一、大致介紹 1、通過 applicat...
閱讀 2218·2021-09-30 09:47
閱讀 974·2021-08-27 13:01
閱讀 2965·2019-08-30 15:54
閱讀 3689·2019-08-30 15:53
閱讀 831·2019-08-29 14:07
閱讀 718·2019-08-28 18:16
閱讀 802·2019-08-26 18:37
閱讀 1412·2019-08-26 13:27