前言
在上一篇文章OpenFeign最核心组件LoadBalancerFeignClient详解分析了OpenFeign的负载均衡客户端,OpenFeign使用LoadBalancerFeignClient
(下文简称LoadBalancerFC)替换默认的通信客户端feign.Client.Default
, LoadBalancerFC是具备负载均衡能力和通信的能力的客户端,而负载均衡能力是通过Ribbon提供的。
本文内容是Ribbon负载均衡的核心部分,Ribbon是一种具备客户端负载均衡能力
的技术。
上文已经分析到LoadBalancerFC最终会通过Ribbon
中的Rule
对象选择一个服务进行发起通信,但是没有详细分析Rule的选择服务流程,本文将继续详细分析。
先回顾上文讲到的选择服务方法,位于com.netflix.loadbalancer.reactive.LoadBalancerCommand#selectServer
方法
private Observable<Server> selectServer() {
return Observable.create(new OnSubscribe<Server>() {
@Override
public void call(Subscriber<? super Server> next) {
try {
//通过loadBalancerContext选择出服务
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server);
next.onCompleted();
} catch (Exception e) {
next.onError(e);
}
}
});
}
loadBalancerContext负责根据loadBalancer对象选择服务,而loadBalancer对象最终是通过Rule对象来获取服务的。
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
String host = null;
int port = -1;
//...
ILoadBalancer lb = getLoadBalancer();
//通过负载均衡获取服务
Server svc = lb.chooseServer(loadBalancerKey);
logger.debug("{} using LB returned Server: {} for request {}", new Object[]{
clientName, svc, original});
return svc;
}
loadBalancerContext负责根据LoadBalancer对象选择服务,而loadBalancer对象最终是通过Rule对象来获取服务的。
那么ILoadBalancer
对象和Rule
对象是如何初始化的呢?
LoadBalancer和Rule初始化
我们可以看到Ribbon的自动装配类RibbonClientConfiguration,找到LoadBalancer和Rule的默认配置类。
public class RibbonClientConfiguration {
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
//初始化LoadBalancer对象为ZoneAwareLoadBalancer
return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class, this.name) ? (ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class, config, this.name)
: new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater));
}
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
if (this.propertiesFactory.isSet(IRule.class, this.name)) {
return (IRule)this.propertiesFactory.get(IRule.class, config, this.name);
} else {
//初始化Rule对象为ZoneAvoidanceRule
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
}
}
通过ribbon的自动装配类看出:loadbalancer默认配置的是ZoneAwareLoadBalancer
,Rule类默认配置的是ZoneAvoidanceRule
,他们都使用了条件注解@ConditionalOnMissingBean,也就是我们如果有自定义的负载规则可以覆盖这两个默认配置
loadbalancer和Rule都是以Zone开头,Zone是区域的意思,也就是说他们能够按照区域实现负载均衡
,这种Rule是代表按区域负载均衡的机制,一般在大型互联网公司都会有多个机房,这种情况下就存在按区域负载的必要性了。
如下图一样,一个公司的服务会分区部署,不同的区的服务器资源都有可能不一样
我们可以看到每个服务类信息里都包含一个zone(区域)属性,就是代表这个服务所属的区域。
package com.netflix.loadbalancer;
public class Server {
private String host;
private int port = 80;
//服务所属区域
private String zone ;
private String scheme;
private volatile String id;
private volatile boolean isAliveFlag;
}
因此可以知道负载均衡的总体逻辑是先选可用分区,再从可用分区里选可用服务,
从源码也可以看出验证上面这个流程,下面就是主要逻辑。
@Override
public Server chooseServer(Object key) {
//1、按区域负载没有开启
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 {
//2、获取有所分区快照
LoadBalancerStats lbStats = getLoadBalancerStats();
Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
logger.debug("Zone snapshots: {}", zoneSnapshot);
//3、计算哪些是可用的分区
Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
logger.debug("Available zones: {}", availableZones);
//4、这里是可用分区小于总分区数的时候(说明当前存在有区域不可用的情况),从可用分区选择一个分区,如果所有区域都可用,可以不走区域负载逻辑
if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {
//5、随机选择一个分区
String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
logger.debug("Zone chosen: {}", zone);
if (zone != null) {
BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
//6、从可用区选择一个具体的服务
server = zoneLoadBalancer.chooseServer(key);
}
}
} catch (Exception e) {
logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
}
if (server != null) {
//7、通过区域负载获取到了可用服务,直接返回
return server;
} else {
//8、不使用按区域负载
logger.debug("Zone avoidance logic is not invoked.");
return super.chooseServer(key);
}
}
下面我们来看看详细的实现。
可用分区的选择
第一步获取分区的快照信息(各个分区服务可用负载情况)
public ZoneSnapshot getZoneSnapshot(List<? extends Server> servers) {
if (servers == null || servers.size() == 0) {
return new ZoneSnapshot();
}
int instanceCount = servers.size();
int activeConnectionsCount = 0;
int activeConnectionsCountOnAvailableServer = 0;
int circuitBreakerTrippedCount = 0;
double loadPerServer = 0;
long currentTime = System.currentTimeMillis();
for (Server server: servers) {
ServerStats stat = getSingleServerStat(server);
if (stat.isCircuitBreakerTripped(currentTime)) {
circuitBreakerTrippedCount++;
} else {
activeConnectionsCountOnAvailableServer += stat.getActiveRequestsCount(currentTime);
}
activeConnectionsCount += stat.getActiveRequestsCount(currentTime);
}
if (circuitBreakerTrippedCount == instanceCount) {
//服务全被熔断了
if (instanceCount > 0) {
// should be NaN, but may not be displayable on Epic
loadPerServer = -1;
}
} else {
//计算每个服务平均负载 活跃的请求线程数/(总服务-熔断服务)
loadPerServer = ((double) activeConnectionsCountOnAvailableServer) / (instanceCount - circuitBreakerTrippedCount);
}
return new ZoneSnapshot(instanceCount, circuitBreakerTrippedCount, activeConnectionsCount, loadPerServer);
}
第二步就是从所有分区里选出可用的分区了
public static Set<String> getAvailableZones(
Map<String, ZoneSnapshot> snapshot, double triggeringLoad,
double triggeringBlackoutPercentage) {
if (snapshot.isEmpty()) {
return null;
}
Set<String> availableZones = new HashSet<String>(snapshot.keySet());
if (availableZones.size() == 1) {
return availableZones;
}
Set<String> worstZones = new HashSet<String>();
double maxLoadPerServer = 0;
boolean limitedZoneAvailability = false;
//循环分区,找到可用分区
for (Map.Entry<String, ZoneSnapshot> zoneEntry : snapshot.entrySet()) {
String zone = zoneEntry.getKey();
ZoneSnapshot zoneSnapshot = zoneEntry.getValue();
int instanceCount = zoneSnapshot.getInstanceCount();
if (instanceCount == 0) {
availableZones.remove(zone);
limitedZoneAvailability = true;
} else {
double loadPerServer = zoneSnapshot.getLoadPerServer();
//熔断的服务比例大于99%
if (((double) zoneSnapshot.getCircuitTrippedCount())
/ instanceCount >= triggeringBlackoutPercentage
|| loadPerServer < 0) {
//服务的平均负载小于0(服务全被熔断了,从可用分区剔除)
availableZones.remove(zone);
limitedZoneAvailability = true;
} else {
// 负载和最大负载相近 1/100000 记录为最差
if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
// they are the same considering double calculation
// round error 记录最差的
worstZones.add(zone);
} else if (loadPerServer > maxLoadPerServer) {
//负载比最大负载还大,重新记录最差
maxLoadPerServer = loadPerServer;
worstZones.clear();
worstZones.add(zone);
}
}
}
}
// 最大负载都小于0.2,说明服务可用性都还好,并且没有剔除任何区
if (maxLoadPerServer < triggeringLoad && !limitedZoneAvailability) {
// zone override is not needed here
return availableZones;
}
//从最差的区里随机取一个
String zoneToAvoid = randomChooseZone(snapshot, worstZones);
//从可用区里移除最差的一个分区
if (zoneToAvoid != null) {
availableZones.remove(zoneToAvoid);
}
return availableZones;
}
可以看到最后,最终是通过随机函数
Random.nextInt()方法从可用分区里获取一个分区。 代码比较冗长,但是逻辑是很清晰的,可以参考下方时序图
可用服务的选择|逻辑较复杂!!
上面分区选好了,下面就是从可用分区里选择可用服务了。
重要说明:说明选择服务的源码逻辑比上方选择分区逻辑复杂多了,不过多看几遍就好了
//获取loadbalancer
BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
//获取服务
server = zoneLoadBalancer.chooseServer(key);
getLoadBalancer
这里运用了享元模式
,每个分区都只会创建一次负载均衡器对象,创建好了就缓存起来,下次请求就使用缓存中的负载均衡对象了。
BaseLoadBalancer getLoadBalancer(String zone) {
zone = zone.toLowerCase();
//首先查缓存
BaseLoadBalancer loadBalancer = balancers.get(zone);
if (loadBalancer == null) {
// We need to create rule object for load balancer for each zone
IRule rule = cloneRule(this.getRule());
loadBalancer = new BaseLoadBalancer(this.getName() + "_" + zone, rule, this.getLoadBalancerStats());
//1个分区一个loadBalancer
BaseLoadBalancer prev = balancers.putIfAbsent(zone, loadBalancer);
if (prev != null) {
loadBalancer = prev;
}
}
return loadBalancer;
}
每个分区创建一个Rule,一个BaseLoadBalancer,服务选择是通过BaseLoadBalancer选择出来的。
继续跟进chooseServer
方法可知,使用Rule对象选择服务
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为ZoneAvoidanceRule
类,它继承了PredicateBasedRule
,而PredicateBasedRule最终使用AbstractServerPredicate
进行服务抉择!
首先拿到所有的服务,这里所有服务是存储在BaseLoadBalancer
public class BaseLoadBalancer {
//所有服务列表
@Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL)
protected volatile List<Server> allServerList = Collections
.synchronizedList(new ArrayList<Server>());
}
继续跟进AbstractServerPredicate我们终于看到了负载均衡的庐山真面目
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())));
}
执行完chooseRoundRobinAfterFiltering方法就拿到了最终选择的服务了,它通过轮训方式获取一个服务。
在getEligibleServers
方法里通过Predicate
服务决策器过滤,过滤出所有可用的服务列表。
在ZoneAvoidanceRule
构造器里面,我们可以看到它默认使用两个决策器,进行过滤服务,一个是ZoneAvoidancePredicate
,一个是AvailabilityPredicate
,使用到组合器CompositePredicate
将两者结合(组合设计模式)。
public ZoneAvoidanceRule() {
super();
ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
}
private CompositePredicate createCompositePredicate(ZoneAvoidancePredicate p1, AvailabilityPredicate p2) {
return CompositePredicate.withPredicates(p1, p2)
.addFallbackPredicate(p2)
.addFallbackPredicate(AbstractServerPredicate.alwaysTrue())
.build();
}
}
com.netflix.loadbalancer.ZoneAvoidancePredicate
负责匹配过滤满足区域可用的服务 com.netflix.loadbalancer.AvailabilityPredicate
负责匹配过滤满足可用性的服务
获取服务实现里面,并没有从最开始获取好的分区里面去找服务,而是再次实时获取全部服务,再通过区域过滤器,服务可用性过滤器,选择可用的服务。
这个原因到底是为什么呢? 可想而知,分区是否可用,服务是否可用这些都是一直在变化的,每次获取最新的服务信息才是更加可靠的!!
总结
好啦,上面就是今天的全部内容,我们从LoadBalancerFeignClient开始,介绍了openFeign集成Ribbon负载均衡能力,而Ribbon使用到LoadBalancer和Rule实现负载均衡,他默认使用按区域负载均衡的策略。 分别使用ZoneAvoidanceRule
和ZoneAwareLoadBalancer
实现负载均衡核心逻辑。
核心流程分两步:
到此OpenFeign集成负载均衡能力核心流程已经分析完了。
后面将继续分析OpenFeign的其他组件能力。