源码:
@LoadBalanced注解用来给RestTemplate做标记,以使用负载均很的客户端(loadBalancerClient)来配置他.
loadBalancerClient类:是个接口,定义了几个核心方法choose、execute、reconstructURI
loadBalancerAutoConfiguration:客户端负载均衡器的自动化配置类。
主要做了三件事:1、创建LoadBalancerInterceptor的bean,用来对客户端发起的请求进行拦截
2、创建了RestTemplateCustomizer的bea,用于给RestTemplate增加LoadBalancerInterceptor拦截器
3、维护一个被@Loadbalanced注解修饰的RestTemplate对象列表,并且初始化
LoadBalancerInterceptor:
/***
* 当被@LoadBalance注解修饰的RestTemplate发起请求会被拦截进入该方法
* @param request
* @param body
* @param execution
* @return
* @throws IOException
*/
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
//具体由LoadBalancerClient的实现类LoadBalancerClient执行
return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
RibbonLoadBalancerClient:
/***
* 请求拦截入口
* @param serviceId 实例名:比如请求总地址为:http://eds2/admin/api/entityOrganization/getOrgTree.json 则该参数为 eds2
* @param request 请求信息
* @param <T>
* @return
* @throws IOException
*/
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
Server server = this.getServer(loadBalancer);//选择具体一个服务器
if(server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
} else {
//将服务器实力信息和请求信息封装在一起的对象
RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
return this.execute(serviceId, ribbonServer, request);//执行远程请求
}
}
getServer:选择具体某台服务器。这个方法涉及到的类图:
上图介绍:AbstractLoadBalancer定义了两个方法以及一个枚举,BaseLoadBalancer负载均衡的基本实现,最后两个是负载均衡实现的扩展。
默认采用ZoneAwareLoadbalancer,而如果没有开启zone或者只有一个默认zone的话,则会调用BaseLoadBalancer的chooseServer
public Server chooseServer(Object key) {
if(ENABLED.get() && this.getLoadBalancerStats().getAvailableZones().size() > 1) {
Server server = null;
//此分支实现的是根据zone选择服务器的逻辑。。。。代码略
} else {
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key);
}
}
核心类解读:
BaseLoadBalancer:是负载均衡器的基础实现类,定义了很多相关基础内容
维护两个存储服务实例的Server对象list。
LoadBalancerStats:用来存储负载均衡器服务实例和统计信息对象。
IPing:检查服务是否正常
IPingStrategy:检查服务实例操作的执行策略对象,在该类的静态内部类有实现SerialPingStrategy,采用线性遍历方式做检查,server列表过大或者Iping慢的时候不理想,可以重新该方法优化。
private static class SerialPingStrategy implements IPingStrategy {
private SerialPingStrategy() {
}
public boolean[] pingServers(IPing ping, Server[] servers) {
int numCandidates = servers.length;
boolean[] results = new boolean[numCandidates];
BaseLoadBalancer.logger.debug("LoadBalancer: PingTask executing [{}] servers configured", Integer.valueOf(numCandidates));
for(int i = 0; i < numCandidates; ++i) {
results[i] = false;
try {
if(ping != null) {
results[i] = ping.isAlive(servers[i]);
}
} catch (Exception var7) {
BaseLoadBalancer.logger.error("Exception while pinging Server: '{}'", servers[i], var7);
}
}
return results;
}
}
IRule:处理规则,chooseServer函数由该属性实现,默认为RondRobinRule的线性负载均衡规则
启动IPing任务:定时任务检查serverlist中server健康,间隔10秒
DynamicServerListLoadBalancer:BaseLoadBalancer的扩展类,实现了实例服务清单在运行期的动态更新能力,同时还具备对服务实例清单的过滤功能。
ServerList属性:为接口,定义了两个抽象方法,获取初始化的服务实例和获取更新的服务实例。通过搜索源码发现该类的实现类如下图:
那么具体用的哪个实现类?
搜索EurekaribbonclientConfiguration类,发现DomainExtractingServerList为实现类。
获取初始化的服务实例和获取更新的服务实例的具体实现方法在类dicoveryEnabledNIWSServerList的obtainServersViaDiscovery方法,该方法直接调取注册中心获取服务实例。
更新服务列表定时任务启动,以构造器为入口追踪会调用到方法enableAndInitLearnNewServersFeature,该方法调用更新器的start方法。搜索源码发现有两个实现类
PollingServerListUpdater:动态服务列表更新,DynamicServerListLoadBalancer默认为此,通过定时任务方法更新,更新服务在实例初始化1秒后开发执行,30秒频率
EurekaNotificationServerListUpdater:也可服务DynamicServerListLoadBalancer,但触发机制不同,需要Eureka事件监听来驱动。
ServerListFilter:对服务实例清单进行过滤的filter。类图:
ZoneAffinityServerListFilter:实现区域感知的过滤器。
public List<T> getFilteredListOfServers(List<T> servers) {
if(this.zone != null && (this.zoneAffinity || this.zoneExclusive) && servers != null && servers.size() > 0) {
//按照zone过滤后的list
List<T> filteredServers = Lists.newArrayList(Iterables.filter(servers, this.zoneAffinityPredicate.getServerOnlyPredicate()));
if(this.shouldEnableZoneAffinity(filteredServers)) {//这个方法判断是否开启过滤器
return filteredServers;
}
if(this.zoneAffinity) {
this.overrideCounter.increment();
}
}
return servers;
}
/***
* 根据一系列的算法求出一些基础指标(包含实例数量、断路器断开数、活动请求数、实例平均负载等)
* 与阈值比较,有一个条件符合,则不开启
* 这一算法实现为集群出现故障时,依然可以跨区域
* @param filtered
* @return
*/
private boolean shouldEnableZoneAffinity(List<T> filtered) {
if(!this.zoneAffinity && !this.zoneExclusive) {
return false;
} else if(this.zoneExclusive) {
return true;
} else {
LoadBalancerStats stats = this.getLoadBalancerStats();
if(stats == null) {
return this.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 / (double)instanceCount < this.blackOutServerPercentageThreshold.get() && loadPerServer < this.activeReqeustsPerServerThreshold.get() && instanceCount - circuitBreakerTrippedCount >= this.availableServersThreshold.get()) {
return true;
} else {
logger.debug("zoneAffinity is overriden. blackOutServerPercentage: {}, activeReqeustsPerServer: {}, availableServers: {}", new Object[]{Double.valueOf((double)circuitBreakerTrippedCount / (double)instanceCount), Double.valueOf(loadPerServer), Integer.valueOf(instanceCount - circuitBreakerTrippedCount)});
return false;
}
}
}
}
ServerListSubsetFilter:
ZonePreferenceServerListFilter:实现了通过配置Eureka的实例元数据的所属区域来过滤出同区域的服务实例
ZoneAwareLoadBalancer:
负载均衡策略:
RandomRule:随机选择
RoundRobinRule:线性轮询
RetryRule:实现了一个具备重试机制的实例选择功能
.
.
.