普通情況下,使用 實現apollo的監聽器ConfigChangeListener,通過onChange方法來實時獲取配置。
但是,如果對配置變化的時效性要求不高,只是想在用到配置的時候,使用新配置的話,可以不實現監聽器,直接從environment裏面獲取配置。
apollo機制
圖解
代碼分析
還是通過老朋友refresh方法來層層揭曉吧。
首先,通過AbstractApplicationContext.refresh()中:
invokeBeanFactoryPostProcessors(beanFactory);
經歷重重磨難,找到了PropertySourcesProcessor,並執行其postProcessBeanFactory方法,進入initializePropertySources()。
主要流程
1.創建Config
2.用CompositePropertySource包裝Config,並塞入environment
private void initializePropertySources() {
if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME)) {
// 已經初始化過apollo配置就直接返回
return;
}
// 創建一個PropertySource,用於裝入所有的配置
CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME);
......
while (iterator.hasNext()) {
int order = iterator.next();
for (String namespace : NAMESPACE_NAMES.get(order)) {
// 獲取一個config,如果沒有,則會創建一個config(後續分析,很重要哦)
Config config = ConfigService.getConfig(namespace);
// 用CompositePropertySource包裝config爲一個PropertySource,後續加入值environment
composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
}
}
// clean up
NAMESPACE_NAMES.clear();
// 如果存在ApolloBootstrapPropertySources,則要保證ApolloBootstrapPropertySources在第一個,並且將上述的composite設置在其後,否則就是將composite設置在第一個
if (environment.getPropertySources()
.contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
ensureBootstrapPropertyPrecedence(environment);
environment.getPropertySources()
.addAfter(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME, composite);
} else {
// 將composite設置在第一個後,即便我們不適用listener方式,被通知配置的變更,也能直接從environment裏面通過getProperty的方式直接獲取配置
environment.getPropertySources().addFirst(composite);
}
}
從上,我們可以知道,最終的配置其實是放入environment裏面了。故而,我們可以通過environment直接獲取配置,只是不如通過監聽器的onChange方法獲取配置實時。
那麼,瞭解完兩部曲,就來細細品品其中的機制吧。一個config,爲啥就能讓你實時知道apollo的變更呢?
創建RemoteConfigRepository
之前說的 ConfigService.getConfig(namespace) ,獲取不到config,就會創建一個config的呀。此時創建的就是RemoteConfigRepository。看其構造方法,除了各種屬性賦值後,還會調用三個方法,它們很關鍵哈。
public RemoteConfigRepository(String namespace) {
m_namespace = namespace;
m_configCache = new AtomicReference<>();
m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
m_httpUtil = ApolloInjector.getInstance(HttpUtil.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);
gson = new Gson();
// 首次同步apollo
this.trySync();
// 定時刷新配置(大部分情況返回304,定時刷新在於可以防止長輪詢失敗)
this.schedulePeriodicRefresh();
// 長輪詢刷新配置(最主要的實時獲取配置的途徑)
this.scheduleLongPollingRefresh();
}
首次同步apollo
@Override
protected synchronized void sync() {
Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig");
try {
ApolloConfig previous = m_configCache.get();
// 根據你設定的apollo地址,appId,maxRetries等信息,發送get請求,獲取當前apollo配置信息
ApolloConfig current = loadApolloConfig();
// 更新本地的apollo配置信息
if (previous != current) {
logger.debug("Remote Config refreshed!");
m_configCache.set(current);
// 刷新配置的任務可能會調用該方法,獲取配置,並且通知客戶端的監聽器
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 void schedulePeriodicRefresh() {
logger.debug("Schedule periodic refresh with interval: {} {}",
m_configUtil.getRefreshInterval(), m_configUtil.getRefreshIntervalTimeUnit());
m_executorService.scheduleAtFixedRate(
new Runnable() {
@Override
public void run() {
Tracer.logEvent("Apollo.ConfigService", String.format("periodicRefresh: %s", m_namespace));
logger.debug("refresh config for namespace: {}", m_namespace);
// 和上述首次同步的trySync是同一個方法
trySync();
Tracer.logEvent("Apollo.Client.Version", Apollo.VERSION);
}
//時間間隔默認是5分鐘
}, m_configUtil.getRefreshInterval(), m_configUtil.getRefreshInterval(),
m_configUtil.getRefreshIntervalTimeUnit());
}
開啓長輪詢刷新配置
public boolean submit(String namespace, RemoteConfigRepository remoteConfigRepository) {
boolean added = m_longPollNamespaces.put(namespace, remoteConfigRepository);
m_notifications.putIfAbsent(namespace, INIT_NOTIFICATION_ID);
if (!m_longPollStarted.get()) {
// 開啓拉取
startLongPolling();
}
return added;
}
private void doLongPollingRefresh(String appId, String cluster, String dataCenter) {
final Random random = new Random();
ServiceDTO lastServiceDto = null;
while (!m_longPollingStopped.get() && !Thread.currentThread().isInterrupted()) {
if (!m_longPollRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) {
// 等待5秒
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
}
}
// 獲取各種信息,發起請求,看apollo中配置有無變更
......
// 配置發生變化,則主動獲取新配置,且調用ConfigChangeListener的實現類的onChange方法
if (response.getStatusCode() == 200 && response.getBody() != null) {
updateNotifications(response.getBody());
updateRemoteNotifications(response.getBody());
transaction.addData("Result", response.getBody().toString());
notify(lastServiceDto, response.getBody());
}
......
} finally {
transaction.complete();
}
}
}
notify方法會最終進入fireRepositoryChange
fireRepositoryChange
該方法是之前介紹的sync()方法裏面提到的,兩類刷新配置的任務都會執行此方法,來通知各個監聽器,有哪些配置變更。
1.獲取新配置
2.統計各種配置的變更
3.通知各個監聽器
protected void fireRepositoryChange(String namespace, Properties newProperties) {
// 遍歷所有的監聽器
for (RepositoryChangeListener listener : m_listeners) {
try {
// 把最新的配置信息傳遞給監聽器
listener.onRepositoryChange(namespace, newProperties);
} catch (Throwable ex) {
Tracer.logError(ex);
logger.error("Failed to invoke repository change listener {}", listener.getClass(), ex);
}
}
}
@Override
public synchronized void onRepositoryChange(String namespace, Properties newProperties) {
if (newProperties.equals(m_configProperties.get())) {
return;
}
ConfigSourceType sourceType = m_configRepository.getSourceType();
Properties newConfigProperties = new Properties();
newConfigProperties.putAll(newProperties);
// 整理配置信息,確定配置的變更類型
Map<String, ConfigChange> actualChanges = updateAndCalcConfigChanges(newConfigProperties, sourceType);
//check double checked result
if (actualChanges.isEmpty()) {
return;
}
// 調用各個ConfigChangeListener實現類的onChange方法,發送配置信息
this.fireConfigChange(new ConfigChangeEvent(m_namespace, actualChanges));
Tracer.logEvent("Apollo.Client.ConfigChanges", m_namespace);
}
private Map<String, ConfigChange> updateAndCalcConfigChanges(Properties newConfigProperties,
ConfigSourceType sourceType) {
// 初次統計變化的配置
List<ConfigChange> configChanges =
calcPropertyChanges(m_namespace, m_configProperties.get(), newConfigProperties);
ImmutableMap.Builder<String, ConfigChange> actualChanges =
new ImmutableMap.Builder<>();
/** === Double check since DefaultConfig has multiple config sources ==== **/
//1. 爲配置設置舊值
for (ConfigChange change : configChanges) {
change.setOldValue(this.getProperty(change.getPropertyName(), change.getOldValue()));
}
//2. 更新 m_configProperties
updateConfig(newConfigProperties, sourceType);
clearConfigCache();
//3. 遍歷所有的新配置,最後確認各個配置的type(ADDED/MODIFIED/DELETED)
for (ConfigChange change : configChanges) {
change.setNewValue(this.getProperty(change.getPropertyName(), change.getNewValue()));
switch (change.getChangeType()) {
case ADDED:
if (Objects.equals(change.getOldValue(), change.getNewValue())) {
break;
}
if (change.getOldValue() != null) {
change.setChangeType(PropertyChangeType.MODIFIED);
}
actualChanges.put(change.getPropertyName(), change);
break;
case MODIFIED:
if (!Objects.equals(change.getOldValue(), change.getNewValue())) {
actualChanges.put(change.getPropertyName(), change);
}
break;
case DELETED:
if (Objects.equals(change.getOldValue(), change.getNewValue())) {
break;
}
if (change.getNewValue() != null) {
change.setChangeType(PropertyChangeType.MODIFIED);
}
actualChanges.put(change.getPropertyName(), change);
break;
default:
//do nothing
break;
}
}
return actualChanges.build();
}