该博文为Ribbon与Eureka整合分析系列文章中的第二篇,主要分析服务过滤以及负载均衡算法。
在上一篇博文,介绍了在DynamicServerListLoadBalancer构造方法中,通过DiscoveryEnabledNIWSServerList从eureka处拉取服务实例。服务实例获取后,Ribbon通过ServerListFilter接口,对服务实例过滤进行一波过滤操作。
ServerListFilter接口定义如下
/**
* This interface allows for filtering the configured or dynamically obtained
* List of candidate servers with desirable characteristics.
*
* @author stonse
*
* @param
*/
public interface ServerListFilter<T extends Server> {
public List<T> getFilteredListOfServers(List<T> servers);
}
针对于ServerListFilter接口,存在如下继承体系
在RibbonClientConfiguration配置类中,选择ZonePreferenceServerListFilter作为服务过滤器。
@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;
}
ZonePreferenceServerListFilter过滤器,首先基于其父类ZoneAffinityServerListFilter,完成一波服务过滤操作,若服务实例没有过滤,则通过判断服务实例的zone与默认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<>();
for (Server server : output) {
if (this.zone.equalsIgnoreCase(server.getZone())) {
local.add(server);
}
}
if (!local.isEmpty()) {
return local;
}
}
return output;
}
ZoneAffinityServerListFilter引入了"区域感知"概念,用于解决当集群出现区域故障时,可以使用其他区域服务实例完成请求,从而实现高可用。整个过滤操作,是通过Google的Predicate接口,完成的过滤操作。
@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;
}
其中Iterables.filter(servers, this.zoneAffinityPredicate.getServerOnlyPredicate()),使用ZoneAffinityPredicate.apply方法,通过判断服务提供方与消费方是否处于同一区域,而进行过滤。完成过滤操作后,不是立即返回过滤结果,而是再次调用shouldEnableZoneAffinity方法,判断是否返回过滤的服务实例。
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;
}
}
}
在这里,通过实例数量,断路器断开数量、活动请求数等数据,分别计算出故障实例百分数(blackOutServerPercentage)、实例平均数(activeReqeustsPerServer)、可用实例数(availableServers)。且如下3个条件,任意一个条件满足,则不过滤
Ribbon通过ILoadBalancer接口,通过serverId,通过负载均衡算法,选择一个合适的服务实例,完成请求。Ribbon通过IRule接口,定义负载均衡算法。该接口定义如下所示:
public interface IRule{
/*
* choose one alive server from lb.allServers or
* lb.upServers according to key
*
* @return choosen Server object. NULL is returned if none
* server is available
*/
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
针对于IRule接口,存在如下实现
基于RibbonClientConfiguration配置类,Spring选择ZoneAvoidanceRule,作为IRule接口的默认实现。现简单介绍一下ZoneAvoidanceRule这一条分支,服务选择操作。
public List<Server> getEligibleServers(List<Server> servers, Object loadBalancerKey) {
if (loadBalancerKey == null) {
return ImmutableList.copyOf(Iterables.filter(servers, this.getServerOnlyPredicate()));
} else {
List<Server> results = Lists.newArrayList();
for (Server server: servers) {
if (this.apply(new PredicateKey(loadBalancerKey, server))) {
results.add(server);
}
}
return results;
}
}
ZoneAvoidanceRule实现类中,基于CompositePredicate指定过滤逻辑,而CompositePredicate内部,通过指定ZoneAvoidancePredicate和AvailabilityPredicate两个联合过滤,其中以ZoneAvoidancePredicate为主,AvailabilityPredicate为辅。
CompositePredicate覆写AbstractServerPredicate的getEligibleServers方法,即先使用ZoneAvoidancePredicate完成服务实例过滤操作,然后在使用AvailabilityPredicate进一步过滤。
@Override
public List<Server> getEligibleServers(List<Server> servers, Object loadBalancerKey) {
List<Server> result = super.getEligibleServers(servers, loadBalancerKey);
Iterator<AbstractServerPredicate> i = fallbacks.iterator();
while (!(result.size() >= minimalFilteredServers && result.size() > (int) (servers.size() * minimalFilteredPercentage))
&& i.hasNext()) {
AbstractServerPredicate predicate = i.next();
result = predicate.getEligibleServers(servers, loadBalancerKey);
}
return result;
}
服务过滤
从Eureka获取服务实例后,会对服务实例进行过滤操作。默认使用ZonePreferenceServerListFilter过滤器,即先判断故障实例百分数、实例平均数、可用实例数因素,若满足时,则基于zone,进行过滤。否则,不过滤。
负载均衡算法
默认选择ZoneAvoidanceRule作为负载均衡算法,在该算法中,同时考虑单个server请求数、打开断路器服务实例等因素,进行服务选择。