不懂Ribbon原理的可以進來看看哦,分析RibbonClientConfiguration完成了哪些核心初始操作
本文在前一篇文章的基礎上來繼續分析Ribbon的核心內容。
不懂Ribbon原理的可以進來看看哦,分析SpringBoot自動裝配完成了Ribbon哪些核心操作
RibbonClientConfiguration
RibbonClientConfiguration是一個非常中的Ribbon配置類,在第一個發起Ribbon請求的時候會完成對應的初始化操作。會完成多個相關的默認設置。
介面 | 默認實現 | 描述 |
---|---|---|
IClientConfig | DefaultClientConfigImpl | 管理配置介面 |
IRule | ZoneAvoidanceRule | 均衡策略介面 |
IPing | DummyPing | 檢查服務可用性介面 |
ServerList<Server> | ConfigurationBasedServerList | 獲取服務列表介面 |
ILoadBalancer | ZoneAwareLoadBalancer | 負載均衡介面 |
ServerListUpdater | PollingServerListUpdater | 定時更新服務列表介面 |
ServerIntrospector | DefaultServerIntrospector | 安全埠介面 |
@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
config.loadProperties(this.name);
config.set(CommonClientConfigKey.ConnectTimeout, 1000);
config.set(CommonClientConfigKey.ReadTimeout, 1000);
config.set(CommonClientConfigKey.GZipPayload, true);
return config;
}
@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 {
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
}
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
return (IPing)(this.propertiesFactory.isSet(IPing.class, this.name) ? (IPing)this.propertiesFactory.get(IPing.class, config, this.name) : new DummyPing());
}
@Bean
@ConditionalOnMissingBean
public ServerList<Server> ribbonServerList(IClientConfig config) {
if (this.propertiesFactory.isSet(ServerList.class, this.name)) {
return (ServerList)this.propertiesFactory.get(ServerList.class, config, this.name);
} else {
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) {
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));
在眾多的默認實現中比較重要的是【ILoadBalancer】對象的實現。即【ZoneAwareLoadBalancer】的實現。實現的原理圖為:
在【ZoneAwareLoadBalancer】裡面完成了服務地址動態獲取和服務地址更新定時任務的配置。首先會進入【ZoneAwareLoadBalancer】的構造方法中
public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
}
通過源碼能夠發現會調用父類中的構造方法。
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
// 繼續調用父類中的方法
super(clientConfig, rule, ping);
this.serverListImpl = serverList;
this.filter = filter;
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
}
// 完成相關的初始操作 服務地址獲取和更新
restOfInit(clientConfig);
}
在上面的源碼中我們先繼續跟蹤父類中的方法。
void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping, LoadBalancerStats stats) {
this.config = clientConfig;
String clientName = clientConfig.getClientName();
this.name = clientName;
// 設置了定時任務的間隔時間為30秒。
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();
}
在initWithConfig方法中比較中的就是設置了定時任務的間隔時間。然後我們再回到restOfInit方法中。(一起來進階提升吧:463257262)
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方法
public void enableAndInitLearnNewServersFeature() {
LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
serverListUpdater.start(updateAction);
}
start方法的實現有多種,根據我們的服務選擇對應的選擇即可。比如本地就使用PollingServerListUpdater,如果是Eureka註冊中心就選擇EurekaNotificationServerListUpdater.
以本地為例:
@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 {
// doUpdate()的方法體要注意
updateAction.doUpdate();
lastUpdated = System.currentTimeMillis();
} catch (Exception e) {
logger.warn("Failed one update cycle", e);
}
}
};
// 設置定時任務 10秒開始第一次檢查,間隔時間是30秒
scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
wrapperRunnable,
initialDelayMs,
refreshIntervalMs,
TimeUnit.MILLISECONDS
);
} else {
logger.info("Already active, no-op");
}
}
此處要注意定時任務的具體內容,以本地為例。
所以定時任務執行的方法也就是【updateListOfServers】方法,也就是:
emsp; 所以我們繼續來看看【updateListOfServers】方法中的邏輯
@VisibleForTesting
public void updateListOfServers() {
List<T> servers = new ArrayList<T>();
if (serverListImpl != null) {
// 從本地或者Eureka或者Nacos等各個配置中心中獲取對應的服務地址資訊
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);
}
上面程式碼中重要的方法是【getUpdatedListOfServers】和【updateAllServerList】,先來看【getUpdatedListOfServers】方法
查看本地的邏輯,Eureka的自行查看
@Override
public List<Server> getUpdatedListOfServers() {
// 從本地配置中獲取
String listOfServers = clientConfig.get(CommonClientConfigKey.ListOfServers);
return derive(listOfServers);
}
然後就是【updateAllServerList】方法
protected void updateAllServerList(List<T> ls) {
// other threads might be doing this - in which case, we pass
// 通過CAS保證操作的原子性
if (serverListUpdateInProgress.compareAndSet(false, true)) {
try {
for (T s : ls) {
s.setAlive(true); // set so that clients can start using these
// servers right away instead
// of having to wait out the ping cycle.
}
// 更新服務地址資訊
setServersList(ls);
// 強制ping服務地址
super.forceQuickPing();
} finally {
serverListUpdateInProgress.set(false);
}
}
}
以上的操作流程圖為:
好了~【RibbonClientConfiguration】這個配置類的內容就給大家介紹到這裡,歡迎大家一鍵三連!!!