在之前的文章《揭秘!微服务架构下,Apollo 配置中心凭啥扮演关键角色?》中,我们提到Apollo的可以支持多环境、多集群灵活配置。今天我们来探究下Apollo是如何实现环境隔离的。
Apollo 实现环境隔离的核心源码剖析
1. 客户端加载指定环境的apollo服务端配置
在 Apollo 客户端源码中,ConfigServiceLocator 类扮演着关键角色。当微服务启动时,它会负责定位和加载配置服务。在这个过程中,会读取系统环境变量。核心方法是tryUpdateConfigServices,该方法有一个核心逻辑会获取meta.service的url,在获取的过程中,会先获取环境变量m_env。
以下是代码片段。
// 获取环境变量的值,test,ontest,prod等m_env = System.getProperty("env");if (!Utils.isBlank(m_env)) {m_env = m_env.trim();logger.info("Environment is set to [{}] by JVM system property 'env'.", m_env);return;}
// 获取meta.service的urlString url = this.assembleMetaServiceUrl();HttpRequest request = new HttpRequest(url);int maxRetries = 2;Throwable exception = null;// 采用重试机制拉取apollo服务端的配置,并缓存到本地for(int i = 0; i < maxRetries; ++i) {HttpResponse<List<ServiceDTO>> response = this.m_httpClient.doGet(request, this.m_responseType);transaction.setStatus("0");List<ServiceDTO> services = (List)response.getBody();if (services == null || services.isEmpty()) {this.logConfigService("Empty response!");continue;}this.setConfigServices(services);}
// 用可调度的线程池每隔5秒,启动一个线程,执行一下上述更新操作。tryUpdateConfigServices()方法的逻辑就是上述提到的2次重试,从apollo服务端拉取配置,更新本地缓存。this.m_executorService.scheduleAtFixedRate(new Runnable() {public void run() {ConfigServiceLocator.logger.debug("refresh config services");Tracer.logEvent("Apollo.MetaService", "periodicRefresh");ConfigServiceLocator.this.tryUpdateConfigServices();}}, (long)this.m_configUtil.getRefreshInterval(), (long)this.m_configUtil.getRefreshInterval(), this.m_configUtil.getRefreshIntervalTimeUnit());
2. 服务端配置信息存储与隔离机制
在 Apollo 服务端源码中,配置信息在数据库中的存储结构设计体现了环境隔离的思想。在配置数据的持久化操作中,例如在InstanceConfigRepository 类的相关方法里,在插入或查询配置数据时,都会带上环境标识作为条件。
每个环境对应一个或多个集群(clusterName),每个集群中有多个命名空间(amespaceNam),每个命名空间属于一个应用(appId)。
3. 客户端缓存
Apollo 客户端为了提高配置读取效率,采用了缓存机制。
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());}
以下是ApolloConfig实体。
public class ApolloConfig {private String appId;private String cluster;private String namespaceName;private Map<String, String> configurations;private String releaseKey;
}
最后在来串一个流程,微服务在启动时,Apollo会通过ConfigServiceLocator类的schedulePeriodicRefresh方法每隔5秒启动一个线程,拉取Apollo服务节点的相关信息,包括AppName, 服务实例ID等,并更新ServiceDTO。
拉取的时候会通过环境变量获取服务节点url,做到了环境隔离。
public class ServiceDTO {private String appName;private String instanceId;private String homepageUrl;
}
同时,会通过RemoteConfigRepository类的schedulePeriodicRefresh方法每隔5秒去拉取Apollo的配置,包括appId, 集群,命名空间等,并更新ApolloConfig(缓存机制)。在拉取的时候,会先获取Apollo服务节点的相关信息。然后组装URL,通过HTTP客户端去发起请求,拉取数据。
private List<ServiceDTO> getConfigServices() {// 调用ConfigServiceLocator类getConfigServices()方法List<ServiceDTO> services = this.m_serviceLocator.getConfigServices();if (services.size() == 0) {throw new ApolloConfigException("No available config service");} else {return services;}}
// 组装urlurl = this.assembleQueryConfigUrl(configService.getHomepageUrl(), appId, cluster, this.m_namespace, dataCenter, (ApolloNotificationMessages)this.m_remoteMessages.get(), (ApolloConfig)this.m_configCache.get());HttpRequest request = new HttpRequest(url);// 发起http请求HttpResponse<ApolloConfig> response = this.m_httpClient.doGet(request, ApolloConfig.class);this.m_configNeedForceRefresh.set(false);this.m_loadConfigFailSchedulePolicy.success();transaction.addData("StatusCode", response.getStatusCode());transaction.setStatus("0");ApolloConfig result;// 根据响应码处理相关逻辑if (response.getStatusCode() != 304) {result = (ApolloConfig)response.getBody();logger.debug("Loaded config for {}: {}", this.m_namespace, result);ApolloConfig var34 = result;return var34;}