Apollo配置中心Client源码学习(二)-- 配置同步

 

  上一篇文章(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。

Apollo配置中心Client源码学习(二)-- 配置同步_第1张图片

  首先研究下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 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 m_configCache;
  private final String m_namespace;
  private final static ScheduledExecutorService m_executorService;
  private final AtomicReference m_longPollServiceDto;
  private final AtomicReference 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 configServices = getConfigServices();
    String url = null;
    for (int i = 0; i < maxRetries; i++) {
      //随机的获取一个configService
      List 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 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 pathParams =
        Lists.newArrayList(pathEscaper.escape(appId), pathEscaper.escape(cluster),
            pathEscaper.escape(namespace));
    Map 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 getConfigServices() {
    List services = m_serviceLocator.getConfigServices();
    if (services.size() == 0) {
      throw new ApolloConfigException("No available config service");
    }

    return services;
  }
}

四、总结

   本小结我们从ConfigRespository入手,分析了Apollo客户端在创建了DefaultConfig后如何来获取配置,我们主要分析了ConfigRespository的主要两个实现类LocalFileConfigRepository和RemoteConfigRespository,接下来我们用一幅图来描述下本结描述的内容,方便理解:

Apollo配置中心Client源码学习(二)-- 配置同步_第2张图片

 

你可能感兴趣的:(Apollo使用及源码分析,注册中心,源码学习)