前言
配置中心我相信有一年开发经验的程序员都听过吧,有三年开发经验的程序100%都使用过配置中心配置吧。apollo做为常用的配置中心,你知道它的原理
吗?你知道它是怎么集成到springboot
的吗? 本篇文章带你了解其原理,让你也能够自定义组件。
一、apollo是如何完成初始化的,拉取配置的呢
1. apollo的初始化阶段
Springboot环境准备阶段,发布ApplicationEnvironmentPreparedEvent
事件,EnvironmentPostProcessorApplicationListener
监听到事件之后 执行 postProcessEnvironment
方法。apollo 中 的初始化类 ApolloApplicationContextInitializer
就实现EnvironmentPostProcessor接口。 ApolloApplicationContextInitializer
java
public void postProcessEnvironment(ConfigurableEnvironment configurableEnvironment, SpringApplication springApplication) {
// should always initialize system properties like app.id in the first place
initializeSystemProperty(configurableEnvironment);
Boolean eagerLoadEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_EAGER_LOAD_ENABLED, Boolean.class, false);
//EnvironmentPostProcessor should not be triggered if you don't want Apollo Loading before Logging System Initialization
if (!eagerLoadEnabled) {
return;
}
Boolean bootstrapEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class, false);
if (bootstrapEnabled) {
DeferredLogger.enable();
//重点实现逻辑的方法
initialize(configurableEnvironment);
}
}
2.初始化RemoteConfigRepository
经过initialize()方法层层调用最后进入到RemoteConfigRepository init
java
public RemoteConfigRepository(String namespace) {
m_namespace = namespace;
m_configCache = new AtomicReference<>();
m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
m_httpClient = ApolloInjector.getInstance(HttpClient.class);
m_serviceLocator = ApolloInjector.getInstance(ConfigServiceLocator.class);
remoteConfigLongPollService = ApolloInjector.getInstance(RemoteConfigLongPollService.class);
m_longPollServiceDto = new AtomicReference<>();
m_remoteMessages = new AtomicReference<>();
m_loadConfigRateLimiter = RateLimiter.create(m_configUtil.getLoadConfigQPS());
m_configNeedForceRefresh = new AtomicBoolean(true);
m_loadConfigFailSchedulePolicy = new ExponentialSchedulePolicy(m_configUtil.getOnErrorRetryInterval(),
m_configUtil.getOnErrorRetryInterval() * 8);
//下面三个方法就是拉去配置的核心代码了
//同步远程配置
this.trySync();
//固定频率刷新、内部实现也是调用trySync()
this.schedulePeriodicRefresh();
//轮询访问远程配置中心,每次访问直到超时或者apollo 配置发生变化
this.scheduleLongPollingRefresh();
}
3.同步远程配置
AbstractConfigRepository.trySync()
同步远程配置方法,方法内部主要实现逻辑在sync()
中
java
protected synchronized void sync() {
Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig");
try {
//m_configCache
ApolloConfig previous = m_configCache.get();
//加载 远程 apollo config
ApolloConfig current = loadApolloConfig();
//reference equals means HTTP 304
if (previous != current) {
logger.debug("Remote Config refreshed!");
m_configCache.set(current);
//getConfig 方法 将apollo配置放进 properties中
this.fireRepositoryChange(m_namespace, this.getConfig());
}
if (current != null) {
Tracer.logEvent(String.format("Apollo.Client.Configs.%s", current.getNamespaceName()),
current.getReleaseKey());
}
transaction.setStatus(Transaction.SUCCESS);
} catch (Throwable ex) {
transaction.setStatus(ex);
throw ex;
} finally {
transaction.complete();
}
}
private volatile AtomicReference<ApolloConfig> m_configCache; 使用了volatile 修饰,原子引用类型进行封装引用,这就是看源码的意义吧
4.发布ConfigFileChangeEvent
事件
sync() 方法中的 调用fireRepositoryChange()
,最后会调用到AbstractConfigFile.fireConfigChange()
方法
java
private void fireConfigChange(final ConfigFileChangeEvent changeEvent) {
for (final ConfigFileChangeListener listener : m_listeners) {
m_executorService.submit(new Runnable() {
@Override
public void run() {
String listenerName = listener.getClass().getName();
Transaction transaction = Tracer.newTransaction("Apollo.ConfigFileChangeListener", listenerName);
try {
//listner 是一个PropertySourcesProcessor 中的一个lambda 类,
//ConfigChangeListener configChangeEventPublisher = changeEvent ->
//applicationEventPublisher.publishEvent(new ApolloConfigChangeEvent(changeEvent));
listener.onChange(changeEvent);
transaction.setStatus(Transaction.SUCCESS);
} catch (Throwable ex) {
transaction.setStatus(ex);
Tracer.logError(ex);
logger.error("Failed to invoke config file change listener {}", listenerName, ex);
} finally {
transaction.complete();
}
}
});
}
}
因此我们可以监听ApolloConfigChangeEvent
事件来监听apollo配置是否发生产变化 eg:
java
public class ListenerApollo implements ConfigChangeListener, ApplicationListener<ApolloConfigChangeEvent> {
@Override
public void onChange(ConfigChangeEvent configChangeEvent) {
//核心线程配置发生变化、重新set
ConfigChange coreSize = configChangeEvent.getChange("threadPool.corePoolSize");
if( coreSize!= null){
ThreadPoolExecutor executor = SpringUtil.getBean(ThreadPoolExecutor.class);
executor.setCorePoolSize(Integer.valueOf(coreSize.getNewValue()));
}
}
@Override
public void onApplicationEvent(ApolloConfigChangeEvent event) {
this.onChange(event.getConfigChangeEvent());
}
}
总结
分析apollo在Springboot启动的哪个环节被集成初始化的,以及跟了同步远程配置的逻辑,初始化时同步一次,定时同步,长轮询监听配置是否发生变化,发生变化又通知程序进行同步。并且同步后也会发布一个ApolloConfigChangeEvent
事件,放开发者去监听配置变化。