上一篇文章(https://blog.csdn.net/crystonesc/article/details/106630412)我們從Apollo社區給出的DEMO開始逐步分析了Apollo客戶端配置的創建過程,作爲Apollo配置中心Client源碼學習的第二篇文章,我們繼續學習,來看看在通過ConfigFactory創建Config後,Config如何來獲取配置信息的。
我們知道Apollo的DefaultConfigFactory會調用create方法來創建默認的DefaultConfig,在DefaultConfig的構造函數中,會傳入namespace(命名空間)和ConfigRepository(配置源),配置源會指定了我們客戶端獲取配置的位置,今天我們就來說說ConfigRepository。下面是DefaultConfigFactory中create方法的內容,可以看到對於非具體格式的配置,Apollo都會使用createLocalConfigRepository返回的LocalConfigRepository作爲配置源。
@Override
//(3) 創建Config
public Config create(String namespace) {
//判斷namespace的文件類型
ConfigFileFormat format = determineFileFormat(namespace);
//如果是property兼容的文件,比如yaml,yml
if (ConfigFileFormat.isPropertiesCompatible(format)) {
//創建了默認的Config,並通過參數制定了namespace和Repository(倉庫)
return new DefaultConfig(namespace, createPropertiesCompatibleFileConfigRepository(namespace, format));
}
//否則創建LocalConfigRepository
return new DefaultConfig(namespace, createLocalConfigRepository(namespace));
}
一、ConfigRepository結構
我們首先來看下ConfigRepository,ConfigRepository是一個接口,其實現包含AbstractConfigRepository以及三個具體實現類:PropertiesCompatibleFileConfigRepository,LocalFileConfigRepository,RemoteConfigRepository。
首先研究下ConfigRepository接口類,接口類包含幾個方法,如getConfig,用於獲取一個Properties類型的配置;setUpstreamRepository用於設置一個備用配置源以及addChangeListener和removeChangeListener,用於添加監聽和移除監聽隊列,最後getSourceType用於獲取配置的來源。接下來我們看下抽象實現類AbstractConfigRepository。
/**
* (1)倉庫接口類,主要表示一個配置源,Apollo的Server是一個數據源,本地緩存也算是一個數據源
* @author Jason Song([email protected])
*/
public interface ConfigRepository {
/**
* (2)獲取配置,返回Properties類型
* Get the config from this repository.
* @return config
*/
public Properties getConfig();
/**
*
* (3)設置一個備用的倉庫
* Set the fallback repo for this repository.
* @param upstreamConfigRepository the upstream repo
*/
public void setUpstreamRepository(ConfigRepository upstreamConfigRepository);
/**
* (4)添加倉庫變更監聽
* Add change listener.
* @param listener the listener to observe the changes
*/
public void addChangeListener(RepositoryChangeListener listener);
/**
* (5)移除倉庫變更監聽
* Remove change listener.
* @param listener the listener to remove
*/
public void removeChangeListener(RepositoryChangeListener listener);
/**
* (6)返回配置是從哪個源獲取的
* Return the config's source type, i.e. where is the config loaded from
*
* @return the config's source type
*/
public ConfigSourceType getSourceType();
首先從總體上我們看到AbstractConfigRepository實現了一些ConfigRepository的方法,包括監聽隊列的添加和刪除,監聽隊列事件調用,這些部分是所有配置源類的共有方法。同時AbstractConfgRepository還包括trySync和Sync方法,trySync實際調用的是sync抽象方法,用於從配置源獲取數據,看到這裏了我們可以繼續查看其實現類的代碼,我們先看下LocalFileConfigRepository的代碼。
/**
* @author Jason Song([email protected])
*/
//(1)配置倉庫抽象類
public abstract class AbstractConfigRepository implements ConfigRepository {
private static final Logger logger = LoggerFactory.getLogger(AbstractConfigRepository.class);
//(2)配置倉庫變更監聽隊列
private List<RepositoryChangeListener> m_listeners = Lists.newCopyOnWriteArrayList();
//(3) 實際調用抽象的sync方法
protected boolean trySync() {
try {
sync();
return true;
} catch (Throwable ex) {
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
logger
.warn("Sync config failed, will retry. Repository {}, reason: {}", this.getClass(), ExceptionUtil
.getDetailMessage(ex));
}
return false;
}
//(4) 需要每個實現類實現的同步方法
protected abstract void sync();
@Override
//(5) 向監聽隊列添加監聽器
public void addChangeListener(RepositoryChangeListener listener) {
if (!m_listeners.contains(listener)) {
m_listeners.add(listener);
}
}
@Override
//(6) 從隊列移除監聽器
public void removeChangeListener(RepositoryChangeListener listener) {
m_listeners.remove(listener);
}
//(7) 觸發監聽器修改事件,遍歷監聽器隊列中的監聽者,調用器處理方法
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);
}
}
}
}
二、LocalFileConfigRepository源碼分析
LocalFileConfigRepository是一個用於表示本地文件配置源的類,我們知道Apollo從服務端同步的數據都會保留在本地緩存目錄,以防止在不能夠連接到遠程配置源的情況下使用本地配置源,那麼所以對於默認的配置,實際上是Config是一個擁有帶遠程配置源(RemoteConfigRepository)的LocalFileConfigRepository,這一點我們可以從createLocalConfigRepository方法中得到印證。如下圖所示:
//(5) 創建LocalFileConfigRepository,如果Apollo是以本地模式運行,則創建沒有upstream的LocalFileConfigRepository,否則
//創建一個帶有遠程倉庫RemoteConfigRepository的創建LocalFileConfigRepository
LocalFileConfigRepository createLocalConfigRepository(String namespace) {
if (m_configUtil.isInLocalMode()) {
logger.warn(
"==== Apollo is in local mode! Won't pull configs from remote server for namespace {} ! ====",
namespace);
return new LocalFileConfigRepository(namespace);
}
return new LocalFileConfigRepository(namespace, createRemoteConfigRepository(namespace));
}
接下來我們仔細閱讀LocalFileConfigRepository的源碼,在LocalFileConfigRepository構造的時候就會進行一次trySync,用於獲取配置數據,並且還會指定一個Upstream配置源(遠程配置),從上面的分析我們可以知道,trySync會調用sync方法,而這裏的sync方法會在LocalFileConfigRepository中被實現,我們繼續研究sync方法(7),在sync方法中它先調用trySyncFromUpStream方法嘗試從Upstream倉庫獲取配置,如果能夠獲取到配置,則直接返回,如果未能同步成功,則會調用loadFromLoaclCacheFile從本地加載配置。(具體分析建代碼)
public class LocalFileConfigRepository extends AbstractConfigRepository
implements RepositoryChangeListener {
private static final Logger logger = LoggerFactory.getLogger(LocalFileConfigRepository.class);
private static final String CONFIG_DIR = "/config-cache";
private final String m_namespace;
private File m_baseDir;
private final ConfigUtil m_configUtil;
private volatile Properties m_fileProperties;
private volatile ConfigRepository m_upstream;
private volatile ConfigSourceType m_sourceType = ConfigSourceType.LOCAL;
/**
* Constructor.
*
* @param namespace the namespace
*/
//(1) 本地配置倉庫構造函數(不指定upstream),實際調用LocalFileConfigRepository,
public LocalFileConfigRepository(String namespace) {
this(namespace, null);
}
//(2) 帶upstream的構造函數,構造的同時就會調用trySync來同步配置
public LocalFileConfigRepository(String namespace, ConfigRepository upstream) {
m_namespace = namespace;
m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
this.setLocalCacheDir(findLocalCacheDir(), false);
this.setUpstreamRepository(upstream);
this.trySync();
}
//(3) 設置本地配置緩存目錄,如果syncImmediately爲true, 則會立即同步配置
void setLocalCacheDir(File baseDir, boolean syncImmediately) {
m_baseDir = baseDir;
this.checkLocalConfigCacheDir(m_baseDir);
if (syncImmediately) {
this.trySync();
}
}
//(4) 定位本地緩存目錄,若不存在則創建目錄,若存在文件則返回File類型對象
private File findLocalCacheDir() {
try {
String defaultCacheDir = m_configUtil.getDefaultLocalCacheDir();
Path path = Paths.get(defaultCacheDir);
if (!Files.exists(path)) {
Files.createDirectories(path);
}
if (Files.exists(path) && Files.isWritable(path)) {
return new File(defaultCacheDir, CONFIG_DIR);
}
} catch (Throwable ex) {
//ignore
}
return new File(ClassLoaderUtil.getClassPath(), CONFIG_DIR);
}
@Override
//(5) 獲取配置,如果不存在就進行同步
public Properties getConfig() {
if (m_fileProperties == null) {
sync();
}
Properties result = new Properties();
result.putAll(m_fileProperties);
return result;
}
@Override
//(6) 設置fallback 配置源
public void setUpstreamRepository(ConfigRepository upstreamConfigRepository) {
if (upstreamConfigRepository == null) {
return;
}
//clear previous listener
if (m_upstream != null) {
m_upstream.removeChangeListener(this);
}
m_upstream = upstreamConfigRepository;
trySyncFromUpstream();
upstreamConfigRepository.addChangeListener(this);
}
@Override
public ConfigSourceType getSourceType() {
return m_sourceType;
}
@Override
public void onRepositoryChange(String namespace, Properties newProperties) {
if (newProperties.equals(m_fileProperties)) {
return;
}
Properties newFileProperties = new Properties();
newFileProperties.putAll(newProperties);
updateFileProperties(newFileProperties, m_upstream.getSourceType());
this.fireRepositoryChange(namespace, newProperties);
}
@Override
//(7) 同步配置
protected void sync() {
//sync with upstream immediately
//首先從Upstream獲取配置
boolean syncFromUpstreamResultSuccess = trySyncFromUpstream();
//如果從upstream源同步成功則直接返回
if (syncFromUpstreamResultSuccess) {
return;
}
//如果未能從upstream同步成功,則通過本地緩存文件加載
Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncLocalConfig");
Throwable exception = null;
try {
transaction.addData("Basedir", m_baseDir.getAbsolutePath());
m_fileProperties = this.loadFromLocalCacheFile(m_baseDir, m_namespace);
m_sourceType = ConfigSourceType.LOCAL;
transaction.setStatus(Transaction.SUCCESS);
} catch (Throwable ex) {
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
transaction.setStatus(ex);
exception = ex;
//ignore
} finally {
transaction.complete();
}
if (m_fileProperties == null) {
m_sourceType = ConfigSourceType.NONE;
throw new ApolloConfigException(
"Load config from local config failed!", exception);
}
}
//(8) 從Upstream中獲取配置
private boolean trySyncFromUpstream() {
if (m_upstream == null) {
return false;
}
try {
updateFileProperties(m_upstream.getConfig(), m_upstream.getSourceType());
return true;
} catch (Throwable ex) {
Tracer.logError(ex);
logger
.warn("Sync config from upstream repository {} failed, reason: {}", m_upstream.getClass(),
ExceptionUtil.getDetailMessage(ex));
}
return false;
}
//(9) 把新配置更新到本地緩存中
private synchronized void updateFileProperties(Properties newProperties, ConfigSourceType sourceType) {
this.m_sourceType = sourceType;
if (newProperties.equals(m_fileProperties)) {
return;
}
this.m_fileProperties = newProperties;
persistLocalCacheFile(m_baseDir, m_namespace);
}
//(10) 從本地緩存加載配置文件
private Properties loadFromLocalCacheFile(File baseDir, String namespace) throws IOException {
Preconditions.checkNotNull(baseDir, "Basedir cannot be null");
File file = assembleLocalCacheFile(baseDir, namespace);
Properties properties = null;
if (file.isFile() && file.canRead()) {
InputStream in = null;
try {
in = new FileInputStream(file);
properties = new Properties();
properties.load(in);
logger.debug("Loading local config file {} successfully!", file.getAbsolutePath());
} catch (IOException ex) {
Tracer.logError(ex);
throw new ApolloConfigException(String
.format("Loading config from local cache file %s failed", file.getAbsolutePath()), ex);
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException ex) {
// ignore
}
}
} else {
throw new ApolloConfigException(
String.format("Cannot read from local cache file %s", file.getAbsolutePath()));
}
return properties;
}
//(11) 持久化到本地文件實際操作
void persistLocalCacheFile(File baseDir, String namespace) {
if (baseDir == null) {
return;
}
File file = assembleLocalCacheFile(baseDir, namespace);
OutputStream out = null;
Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "persistLocalConfigFile");
transaction.addData("LocalConfigFile", file.getAbsolutePath());
try {
out = new FileOutputStream(file);
m_fileProperties.store(out, "Persisted by DefaultConfig");
transaction.setStatus(Transaction.SUCCESS);
} catch (IOException ex) {
ApolloConfigException exception =
new ApolloConfigException(
String.format("Persist local cache file %s failed", file.getAbsolutePath()), ex);
Tracer.logError(exception);
transaction.setStatus(exception);
logger.warn("Persist local cache file {} failed, reason: {}.", file.getAbsolutePath(),
ExceptionUtil.getDetailMessage(ex));
} finally {
if (out != null) {
try {
out.close();
} catch (IOException ex) {
//ignore
}
}
transaction.complete();
}
}
//(12) 檢查緩存目錄是否存在,若不存在則建立目錄
private void checkLocalConfigCacheDir(File baseDir) {
if (baseDir.exists()) {
return;
}
Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "createLocalConfigDir");
transaction.addData("BaseDir", baseDir.getAbsolutePath());
try {
Files.createDirectory(baseDir.toPath());
transaction.setStatus(Transaction.SUCCESS);
} catch (IOException ex) {
ApolloConfigException exception =
new ApolloConfigException(
String.format("Create local config directory %s failed", baseDir.getAbsolutePath()),
ex);
Tracer.logError(exception);
transaction.setStatus(exception);
logger.warn(
"Unable to create local config cache directory {}, reason: {}. Will not able to cache config file.",
baseDir.getAbsolutePath(), ExceptionUtil.getDetailMessage(ex));
} finally {
transaction.complete();
}
}
File assembleLocalCacheFile(File baseDir, String namespace) {
String fileName =
String.format("%s.properties", Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR)
.join(m_configUtil.getAppId(), m_configUtil.getCluster(), namespace));
return new File(baseDir, fileName);
}
}
三、RemoteConfigRespository源碼分析
接下來我們來分析RemoteConfigRespository,從上面我們可以看到RemoteConfigRespository代表的是遠程配置源,在其構造函數中(1),就會調用trySync方法來進行同步,同樣trySync方法也會調用sync方法來實際觸發同步(5),在sync方法中我們可以看到,先從緩存中獲取(ApolloConfig previous = m_configCache.get())之前獲取的配置,然後通過loadApolloConfig方法從遠程配置源獲取配置,接下來通過判斷previous和current來判斷是否相同,這裏它直接使用(previous != current)來做判斷,實際上是對previous和current進行對象比較,看到這裏可能大家比較疑惑,從loadApolloConfig的配置爲什麼可以直接與緩存中後去的previous進行比較,原來在loadApolloConfig方法中,從遠程配置源獲取配置的時候,如果配置源返回HTTP CODE 304的時候,loadApolloConfig就會將緩存中的配置直接返回給current,從而sync中可以通過判斷對象是否相等的方法來決定是否更新緩存配置。如果previous!=current,則會將當前的配置設置到緩存當中,並且觸發監聽者更新回調。
接着我們就來研究下loadApolloConfig方法(7),這個方法的內容比較多,首先爲了防止客戶端頻繁獲取遠程配置造成遠程配置源壓力,這裏Apollo使用了一個限速器,保證在每次更新操作間隔5秒,接下來通過調用getConfigServices()方法來獲取configservice地址,在getConfigServices 中會使用ConfigServiceLoactor來獲取configservice的地址(具體怎麼獲取,我們之後來分析),拿到configServices地址後,隨機從configServices地址中獲取一個地址並獲取配置,如果獲取失敗,Apollo還會又重試機制,最終獲取到的結果會進行返回。除了在構造的時候去拉去配置,Apollo還會定期通過單獨的線程去獲取配置,代碼見(4)schedulePeriodicRefresh。
/**
* @author Jason Song([email protected])
*/
public class RemoteConfigRepository extends AbstractConfigRepository {
private static final Logger logger = LoggerFactory.getLogger(RemoteConfigRepository.class);
private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR);
private static final Joiner.MapJoiner MAP_JOINER = Joiner.on("&").withKeyValueSeparator("=");
private static final Escaper pathEscaper = UrlEscapers.urlPathSegmentEscaper();
private static final Escaper queryParamEscaper = UrlEscapers.urlFormParameterEscaper();
private final ConfigServiceLocator m_serviceLocator;
private final HttpUtil m_httpUtil;
private final ConfigUtil m_configUtil;
private final RemoteConfigLongPollService remoteConfigLongPollService;
private volatile AtomicReference<ApolloConfig> m_configCache;
private final String m_namespace;
private final static ScheduledExecutorService m_executorService;
private final AtomicReference<ServiceDTO> m_longPollServiceDto;
private final AtomicReference<ApolloNotificationMessages> m_remoteMessages;
private final RateLimiter m_loadConfigRateLimiter;
private final AtomicBoolean m_configNeedForceRefresh;
private final SchedulePolicy m_loadConfigFailSchedulePolicy;
private final Gson gson;
static {
m_executorService = Executors.newScheduledThreadPool(1,
ApolloThreadFactory.create("RemoteConfigRepository", true));
}
/**
* Constructor.
*
* @param namespace the namespace
*/
//(1) 遠程配置源構造函數
public RemoteConfigRepository(String namespace) {
m_namespace = namespace;
//配置緩存
m_configCache = new AtomicReference<>();
//通過AoplloInjector注入configUtil工具類、httpUitl、serviceLocator、remoteConfigLongPollService
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();
this.trySync();
this.schedulePeriodicRefresh();
this.scheduleLongPollingRefresh();
}
@Override
//(2) 獲取配置方法實現
public Properties getConfig() {
if (m_configCache.get() == null) {
this.sync();
}
//返回的時候轉換爲Properties類型的格式
return transformApolloConfigToProperties(m_configCache.get());
}
@Override
//(3) 設置備用Upstream配置源,因爲本生是遠程倉庫,所以這裏並未實現
public void setUpstreamRepository(ConfigRepository upstreamConfigRepository) {
//remote config doesn't need upstream
}
@Override
public ConfigSourceType getSourceType() {
return ConfigSourceType.REMOTE;
}
//(4) 設置定期獲取配置得線程,定期從遠程配置源獲取配置
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();
Tracer.logEvent("Apollo.Client.Version", Apollo.VERSION);
}
}, m_configUtil.getRefreshInterval(), m_configUtil.getRefreshInterval(),
m_configUtil.getRefreshIntervalTimeUnit());
}
@Override
//(5) 從遠程配置源獲取配置,實際調用方法loadApolloConfig,如果遠程配置源更新後,觸發監聽更新
protected synchronized void sync() {
Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig");
try {
ApolloConfig previous = m_configCache.get();
ApolloConfig current = loadApolloConfig();
//reference equals means HTTP 304
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();
}
}
//(6) 將Apollo配置轉換爲Properties類型,並且返回,apolloConfig中的configurations是一個MAP類型
private Properties transformApolloConfigToProperties(ApolloConfig apolloConfig) {
Properties result = new Properties();
result.putAll(apolloConfig.getConfigurations());
return result;
}
//(7) 實際獲取配置方法
private ApolloConfig loadApolloConfig() {
//獲取限速器的允許,得到允許後才能進行配置獲取
if (!m_loadConfigRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) {
//wait at most 5 seconds
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
}
}
String appId = m_configUtil.getAppId();
String cluster = m_configUtil.getCluster();
String dataCenter = m_configUtil.getDataCenter();
Tracer.logEvent("Apollo.Client.ConfigMeta", STRING_JOINER.join(appId, cluster, m_namespace));
//配置獲取重試次數
int maxRetries = m_configNeedForceRefresh.get() ? 2 : 1;
long onErrorSleepTime = 0; // 0 means no sleep
Throwable exception = null;
List<ServiceDTO> configServices = getConfigServices();
String url = null;
for (int i = 0; i < maxRetries; i++) {
//隨機的獲取一個configService
List<ServiceDTO> randomConfigServices = Lists.newLinkedList(configServices);
Collections.shuffle(randomConfigServices);
//Access the server which notifies the client first
if (m_longPollServiceDto.get() != null) {
randomConfigServices.add(0, m_longPollServiceDto.getAndSet(null));
}
for (ServiceDTO configService : randomConfigServices) {
if (onErrorSleepTime > 0) {
logger.warn(
"Load config failed, will retry in {} {}. appId: {}, cluster: {}, namespaces: {}",
onErrorSleepTime, m_configUtil.getOnErrorRetryIntervalTimeUnit(), appId, cluster, m_namespace);
try {
m_configUtil.getOnErrorRetryIntervalTimeUnit().sleep(onErrorSleepTime);
} catch (InterruptedException e) {
//ignore
}
}
url = assembleQueryConfigUrl(configService.getHomepageUrl(), appId, cluster, m_namespace,
dataCenter, m_remoteMessages.get(), m_configCache.get());
logger.debug("Loading config from {}", url);
HttpRequest request = new HttpRequest(url);
Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "queryConfig");
transaction.addData("Url", url);
try {
//實際獲取配置的HTTP調用
HttpResponse<ApolloConfig> response = m_httpUtil.doGet(request, ApolloConfig.class);
m_configNeedForceRefresh.set(false);
m_loadConfigFailSchedulePolicy.success();
transaction.addData("StatusCode", response.getStatusCode());
transaction.setStatus(Transaction.SUCCESS);
if (response.getStatusCode() == 304) {
logger.debug("Config server responds with 304 HTTP status code.");
return m_configCache.get();
}
ApolloConfig result = response.getBody();
logger.debug("Loaded config for {}: {}", m_namespace, result);
return result;
} catch (ApolloConfigStatusCodeException ex) {
ApolloConfigStatusCodeException statusCodeException = ex;
//config not found
if (ex.getStatusCode() == 404) {
String message = String.format(
"Could not find config for namespace - appId: %s, cluster: %s, namespace: %s, " +
"please check whether the configs are released in Apollo!",
appId, cluster, m_namespace);
statusCodeException = new ApolloConfigStatusCodeException(ex.getStatusCode(),
message);
}
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(statusCodeException));
transaction.setStatus(statusCodeException);
exception = statusCodeException;
} catch (Throwable ex) {
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
transaction.setStatus(ex);
exception = ex;
} finally {
transaction.complete();
}
// if force refresh, do normal sleep, if normal config load, do exponential sleep
onErrorSleepTime = m_configNeedForceRefresh.get() ? m_configUtil.getOnErrorRetryInterval() :
m_loadConfigFailSchedulePolicy.fail();
}
}
String message = String.format(
"Load Apollo Config failed - appId: %s, cluster: %s, namespace: %s, url: %s",
appId, cluster, m_namespace, url);
throw new ApolloConfigException(message, exception);
}
String assembleQueryConfigUrl(String uri, String appId, String cluster, String namespace,
String dataCenter, ApolloNotificationMessages remoteMessages, ApolloConfig previousConfig) {
String path = "configs/%s/%s/%s";
List<String> pathParams =
Lists.newArrayList(pathEscaper.escape(appId), pathEscaper.escape(cluster),
pathEscaper.escape(namespace));
Map<String, String> queryParams = Maps.newHashMap();
if (previousConfig != null) {
queryParams.put("releaseKey", queryParamEscaper.escape(previousConfig.getReleaseKey()));
}
if (!Strings.isNullOrEmpty(dataCenter)) {
queryParams.put("dataCenter", queryParamEscaper.escape(dataCenter));
}
String localIp = m_configUtil.getLocalIp();
if (!Strings.isNullOrEmpty(localIp)) {
queryParams.put("ip", queryParamEscaper.escape(localIp));
}
if (remoteMessages != null) {
queryParams.put("messages", queryParamEscaper.escape(gson.toJson(remoteMessages)));
}
String pathExpanded = String.format(path, pathParams.toArray());
if (!queryParams.isEmpty()) {
pathExpanded += "?" + MAP_JOINER.join(queryParams);
}
if (!uri.endsWith("/")) {
uri += "/";
}
return uri + pathExpanded;
}
private void scheduleLongPollingRefresh() {
remoteConfigLongPollService.submit(m_namespace, this);
}
public void onLongPollNotified(ServiceDTO longPollNotifiedServiceDto, ApolloNotificationMessages remoteMessages) {
m_longPollServiceDto.set(longPollNotifiedServiceDto);
m_remoteMessages.set(remoteMessages);
m_executorService.submit(new Runnable() {
@Override
public void run() {
m_configNeedForceRefresh.set(true);
trySync();
}
});
}
private List<ServiceDTO> getConfigServices() {
List<ServiceDTO> services = m_serviceLocator.getConfigServices();
if (services.size() == 0) {
throw new ApolloConfigException("No available config service");
}
return services;
}
}
四、總結
本小結我們從ConfigRespository入手,分析了Apollo客戶端在創建了DefaultConfig後如何來獲取配置,我們主要分析了ConfigRespository的主要兩個實現類LocalFileConfigRepository和RemoteConfigRespository,接下來我們用一幅圖來描述下本結描述的內容,方便理解: