一、ConfigService.getConfig
- ConfigService.getConfig这个方法基本加载了Apollo通过com.google.inject.Injector实现的所有单例
- 执行配置同步的入口
- DefaultConfigFactory#create创建了DefaultConfig,DefaultConfig是读取Apollo配置的入口,提供了如getProperties等方法
@Override
public Config create(String namespace) {
// 1. 如果是ConfigFileFormat枚举定义的后缀结尾,如yml,创建PropertiesCompatibleFileConfigRepository
ConfigFileFormat format = determineFileFormat(namespace);
if (ConfigFileFormat.isPropertiesCompatible(format)) {
return new DefaultConfig(namespace, createPropertiesCompatibleFileConfigRepository(namespace, format));
}
// 2. 其他创建LocalFileConfigRepository
return new DefaultConfig(namespace, createLocalConfigRepository(namespace));
}
LocalFileConfigRepository createLocalConfigRepository(String namespace) {
// 1. 判断env是否未Local模式,如果是的话,不走Apollo,直接取本地配置文件(-Denv=LOCAL可以不连接Apollo服务端,走本地配置文件)
if (m_configUtil.isInLocalMode()) {
return new LocalFileConfigRepository(namespace);
}
// 2. 创建RemoteConfigRepository,设置LocalFileConfigRepository的upstream为这个RemoteConfigRepository
return new LocalFileConfigRepository(namespace, createRemoteConfigRepository(namespace));
}
二、 LocalFileConfigRepository(namespace, createRemoteConfigRepository(namespace));
public LocalFileConfigRepository(String namespace, ConfigRepository upstream) {
// 1. 创建本地目录 mac - /opt/data/{app.id}/config-cache/
this.setLocalCacheDir(findLocalCacheDir(), false);
// 2. 设置upstream,从upstream(RemoteConfigRepository)同步配置信息并持久化到本地文件,加载配置信息到内存m_fileProperties
this.setUpstreamRepository(upstream);
// 3. 如果上面同步配置信息失败,这里会再同步一次,加载配置信息到内存m_fileProperties
this.trySync();
}
三、 new RemoteConfigRepository(namespace)
- 首次配置同步
- 启动一个线程每5分钟更新一次配置
- 启动一个线程,使用HttpLongPolling,建立90s长连接,实时获取服务端的配置推送
public RemoteConfigRepository(String namespace) {
gson = new Gson();
// 首次配置同步
this.trySync();
// 定时从Apollo配置中心服务端拉取应用的最新配置 fallback机制,为了防止推送机制失效导致配置不更新
this.schedulePeriodicRefresh();
// 客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送。(通过Http Long Polling实现)
this.scheduleLongPollingRefresh();
}
四、DefaultConfig、LocalFileConfigRepository和RemoteConfigRepository的关系
-
首先他们都是namespace级别的,即一个namespace一个实例
-
DefaultConfig的getProperties方法的数据来源是
public String getProperty(String key, String defaultValue) { // 1. -D参数 String value = System.getProperty(key); // 2. upstream给到的properties,这里就是LocalFileConfigRepository给的properties if (value == null && m_configProperties.get() != null) { value = m_configProperties.get().getProperty(key); } // 3. 系统变量 if (value == null) { value = System.getenv(key); } // 4. META-INF/config/{namespace}.properties if (value == null && m_resourceProperties != null) { value = m_resourceProperties.getProperty(key); } return value == null ? defaultValue : value; } -
当RemoteConfigRepository获取到Apollo服务端的最新配置后,会通知LocalFileConfigRepository更新本地配置文件;当LocalFileConfigRepository更新完本地配置文件中,会通知DefaultConfig更新m_configProperties这个属性
五、RemoteConfigRepository
1、sync方法
- trySync方法是AbstractConfigRepository定义的,主要是trycatch了异常,真正实现配置同步的方法是子类的sync方法
@Override
protected synchronized void sync() {
// 1. 当前缓存的配置对象
ApolloConfig previous = m_configCache.get();
// 2. 调ConfigService获取当前配置信息,如果返回304则用当前的配置对象返回
ApolloConfig current = loadApolloConfig();
// 3. 如果当前缓存里的配置与拉取的配置不相等,更新缓存里的配置对象
if (previous != current) {
m_configCache.set(current);
// 4. 通知所有RepositoryChangeListener(首次启动同步配置时,还未注册Listener;后续每次配置更新,都会走这里通知到LocalFileConfigRepository, 然后通知到DefaultConfig)
this.fireRepositoryChange(m_namespace, this.getConfig());
}
}
private ApolloConfig loadApolloConfig() {
// RateLimiter
if (!m_loadConfigRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) {
TimeUnit.SECONDS.sleep(5);
}
String appId = m_configUtil.getAppId();
String cluster = m_configUtil.getCluster();
String dataCenter = m_configUtil.getDataCenter();
String secret = m_configUtil.getAccessKeySecret();
int maxRetries = m_configNeedForceRefresh.get() ? 2 : 1;
// 1. 获取ConfigService的信息 http://localhost:8080/services/config?appId=SampleApp&ip=192.168.0.105
// appName='APOLLO-CONFIGSERVICE', instanceId='192.168.0.105:apollo-configservice:8080', homepageUrl='http://192.168.0.105:8080/'
List<ServiceDTO> configServices = getConfigServices();
String url = null;
// 2. 最多尝试拉取2次配置maxRetries = 2
for (int i = 0; i < maxRetries; i++) {
// 3. 优先使用常轮询过程中调用本机的ConfigService(m_longPollServiceDto)
List<ServiceDTO> randomConfigServices = Lists.newLinkedList(configServices);
Collections.shuffle(randomConfigServices);
if (m_longPollServiceDto.get() != null) {
randomConfigServices.add(0, m_longPollServiceDto.getAndSet(null));
}
for (ServiceDTO configService : randomConfigServices) {
// 4. 组装URL:http://192.168.0.105:8080/configs/SampleApp/default/application?ip=192.168.0.105
url = assembleQueryConfigUrl(configService.getHomepageUrl(), appId, cluster, m_namespace,
dataCenter, m_remoteMessages.get(), m_configCache.get());
HttpRequest request = new HttpRequest(url);
// 5. 如果设置了secret,把secret放入请求头
if (!StringUtils.isBlank(secret)) {
Map<String, String> headers = Signature.buildHttpHeaders(url, appId, secret);
request.setHeaders(headers);
}
// 6. 调用
HttpResponse<ApolloConfig> response = m_httpUtil.doGet(request, ApolloConfig.class);
// 7. 304直接用缓存对象,否则用configService提供的Config
if (response.getStatusCode() == 304) {
return m_configCache.get();
}
ApolloConfig result = response.getBody();
return result;
}
}
}
2、 每5分钟请求Apollo服务端拉取配置的定时任务,走的也是trySync
private void schedulePeriodicRefresh() {
m_configUtil.getRefreshInterval(), m_configUtil.getRefreshIntervalTimeUnit());
m_executorService.scheduleAtFixedRate(
new Runnable() {
@Override
public void run() {
trySync();
}
}, m_configUtil.getRefreshInterval(), m_configUtil.getRefreshInterval(),
m_configUtil.getRefreshIntervalTimeUnit());
}
3、 启动一个线程,使用HttpLongPolling,建立90s长连接,实时获取服务端的配置推送
- 这里服务端只会告知哪个namespace的配置发生了更新,需要客户端自己使用trySync方法去取配置
private void scheduleLongPollingRefresh() {
remoteConfigLongPollService.submit(m_namespace, this);
}
RemoteConfigLongPollService#doLongPollingRefresh
private void doLongPollingRefresh(String appId, String cluster, String dataCenter, String secret) {
final Random random = new Random();
ServiceDTO lastServiceDto = null;
while (!m_longPollingStopped.get() && !Thread.currentThread().isInterrupted()) {
if (!m_longPollRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) {
TimeUnit.SECONDS.sleep(5);
}
String url = null;
try {
// 1. 随机选取一个ConfigService
if (lastServiceDto == null) {
List<ServiceDTO> configServices = getConfigServices();
lastServiceDto = configServices.get(random.nextInt(configServices.size()));
}
// 2. 组装URL http://192.168.0.105:8080/notifications/v2?cluster=default&appId=SampleApp&ip=192.168.0.105¬ifications=%5B%7B%22namespaceName%22%3A%22application%22%2C%22notificationId%22%3A-1%7D%5D
url = assembleLongPollRefreshUrl(lastServiceDto.getHomepageUrl(), appId, cluster, dataCenter, m_notifications);
HttpRequest request = new HttpRequest(url);
request.setReadTimeout(LONG_POLLING_READ_TIMEOUT); // 90秒长轮询
// 设置secret
if (!StringUtils.isBlank(secret)) {
Map<String, String> headers = Signature.buildHttpHeaders(url, appId, secret);
request.setHeaders(headers);
}
// 3. 返回 [{"namespaceName":"application","notificationId":40,"messages":{"details":{"SampleApp+default+application":40}}}]
final HttpResponse<List<ApolloConfigNotification>> response =
m_httpUtil.doGet(request, m_responseType);
// 4. 处理返回的结果
if (response.getStatusCode() == 200 && response.getBody() != null) {
// 4-1. 更新this.m_notifications的value为返回的notificationId
updateNotifications(response.getBody());
// 4-2. 更新this.m_remoteNotificationMessages
updateRemoteNotifications(response.getBody());
// 4-3. 通知RemoteConfigRepository.onLongPollNotified
notify(lastServiceDto, response.getBody());
}
//try to load balance
// 如果60秒内没有配置更新,随机触发是否置空lastServiceDto,下次循环换一个ConfigService请求
if (response.getStatusCode() == 304 && random.nextBoolean()) {
lastServiceDto = null;
}
// 5. 重置失败策略(睡眠)
m_longPollFailSchedulePolicyInSecond.success();
} catch (Throwable ex) {
lastServiceDto = null;
// 失败睡眠,1,2,4,...秒
long sleepTimeInSecond = m_longPollFailSchedulePolicyInSecond.fail();
TimeUnit.SECONDS.sleep(sleepTimeInSecond);
}
}
}
- RemoteConfigRepository.onLongPollNotified,最后还是通过trySync同步配置
// Client长轮询,更新配置入口
public void onLongPollNotified(ServiceDTO longPollNotifiedServiceDto, ApolloNotificationMessages remoteMessages) {
// 1. 设置长轮询对应的ConfigService ip和port,之后trySync直接调这个节点查询配置
m_longPollServiceDto.set(longPollNotifiedServiceDto);
// 2. 设置收到的notify消息
m_remoteMessages.set(remoteMessages);
// 3. 同步配置(和启动流程里的同步逻辑一致)
m_executorService.submit(new Runnable() {
@Override
public void run() {
m_configNeedForceRefresh.set(true);
trySync();
}
});
}