Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。

服务端基于Spring Boot和Spring Cloud开发,打包后可以直接运行,不需要额外安装Tomcat等应用容器。

Java客户端不依赖任何框架,能够运行于所有Java运行时环境,同时对Spring/Spring Boot环境也有较好的支持。

分两个部分去分析,首先从获取配置信息角度。

添加pom依赖

<dependency>
			<groupId>com.ctrip.framework.apollo</groupId>
			<artifactId>apollo-client</artifactId>
			<version>1.1.0</version>
		</dependency>

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

看一下资源路径

如何获取apollo上所有namespace的配置数据 获取apollo配置代码实现_ide

spring.factories文件中,包含两个启动会注入的类。首先看ApolloApplicationContextInitializer。

public class ApolloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    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<String> 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<ServiceDTO> configServices = this.getConfigServices();
        String url = null;

        for(int i = 0; i < maxRetries; ++i) {
            List<ServiceDTO> 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<ApolloConfig> 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将从服务器获取的配置信息同步到本地文件中。

至此,从服务器获取配置信息分析完毕。