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。

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

    return services;
  }
}

四、總結

   本小結我們從ConfigRespository入手,分析了Apollo客戶端在創建了DefaultConfig後如何來獲取配置,我們主要分析了ConfigRespository的主要兩個實現類LocalFileConfigRepository和RemoteConfigRespository,接下來我們用一幅圖來描述下本結描述的內容,方便理解:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章