Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
服务端基于Spring Boot和Spring Cloud开发,打包后可以直接运行,不需要额外安装Tomcat等应用容器。
Java客户端不依赖任何框架,能够运行于所有Java运行时环境,同时对Spring/Spring Boot环境也有较好的支持。
分两个部分去分析,首先从获取配置信息角度。
添加pom依赖
com.ctrip.framework.apollo
apollo-client
1.1.0
application.yml文件中添加配置:
这里需要注意下,apollo.bootstrap.enabled这个参数设不设置均可。
apollo.bootstrap.enabled=true,代表springboot在启动阶段就会加载,具体在SpringApplication类prepareContext方法中执行applyInitializers(context)时会加载。会在创建Bean之前。
apollo.bootstrap.enabled=false,代表在springboot启动时不会加载配置,而是通过apollo注入PropertySourcesProcessor对象时开始加载,这时加载的配置可能并不一定是apollo的配置信息了,因为其他Bean对象可能提前加载,注入的Value属性就有可能是application.yml中的。
另外如果Apollo和Spring Cloud Config同时都有,会有两种情况,
1、apollo.bootstrap.enabled=true,取的是apollo的配置,因为apollo的order值再最后加载,但是调用了addFirst。
2、apollo.bootstrap.enabled=false,Spring Conig会先加载,如果在apollo的PropertySourcesProcessor加载前注入的value就是Spring Config的,再PropertySourcesProcessor加载后,注入的就是Apollo的配置信息。
#appId 同来区别不同的配置
app:
id: com.test.apollo
#apollo服务器地址
apollo:
bootstrap:
enabled: true //启动的bootstrap阶段,向Spring容器注入
meta: http://localhost:8080
看一下资源路径
spring.factories文件中,包含两个启动会注入的类。首先看ApolloApplicationContextInitializer。
public class ApolloApplicationContextInitializer implements ApplicationContextInitializer {
private static final Logger logger = LoggerFactory.getLogger(ApolloApplicationContextInitializer.class);
private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();
private static final String[] APOLLO_SYSTEM_PROPERTIES = new String[]{"app.id", "apollo.cluster", "apollo.cacheDir", "apollo.meta"};
private final ConfigPropertySourceFactory configPropertySourceFactory = (ConfigPropertySourceFactory)SpringInjector.getInstance(ConfigPropertySourceFactory.class);
public ApolloApplicationContextInitializer() {
}
public void initialize(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
this.initializeSystemProperty(environment);
String enabled = environment.getProperty("apollo.bootstrap.enabled", "false");
if (!Boolean.valueOf(enabled).booleanValue()) {
logger.debug("Apollo bootstrap config is not enabled for context {}, see property: ${
{}}", context, "apollo.bootstrap.enabled");
} else {
logger.debug("Apollo bootstrap config is enabled for context {}", context);
if (!environment.getPropertySources().contains("ApolloBootstrapPropertySources")) {
String namespaces = environment.getProperty("apollo.bootstrap.namespaces", "application");
logger.debug("Apollo bootstrap namespaces: {}", namespaces);
List namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);
CompositePropertySource composite = new CompositePropertySource("ApolloBootstrapPropertySources");
Iterator i$ = namespaceList.iterator();
while(i$.hasNext()) {
String namespace = (String)i$.next();
Config config = ConfigService.getConfig(namespace);
composite.addPropertySource(this.configPropertySourceFactory.getConfigPropertySource(namespace, config));
}
environment.getPropertySources().addFirst(composite);
}
}
}
void initializeSystemProperty(ConfigurableEnvironment environment) {
String[] arr$ = APOLLO_SYSTEM_PROPERTIES;
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; ++i$) {
String propertyName = arr$[i$];
this.fillSystemPropertyFromEnvironment(environment, propertyName);
}
}
private void fillSystemPropertyFromEnvironment(ConfigurableEnvironment environment, String propertyName) {
if (System.getProperty(propertyName) == null) {
String propertyValue = environment.getProperty(propertyName);
if (!Strings.isNullOrEmpty(propertyValue)) {
System.setProperty(propertyName, propertyValue);
}
}
}
}
主要就是通过initialize的initializeSystemProperty方法,将application.yml配置文件中的信息配置到System.property中的属性中。
之后获取apollo.bootstrap.enabled参数,如果为true,则进入else代码段。首先看是否设置了apollo的namespaces,没有的话默认为application。之后创建一个PropertySource,名字为ApolloBootstrapPropertySources。之后进入ConfigService.getConfig(namespace)方法。
while(i$.hasNext()) {
String namespace = (String)i$.next();
Config config = ConfigService.getConfig(namespace);
composite.addPropertySource(this.configPropertySourceFactory.getConfigPropertySource(namespace, config));
}
进入DefaultConfigManager.getConfig方法
public Config getConfig(String namespace) {
Config config = (Config)this.m_configs.get(namespace);
if (config == null) {
synchronized(this) {
config = (Config)this.m_configs.get(namespace);
if (config == null) {
ConfigFactory factory = this.m_factoryManager.getFactory(namespace);
config = factory.create(namespace);
this.m_configs.put(namespace, config);
}
}
}
return config;
}
继续进入DefaultConfigFactory.create方法
public Config create(String namespace) {
DefaultConfig defaultConfig = new DefaultConfig(namespace, this.createLocalConfigRepository(namespace));
return defaultConfig;
}
LocalFileConfigRepository createLocalConfigRepository(String namespace) {
if (this.m_configUtil.isInLocalMode()) {
logger.warn("==== Apollo is in local mode! Won't pull configs from remote server for namespace {} ! ====", namespace);
return new LocalFileConfigRepository(namespace);
} else {
return new LocalFileConfigRepository(namespace, this.createRemoteConfigRepository(namespace));
}
}
RemoteConfigRepository createRemoteConfigRepository(String namespace) {
return new RemoteConfigRepository(namespace);
}
默认不是从本地获取,则会调用createRemoteConfigRepository方法,创建RemoteConfigRepository对象。
public RemoteConfigRepository(String namespace) {
this.m_namespace = namespace;
this.m_configCache = new AtomicReference();
this.m_configUtil = (ConfigUtil)ApolloInjector.getInstance(ConfigUtil.class);
this.m_httpUtil = (HttpUtil)ApolloInjector.getInstance(HttpUtil.class);
this.m_serviceLocator = (ConfigServiceLocator)ApolloInjector.getInstance(ConfigServiceLocator.class);
this.remoteConfigLongPollService = (RemoteConfigLongPollService)ApolloInjector.getInstance(RemoteConfigLongPollService.class);
this.m_longPollServiceDto = new AtomicReference();
this.m_remoteMessages = new AtomicReference();
this.m_loadConfigRateLimiter = RateLimiter.create((double)this.m_configUtil.getLoadConfigQPS());
this.m_configNeedForceRefresh = new AtomicBoolean(true);
this.m_loadConfigFailSchedulePolicy = new ExponentialSchedulePolicy(this.m_configUtil.getOnErrorRetryInterval(), this.m_configUtil.getOnErrorRetryInterval() * 8L);
this.gson = new Gson();
this.trySync();
this.schedulePeriodicRefresh();
this.scheduleLongPollingRefresh();
}
第三行创建m_configUtil对象,看一下构造方法。
public ConfigUtil() {
this.refreshIntervalTimeUnit = TimeUnit.MINUTES;
this.connectTimeout = 1000;
this.readTimeout = 5000;
this.loadConfigQPS = 2;
this.longPollQPS = 2;
this.onErrorRetryInterval = 1L;
this.onErrorRetryIntervalTimeUnit = TimeUnit.SECONDS;
this.maxConfigCacheSize = 500L;
this.configCacheExpireTime = 1L;
this.configCacheExpireTimeUnit = TimeUnit.MINUTES;
this.longPollingInitialDelayInMills = 2000L;
this.autoUpdateInjectedSpringProperties = true;
this.initRefreshInterval();
this.initConnectTimeout();
this.initReadTimeout();
this.initCluster();
this.initQPS();
this.initMaxConfigCacheSize();
this.initLongPollingInitialDelayInMills();
this.initAutoUpdateInjectedSpringProperties();
}
private void initCluster() {
this.cluster = System.getProperty("apollo.cluster");
if (Strings.isNullOrEmpty(this.cluster)) {
this.cluster = this.getDataCenter();
}
if (Strings.isNullOrEmpty(this.cluster)) {
this.cluster = "default";
}
}
public String getDataCenter() {
return Foundation.server().getDataCenter();
}
再跟进看下Foundation类。类加载时会通过静态代码段调用getManager方法。
public abstract class Foundation {
static {
getManager();
}
private static ProviderManager getManager() {
try {
if (s_manager == null) {
Object var0 = lock;
synchronized(lock) {
if (s_manager == null) {
s_manager = (ProviderManager)ServiceBootstrap.loadFirst(ProviderManager.class);
}
}
}
}
}
该方法会创建DefaultProviderManager对象。
public DefaultProviderManager() {
Provider applicationProvider = new DefaultApplicationProvider();
applicationProvider.initialize();
this.register(applicationProvider);
Provider networkProvider = new DefaultNetworkProvider();
networkProvider.initialize();
this.register(networkProvider);
Provider serverProvider = new DefaultServerProvider();
serverProvider.initialize();
this.register(serverProvider);
}
//applicationProvider的initialize
public void initialize() {
try {
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("/META-INF/app.properties");
if (in == null) {
in = DefaultApplicationProvider.class.getResourceAsStream("/META-INF/app.properties");
}
this.initialize(in);
} catch (Throwable var2) {
logger.error("Initialize DefaultApplicationProvider failed.", var2);
}
}
//如果没有会从application.yml中读取
private void initAppId() {
this.m_appId = System.getProperty("app.id");
if (!Utils.isBlank(this.m_appId)) {
this.m_appId = this.m_appId.trim();
logger.info("App ID is set to {} by app.id property from System Property", this.m_appId);
} else {
this.m_appId = this.m_appProperties.getProperty("app.id");
if (!Utils.isBlank(this.m_appId)) {
this.m_appId = this.m_appId.trim();
logger.info("App ID is set to {} by app.id property from {}", this.m_appId, "/META-INF/app.properties");
} else {
this.m_appId = null;
logger.warn("app.id is not available from System Property and {}. It is set to null", "/META-INF/app.properties");
}
}
}
会分别创建DefaultApplicationProvider、DefaultNetworkProvider、DefaultServerProvider对象。会分别从/META-INF/app.properties、C:/opt/settings/server.properties" : "/opt/settings/server.properties 读取配置信息。
继续回到RemoteConfigRepository构造方法,进入trySync方法。方法会调用loadApolloConfig方法调用Apollo服务端地址获取对应appId的配置信息。
protected synchronized void sync() {
Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig");
try {
ApolloConfig previous = (ApolloConfig)this.m_configCache.get();
ApolloConfig current = this.loadApolloConfig();
if (previous != current) {
logger.debug("Remote Config refreshed!");
this.m_configCache.set(current);
this.fireRepositoryChange(this.m_namespace, this.getConfig());
}
if (current != null) {
Tracer.logEvent(String.format("Apollo.Client.Configs.%s", current.getNamespaceName()), current.getReleaseKey());
}
transaction.setStatus("0");
} catch (Throwable var7) {
transaction.setStatus(var7);
throw var7;
} finally {
transaction.complete();
}
}
private ApolloConfig loadApolloConfig() {
if (!this.m_loadConfigRateLimiter.tryAcquire(5L, TimeUnit.SECONDS)) {
try {
TimeUnit.SECONDS.sleep(5L);
} catch (InterruptedException var26) {
;
}
}
String appId = this.m_configUtil.getAppId();
String cluster = this.m_configUtil.getCluster();
String dataCenter = this.m_configUtil.getDataCenter();
Tracer.logEvent("Apollo.Client.ConfigMeta", STRING_JOINER.join(appId, cluster, new Object[]{this.m_namespace}));
int maxRetries = this.m_configNeedForceRefresh.get() ? 2 : 1;
long onErrorSleepTime = 0L;
Throwable exception = null;
List configServices = this.getConfigServices();
String url = null;
for(int i = 0; i < maxRetries; ++i) {
List randomConfigServices = Lists.newLinkedList(configServices);
Collections.shuffle(randomConfigServices);
if (this.m_longPollServiceDto.get() != null) {
randomConfigServices.add(0, this.m_longPollServiceDto.getAndSet((Object)null));
}
for(Iterator i$ = randomConfigServices.iterator(); i$.hasNext(); onErrorSleepTime = this.m_configNeedForceRefresh.get() ? this.m_configUtil.getOnErrorRetryInterval() : this.m_loadConfigFailSchedulePolicy.fail()) {
ServiceDTO configService = (ServiceDTO)i$.next();
if (onErrorSleepTime > 0L) {
logger.warn("Load config failed, will retry in {} {}. appId: {}, cluster: {}, namespaces: {}", new Object[]{onErrorSleepTime, this.m_configUtil.getOnErrorRetryIntervalTimeUnit(), appId, cluster, this.m_namespace});
try {
this.m_configUtil.getOnErrorRetryIntervalTimeUnit().sleep(onErrorSleepTime);
} catch (InterruptedException var25) {
;
}
}
url = this.assembleQueryConfigUrl(configService.getHomepageUrl(), appId, cluster, this.m_namespace, dataCenter, (ApolloNotificationMessages)this.m_remoteMessages.get(), (ApolloConfig)this.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);
ApolloConfig result;
try {
HttpResponse response = this.m_httpUtil.doGet(request, ApolloConfig.class);
this.m_configNeedForceRefresh.set(false);
this.m_loadConfigFailSchedulePolicy.success();
transaction.addData("StatusCode", response.getStatusCode());
transaction.setStatus("0");
if (response.getStatusCode() != 304) {
result = (ApolloConfig)response.getBody();
logger.debug("Loaded config for {}: {}", this.m_namespace, result);
ApolloConfig var32 = result;
return var32;
}
logger.debug("Config server responds with 304 HTTP status code.");
result = (ApolloConfig)this.m_configCache.get();
} catch (ApolloConfigStatusCodeException var27) {
ApolloConfigStatusCodeException statusCodeException = var27;
if (var27.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, this.m_namespace);
statusCodeException = new ApolloConfigStatusCodeException(var27.getStatusCode(), message);
}
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(statusCodeException));
transaction.setStatus(statusCodeException);
exception = statusCodeException;
continue;
} catch (Throwable var28) {
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(var28));
transaction.setStatus(var28);
exception = var28;
continue;
} finally {
transaction.complete();
}
return result;
}
}
String message = String.format("Load Apollo Config failed - appId: %s, cluster: %s, namespace: %s, url: %s", appId, cluster, this.m_namespace, url);
throw new ApolloConfigException(message, (Throwable)exception);
}
创建完createRemoteConfigRepository后,继续回到createLocalConfigRepository方法,
LocalFileConfigRepository createLocalConfigRepository(String namespace) {
if (this.m_configUtil.isInLocalMode()) {
logger.warn("==== Apollo is in local mode! Won't pull configs from remote server for namespace {} ! ====", namespace);
return new LocalFileConfigRepository(namespace);
} else {
return new LocalFileConfigRepository(namespace, this.createRemoteConfigRepository(namespace));
}
}
看一下LocalFileConfigRepository构造方法。
public LocalFileConfigRepository(String namespace, ConfigRepository upstream) {
this.m_sourceType = ConfigSourceType.LOCAL;
this.m_namespace = namespace;
this.m_configUtil = (ConfigUtil)ApolloInjector.getInstance(ConfigUtil.class);
this.setLocalCacheDir(this.findLocalCacheDir(), false);
this.setUpstreamRepository(upstream);
this.trySync();
}
private File findLocalCacheDir() {
try {
String defaultCacheDir = this.m_configUtil.getDefaultLocalCacheDir();
Path path = Paths.get(defaultCacheDir);
if (!Files.exists(path, new LinkOption[0])) {
Files.createDirectories(path);
}
if (Files.exists(path, new LinkOption[0]) && Files.isWritable(path)) {
return new File(defaultCacheDir, "/config-cache");
}
} catch (Throwable var3) {
;
}
return new File(ClassLoaderUtil.getClassPath(), "/config-cache");
}
public String getDefaultLocalCacheDir() {
String cacheRoot = this.getCustomizedCacheRoot();
if (!Strings.isNullOrEmpty(cacheRoot)) {
return cacheRoot + File.separator + this.getAppId();
} else {
cacheRoot = this.isOSWindows() ? "C:\\opt\\data\\%s" : "/opt/data/%s";
return String.format(cacheRoot, this.getAppId());
}
}
public void setUpstreamRepository(ConfigRepository upstreamConfigRepository) {
if (upstreamConfigRepository != null) {
if (this.m_upstream != null) {
this.m_upstream.removeChangeListener(this);
}
this.m_upstream = upstreamConfigRepository;
this.trySyncFromUpstream();
upstreamConfigRepository.addChangeListener(this);
}
}
private boolean trySyncFromUpstream() {
if (this.m_upstream == null) {
return false;
} else {
try {
this.updateFileProperties(this.m_upstream.getConfig(), this.m_upstream.getSourceType());
return true;
} catch (Throwable var2) {
}
}
}
private synchronized void updateFileProperties(Properties newProperties, ConfigSourceType sourceType) {
this.m_sourceType = sourceType;
if (!newProperties.equals(this.m_fileProperties)) {
this.m_fileProperties = newProperties;
this.persistLocalCacheFile(this.m_baseDir, this.m_namespace);
}
}
setLocalCacheDir会找本地存储服务器端的配置项信息,windows环境在c:/opt/data,linux在/opt/data。
setUpstreamRepository方法调用trySyncFromUpstream方法,最后通过persistLocalCacheFile将从服务器获取的配置信息同步到本地文件中。
至此,从服务器获取配置信息分析完毕。