本文已参与「新人创作礼」活动,一起开启掘金创作之路。
LoadBalancerInterceptor
@LoadBalanced
spring.factories
LoadBalancerAutoConfiguration
LoadBalancerInterceptor & ClientHttpRequestInterceptor
LoadBalancerClient & RibbonLoadBalancedClient
LoadBalancerRequest & LoadBalancerRequestFactory
LocalBalancerClient
spring.factories
RibbonAutoConfiguration
SpringClientFactory
RibbonLoadBalancerClient & LoadBalancerClient & LoadBalancerChooser
LocalBalancer
SpringClientFactory & NamedContextFactory
RibbonClientConfiguration
IConfig & IRule & IPing & ServerList & ServerListUpdater & ServerListFilter
ZoneAwareLoadBalancer & DynamicServerListLoadBalancer & BaseLoadBalancer & ILoadBalancer
ServerList
EurekaRibbonConfiguration
DomainExtractingServerList(DiscoveryEnableNIWSServerList) & ServerList
ServerListUpdater
PollingServerListUpdater
IRule
ZoneAvoidanceRule & PredicateBasedRule & ClientConfigEnableRoundRibbonRule & AbstractLoadBalancerRule & IRule
IPing
RibbonEurekaAutoConfiguration
@RibbonClients
EurekaRibbonClientConfiguration
NIWSDiscoveryPing & AbstractLoadBalancerPing & IPing
LocalBalancerRequest
LocalBalancerAutoConfiguration
LocalBalancerClient & List
LocalBalancerRequestFactory
ServiceRequestWrapper
ClientHttpRequestFactory(uri,method)
ClientHttpRequest
LocalBalancerRequestFactory
使用装饰器模式用ServiceInstance和LoadBalancerClient装饰HttpRequest构造成ServiceRequestWrapper
ServiceRequestWrapper
重载HttpRequest.getRUI()方法,通过ServiceInstance和LoadBalancerClient获取重构之后的真实HTTP请求地址。
ClientHttpRequestFactory
HttpAccesor & InterceptingHttpAccessor
ClientHttpRequestFactory
IPing机制检查服务实例是否存活
原生的ribbon有一个ping机制,IPing的组件,会定时的ping一下服务器,看看服务器是否存活,让自己仅仅请求存活的服务器即可。
Spring Cloud整合Ribbon,IPing组件策略。
Spring Cloud整合Ribbon环境下,使用的是DummyPing组件。
//
@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();
}
Spring Cloud整合Ribbon环境下, 默认的情况下,IPing组件是不生效的。
package com.netflix.loadbalancer;
import com.netflix.client.config.IClientConfig;
public class DummyPing extends AbstractLoadBalancerPing {
public DummyPing() {
}
public boolean isAlive(Server server) {
return true;
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
Ribbon整合Eureka,IPing主键策略
Ribbon整合Eureka环境下,使用的是Eureka项目中定义的NIWSDiscoveryPing(IPing)组件。
@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;
}
NIWSDiscoveryPing(IPing)组件,将server list中的每个server,都检查一下对应的Eureka中的InstanceInfo的状态,看看InstanceInfo,服务实例的status是否是正常的。
在ZoneAwareLoadBalancer实例构造的时候,启动一个定时调度的任务,用IPing组件对server list中的每个server都执行一下isAlive()方法。
public boolean isAlive(Server server) {
boolean isAlive = true;
if (server!=null && server instanceof DiscoveryEnabledServer){
DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server;
InstanceInfo instanceInfo = dServer.getInstanceInfo();
if (instanceInfo!=null){
InstanceStatus status = instanceInfo.getStatus();
if (status!=null){
isAlive = status.equals(InstanceStatus.UP);
}
}
}
return isAlive;
}
BaseLoadBalancer.initWithConfig()方法中,setPingInterval()方法,setupPingTask()方法,启动定时ping server list的定时调度任务
// ZoneAwareLoadBalancer -> DynamicServerListLoadBalancer -> BaseLoadBalancer
public BaseLoadBalancer(IClientConfig config, IRule rule, IPing ping) {
initWithConfig(config, rule, ping, createLoadBalancerStatsFromConfig(config));
}
void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping, LoadBalancerStats stats) {
this.config = clientConfig;
String clientName = clientConfig.getClientName();
this.name = clientName;
int pingIntervalTime = Integer.parseInt(""
+ clientConfig.getProperty(
CommonClientConfigKey.NFLoadBalancerPingInterval,
Integer.parseInt("30")));
int maxTotalPingTime = Integer.parseInt(""
+ clientConfig.getProperty(
CommonClientConfigKey.NFLoadBalancerMaxTotalPingTime,
Integer.parseInt("2")));
setPingInterval(pingIntervalTime);
setMaxTotalPingTime(maxTotalPingTime);
// cross associate with each other
// i.e. Rule,Ping meet your container LB
// LB, these are your Ping and Rule guys ...
setRule(rule);
setPing(ping);
setLoadBalancerStats(stats);
rule.setLoadBalancer(this);
if (ping instanceof AbstractLoadBalancerPing) {
((AbstractLoadBalancerPing) ping).setLoadBalancer(this);
}
logger.info("Client: {} instantiated a LoadBalancer: {}", name, this);
boolean enablePrimeConnections = clientConfig.get(
CommonClientConfigKey.EnablePrimeConnections, DefaultClientConfigImpl.DEFAULT_ENABLE_PRIME_CONNECTIONS);
if (enablePrimeConnections) {
this.setEnablePrimingConnections(true);
PrimeConnections primeConnections = new PrimeConnections(
this.getName(), clientConfig);
this.setPrimeConnections(primeConnections);
}
init();
}
public void setPingInterval(int pingIntervalSeconds) {
if (pingIntervalSeconds < 1) {
return;
}
this.pingIntervalSeconds = pingIntervalSeconds;
if (logger.isDebugEnabled()) {
logger.debug("LoadBalancer [{}]: pingIntervalSeconds set to {}",
name, this.pingIntervalSeconds);
}
setupPingTask(); // since ping data changed
}
void setupPingTask() {
if (canSkipPing()) {
return;
}
if (lbTimer != null) {
lbTimer.cancel();
}
lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
true);
lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
forceQuickPing();
}
class PingTask extends TimerTask {
public void run() {
try {
new Pinger(pingStrategy).runPinger();
} catch (Exception e) {
logger.error("LoadBalancer [{}]: Error pinging", name, e);
}
}
}
public void runPinger() throws Exception {
if (!pingInProgress.compareAndSet(false, true)) {
return; // Ping in progress - nothing to do
}
// we are "in" - we get to Ping
Server[] allServers = null;
boolean[] results = null;
Lock allLock = null;
Lock upLock = null;
try {
/*
* The readLock should be free unless an addServer operation is
* going on...
*/
allLock = allServerLock.readLock();
allLock.lock();
allServers = allServerList.toArray(new Server[allServerList.size()]);
allLock.unlock();
int numCandidates = allServers.length;
results = pingerStrategy.pingServers(ping, allServers);
final List<Server> newUpList = new ArrayList<Server>();
final List<Server> changedServers = new ArrayList<Server>();
for (int i = 0; i < numCandidates; i++) {
boolean isAlive = results[i];
Server svr = allServers[i];
boolean oldIsAlive = svr.isAlive();
svr.setAlive(isAlive);
if (oldIsAlive != isAlive) {
changedServers.add(svr);
logger.debug("LoadBalancer [{}]: Server [{}] status changed to {}",
name, svr.getId(), (isAlive ? "ALIVE" : "DEAD"));
}
if (isAlive) {
newUpList.add(svr);
}
}
upLock = upServerLock.writeLock();
upLock.lock();
upServerList = newUpList;
upLock.unlock();
notifyServerStatusChangeListener(changedServers);
} finally {
pingInProgress.set(false);
}
}
默认每隔30秒执行一下PingTask调度任务,执行一个Pinger()的东西,执行SerialPingStrategy。
命令模式+策略模式
用IPing组件对每个server都执行一下isAlive()方法。
Spring Cloud 整合 Ribbon 整合 Eureka
Eureka项目下的EurekaRibbonClinetConfiguration内实例化IPing组件,Ribbon项目下的RibbonClientConfiguration内IPing组件的实例化由于@ConfigurationOnMissingBean注解的原因不会执行实例化。