接下來擼一擼負載均衡器的內部,看看是如何獲取服務實例,獲取以后做了哪些處理,處理后又是如何選取服務實例的。
分成三個部分來擼:
- 配置
- 獲取服務
- 選擇服務
配置
在上一篇《擼一擼Spring Cloud Ribbon的原理》的配置部分可以看到默認的負載均衡器是ZoneAwareLoadBalancer。
看一看配置類。
位置:
1
2
3
|
spring-cloud-netflix-core- 1.3 . 5 .RELEASE.jar org.springframework.cloud.netflix.ribbon RibbonClientConfiguration. class |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@SuppressWarnings ( "deprecation" ) @Configuration @EnableConfigurationProperties //Order is important here, last should be the default, first should be optional // see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653 @Import ({OkHttpRibbonConfiguration. class , RestClientRibbonConfiguration. class , HttpClientRibbonConfiguration. class }) public class RibbonClientConfiguration { // 略 @Bean @ConditionalOnMissingBean public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) { if ( this .propertiesFactory.isSet(ILoadBalancer. class , name)) { return this .propertiesFactory.get(ILoadBalancer. class , config, name); } return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList, serverListFilter, serverListUpdater); } // 略 } |
在實例化ZoneAwareLoadBalancer的時候注入了,config、rule、ping、serverList、serverListFilter、serverListUpdater實例。
config:配置實例。
rule:負載均衡策略實例。
ping:ping實例。
serverList:獲取和更新服務的實例。
serverListFilter:服務過濾實例。
serverListUpdater:服務列表信息更新實例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
@SuppressWarnings ( "deprecation" ) @Configuration @EnableConfigurationProperties //Order is important here, last should be the default, first should be optional // see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653 @Import ({OkHttpRibbonConfiguration. class , RestClientRibbonConfiguration. class , HttpClientRibbonConfiguration. class }) public class RibbonClientConfiguration { // 略 @Bean @ConditionalOnMissingBean public IClientConfig ribbonClientConfig() { DefaultClientConfigImpl config = new DefaultClientConfigImpl(); config.loadProperties( this .name); return config; } @Bean @ConditionalOnMissingBean public IRule ribbonRule(IClientConfig config) { if ( this .propertiesFactory.isSet(IRule. class , name)) { return this .propertiesFactory.get(IRule. class , config, name); } ZoneAvoidanceRule rule = new ZoneAvoidanceRule(); rule.initWithNiwsConfig(config); return rule; } @Bean @ConditionalOnMissingBean public IPing ribbonPing(IClientConfig config) { if ( this .propertiesFactory.isSet(IPing. class , name)) { return this .propertiesFactory.get(IPing. class , config, name); } return new DummyPing(); } @Bean @ConditionalOnMissingBean @SuppressWarnings ( "unchecked" ) public ServerList<Server> ribbonServerList(IClientConfig config) { if ( this .propertiesFactory.isSet(ServerList. class , name)) { return this .propertiesFactory.get(ServerList. class , config, name); } ConfigurationBasedServerList serverList = new ConfigurationBasedServerList(); serverList.initWithNiwsConfig(config); return serverList; } @Bean @ConditionalOnMissingBean public ServerListUpdater ribbonServerListUpdater(IClientConfig config) { return new PollingServerListUpdater(config); } @Bean @ConditionalOnMissingBean public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) { if ( this .propertiesFactory.isSet(ILoadBalancer. class , name)) { return this .propertiesFactory.get(ILoadBalancer. class , config, name); } return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList, serverListFilter, serverListUpdater); } @Bean @ConditionalOnMissingBean @SuppressWarnings ( "unchecked" ) public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) { if ( this .propertiesFactory.isSet(ServerListFilter. class , name)) { return this .propertiesFactory.get(ServerListFilter. class , config, name); } ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter(); filter.initWithNiwsConfig(config); return filter; } @Bean @ConditionalOnMissingBean public RibbonLoadBalancerContext ribbonLoadBalancerContext( ILoadBalancer loadBalancer, IClientConfig config, RetryHandler retryHandler) { return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler); } // 略 } |
在這里配置相關的實例
config:DefaultClientConfigImpl。
rule:ZoneAvoidanceRule。
ping:DummyPing。
serverList:ConfigurationBasedServerList,基于配置的服務列表實例。
serverListFilter:ZonePreferenceServerListFilter。
serverListUpdater:PollingServerListUpdater。
要注意的是,在這里serverList的實例是ConfigurationBasedServerList,這是在未使用Eureka時獲取服務信息的實例,是從配置文件中獲取。
那么在和Eureka配合使用時,需要從 Eureka Server獲取服務信息,那該是哪個實例來做這件事情呢。
在啟用Eureka服務發現時,會首先會采用EurekaRibbonClientConfiguration配置類。
位置:
1
2
3
|
spring-cloud-netflix-eureka-client- 1.3 . 5 .RELEASE.jar org.springframework.cloud.netflix.ribbon.eureka EurekaRibbonClientConfiguration. class |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
@Configuration @CommonsLog public class EurekaRibbonClientConfiguration { // 略 @Bean @ConditionalOnMissingBean public IPing ribbonPing(IClientConfig config) { if ( this .propertiesFactory.isSet(IPing. class , serviceId)) { return this .propertiesFactory.get(IPing. class , config, serviceId); } NIWSDiscoveryPing ping = new NIWSDiscoveryPing(); ping.initWithNiwsConfig(config); return ping; } @Bean @ConditionalOnMissingBean public ServerList<?> ribbonServerList(IClientConfig config, Provider<EurekaClient> eurekaClientProvider) { 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; } // 略 } |
在首先采用了EurekaRibbonClientConfiguration配置后,實際上各實例變成了
config:DefaultClientConfigImpl。
rule:ZoneAvoidanceRule。
ping:NIWSDiscoveryPing。
serverList:DomainExtractingServerList,內部是DiscoveryEnabledNIWSServerList,實際上是通過服務發現獲取服務信息列表。
serverListFilter:ZonePreferenceServerListFilter。
serverListUpdater:PollingServerListUpdater。
獲取服務
在找到獲取服務信息入口前,先把負載均衡器的類繼承關系擼一下。
在ZoneAwareLoadBalancer的構造中調用了父類DynamicServerListLoadBalancer構造。
位置:
ribbon-loadbalancer-2.2.2.jar
com.netflix.loadbalancer
ZoneAwareLoadBalancer.class
在DynamicServerListLoadBalancer的構造中,調用了restOfInit函數。
ribbon-loadbalancer-2.2.2.jar
com.netflix.loadbalancer
DynamicServerListLoadBalancer.class
1
2
3
4
5
6
7
8
9
10
11
12
13
|
void restOfInit(IClientConfig clientConfig) { boolean primeConnection = this .isEnablePrimingConnections(); // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList() this .setEnablePrimingConnections( false ); enableAndInitLearnNewServersFeature(); updateListOfServers(); if (primeConnection && this .getPrimeConnections() != null ) { this .getPrimeConnections() .primeConnections(getReachableServers()); } this .setEnablePrimingConnections(primeConnection); LOGGER.info( "DynamicServerListLoadBalancer for client {} initialized: {}" , clientConfig.getClientName(), this .toString()); } |
先是通過調用enableAndInitLearnNewServersFeature方法啟動定時更新服務列表,然后立即調用updateListOfServers函數馬上獲取并更新服務列表信息。
先看下enableAndInitLearnNewServersFeature方法,實際上是調用了服務列表信息更新實例的start方法啟動定時更新功能。
1
2
3
4
5
6
7
8
9
|
/** * Feature that lets us add new instances (from AMIs) to the list of * existing servers that the LB will use Call this method if you want this * feature enabled */ public void enableAndInitLearnNewServersFeature() { LOGGER.info( "Using serverListUpdater {}" , serverListUpdater.getClass().getSimpleName()); serverListUpdater.start(updateAction); } |
這里的服務列表信息更新實例就是配置階段配置的PollingServerListUpdater實例,看一下這個類的構造和start方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
public class PollingServerListUpdater implements ServerListUpdater { // 略 private static long LISTOFSERVERS_CACHE_UPDATE_DELAY = 1000 ; // msecs; private static int LISTOFSERVERS_CACHE_REPEAT_INTERVAL = 30 * 1000 ; // msecs; // 略 private final AtomicBoolean isActive = new AtomicBoolean( false ); private volatile long lastUpdated = System.currentTimeMillis(); private final long initialDelayMs; private final long refreshIntervalMs; // 略 public PollingServerListUpdater(IClientConfig clientConfig) { this (LISTOFSERVERS_CACHE_UPDATE_DELAY, getRefreshIntervalMs(clientConfig)); } public PollingServerListUpdater( final long initialDelayMs, final long refreshIntervalMs) { this .initialDelayMs = initialDelayMs; this .refreshIntervalMs = refreshIntervalMs; } @Override 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" ); } } // 略 } |
從構造和常量定義看出來,延遲一秒執行,默認每隔30秒執行更新,可以通過配置修改間隔更新的時間。
從start方法看,就是開了一個定時執行的schedule,定時執行 updateAction.doUpdate()。
回到start方法調用方DynamicServerListLoadBalancer類中看一下UpdateAction實例的定義。
1
2
3
4
5
6
|
protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() { @Override public void doUpdate() { updateListOfServers(); } }; |
實際上就是調用了DynamicServerListLoadBalancer類的updateListOfServers方法,這跟啟動完定時更新后立即更新服務信息列表的路徑是一致的。
繼續看updateListOfServers方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public void updateListOfServers() { List<T> servers = new ArrayList<T>(); 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); } |
1.通過ServerList實例獲取服務信息列表。
2.通過ServerListFilter 實例對獲取到的服務信息列表進行過濾。
3.將過濾后的服務信息列表保存到LoadBalancerStats中作為狀態保持。
接下分別看一下。
1.通過ServerList實例獲取服務信息列表。
ServerList實例就是配置階段生成的DomainExtractingServerList,獲取服務信息都是委托給DiscoveryEnabledNIWSServerList。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
public class DiscoveryEnabledNIWSServerList extends AbstractServerList<DiscoveryEnabledServer>{ // 略 @Override public List<DiscoveryEnabledServer> getInitialListOfServers(){ return obtainServersViaDiscovery(); } @Override public List<DiscoveryEnabledServer> getUpdatedListOfServers(){ return obtainServersViaDiscovery(); } private List<DiscoveryEnabledServer> obtainServersViaDiscovery() { List<DiscoveryEnabledServer> serverList = new ArrayList<DiscoveryEnabledServer>(); if (eurekaClientProvider == null || eurekaClientProvider.get() == null ) { logger.warn( "EurekaClient has not been initialized yet, returning an empty list" ); return new ArrayList<DiscoveryEnabledServer>(); } 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<InstanceInfo> 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; } // 略 } |
可以看到其實就是通過Eureka客戶端從Eureka服務端獲取所有服務實例信息并把上線的包裝成DiscoveryEnabledServer實例,帶有zone信息,做到服務列表中。
2.通過ServerListFilter 實例對獲取到的服務信息列表進行過濾。
serverListFilte實例就是配置階段生成的ZonePreferenceServerListFilter,通過調用該實例的getFilteredListOfServers方法進行過濾。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
@Data @EqualsAndHashCode (callSuper = false ) public class ZonePreferenceServerListFilter extends ZoneAffinityServerListFilter<Server> { private String zone; @Override public void initWithNiwsConfig(IClientConfig niwsClientConfig) { super .initWithNiwsConfig(niwsClientConfig); if (ConfigurationManager.getDeploymentContext() != null ) { this .zone = ConfigurationManager.getDeploymentContext().getValue( ContextKey.zone); } } @Override public List<Server> getFilteredListOfServers(List<Server> servers) { List<Server> output = super .getFilteredListOfServers(servers); if ( this .zone != null && output.size() == servers.size()) { List<Server> local = new ArrayList<Server>(); for (Server server : output) { if ( this .zone.equalsIgnoreCase(server.getZone())) { local.add(server); } } if (!local.isEmpty()) { return local; } } return output; } } |
在getFilteredListOfServers方法里面,一上來是調用父類的同名方法先過濾,其實父類也是把和消費端同區域的服務給過濾出來使用,不僅如此,增加了些智能的判定,保證在故障/負載較高時或者可用實例較少時不進行同區域的過濾。
但是在ZonePreferenceServerListFilter.getFilteredListOfServers這里,就算父類沒做過過濾,這里依然要把同zone的服務給濾出來使用,誰叫這里的類是ZonePreference的呢。
這是比較怪異的地方,感覺父類的智能判定沒什么作用。
還是看看ZoneAffinityServerListFilter.getFilteredListOfServers做的辛苦工作吧。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
public class ZoneAffinityServerListFilter<T extends Server> extends AbstractServerListFilter<T> implements IClientConfigAware { // 略 private boolean shouldEnableZoneAffinity(List<T> filtered) { 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 ; } } } @Override public List<T> getFilteredListOfServers(List<T> servers) { if (zone != null && (zoneAffinity || zoneExclusive) && servers != null && servers.size() > 0 ){ List<T> filteredServers = Lists.newArrayList(Iterables.filter( servers, this .zoneAffinityPredicate.getServerOnlyPredicate())); if (shouldEnableZoneAffinity(filteredServers)) { return filteredServers; } else if (zoneAffinity) { overrideCounter.increment(); } } return servers; } // 略 } |
首先會將與消費端相同的zone的服務過濾出來,然后通過shouldEnableZoneAffinity(filteredServers)來判定是否可以采納同zone的服務,還是采用所有的服務。
在shouldEnableZoneAffinity方法內,對相同zone的服務做了一次snapshot,獲取這些服務的實例數量,平均負載,斷路的實例數進行計算判定。
可以看一下initWithNiwsConfig方法中關鍵指標的值。
判定條件:
斷路實例百分比>=0.8(斷路的實例數/服務的實例數量)
平均負載>=0.6
可用實例數<2(實例數量-斷路的實例數)
如果達到判定條件,那么就使用全部的服務,保證可用性。
但,上面也說了,因為ZonePreferenceServerListFilter本身總是會選用和消費端zone一致的服務,所以ZoneAffinityServerListFilter.getFilteredListOfServers中做的智能操作并沒什么用。
不過,當然可以通過自定義配置來采用ZoneAffinityServerListFilter實例。
3.將過濾后的服務信息列表保存到LoadBalancerStats中作為狀態保持。
跟進updateAllServerList(servers);
去,一步步深入,會發現,實際上是保存到LoadBalancerStats
中,并且這時候的服務是按照zone分組以HashMap<String, List<Server>>
結構保存的,key是zone。
選擇服務
實現了ILoadBalancer接口的負載均衡器,是通過實現chooseServer方法來進行服務的選擇,選擇后的服務做為目標請求服務。
看一下ZoneAwareLoadBalancer.chooseServer方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
@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(); Map<String, ZoneSnapshot> zoneSnapshot = 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<String> 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); } } |
注意這里有兩種用法:
1.通過配置ZoneAwareNIWSDiscoveryLoadBalancer.enabled=false關閉區域感知負載均衡,或者zone的個數<=1個。
2.采用區域感知,或者zone的個數>1。
一個個來看一下
1.通過配置ZoneAwareNIWSDiscoveryLoadBalancer.enabled=false關閉區域感知負載均衡,或者zone的個數<=1個。
這種情況下,調用了父類BaseLoadBalancer.chooseServer方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public Server chooseServer(Object key) { if (counter == null ) { counter = createCounter(); } counter.increment(); if (rule == null ) { return null ; } else { try { return rule.choose(key); } catch (Exception e) { logger.warn( "LoadBalancer [{}]: Error choosing server for key {}" , name, key, e); return null ; } } } |
這里使用的負載均衡策略rule實際上就是構造ZoneAwareLoadBalancer時傳進來的,在配置階段生成的ZoneAvoidanceRule策略實例。
1
2
3
4
5
6
7
8
9
10
11
|
public void setRule(IRule rule) { if (rule != null ) { this .rule = rule; } else { /* default rule */ this .rule = new RoundRobinRule(); } if ( this .rule.getLoadBalancer() != this ) { this .rule.setLoadBalancer( this ); } } |
假設,如果沒有配置,默認用的是RoundRobinRule策略實例。
2.采用區域感知,或者zone的個數>1。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
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(); Map<String, ZoneSnapshot> zoneSnapshot = 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<String> 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); } } |
在這種情況下默認使用ZoneAvoidanceRule負載均衡策略。
獲取zone的snapshot信息。
獲取可用的zone,通過觀察ZoneAvoidanceRule.getAvailableZones定義,不是可用zone的條件是:
- 所屬實例數==0。
- 故障率>0.99999或者平均負載<0。
- 如果不是上面兩種情況,就選擇負載最高的一個去除不作為可用的zone。
可用zone都獲取后,隨機選一個。
并從該zone中,通過ZoneAwareLoadBalancer的父類BaseLoadBalancer.chooseServer選取服務,上面整理過,BaseLoadBalancer里如果沒有傳入rule,那么默認使用RoundRobinRule策略輪尋一個服務。
其實,還是上面獲取服務中ZonePreferenceServerListFilter過濾器的問題,實際上過濾出來的只有一個和消費端相同的一個zone的服務,所以第2.部分的從可用zone中選取服務的功能是走不到,要走到就得把過濾器給換掉。
總結:
配置的負載均衡器會啟動schedule獲取服務信息,在使用了Eureka客戶端時,會從Eureka服務獲取所有服務實例信息,通過過濾器過濾出可以使用的服務,過濾器默認只過濾出與消費端相同zone的服務,如果要保證高可用可配置ZoneAffinityServerListFilter過濾器,過濾后的服務列表,通過實現了IRule接口的負載均衡策略選取對應的服務,如果是使用zone感知的策略,可以從負載情況良好的zone中選取合適的服務。
原文鏈接:https://www.cnblogs.com/kongxianghai/p/8456576.html