爲什麼apollo客戶端可以實時獲取配置

普通情況下,使用 實現apollo的監聽器ConfigChangeListener,通過onChange方法來實時獲取配置。

但是,如果對配置變化的時效性要求不高,只是想在用到配置的時候,使用新配置的話,可以不實現監聽器,直接從environment裏面獲取配置。

apollo機制

圖解

在這裏插入圖片描述

代碼分析

還是通過老朋友refresh方法來層層揭曉吧。

首先,通過AbstractApplicationContext.refresh()中:

invokeBeanFactoryPostProcessors(beanFactory);

經歷重重磨難,找到了PropertySourcesProcessor,並執行其postProcessBeanFactory方法,進入initializePropertySources()。

主要流程

1.創建Config

2.用CompositePropertySource包裝Config,並塞入environment

private void initializePropertySources() {
  if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME)) {
    // 已經初始化過apollo配置就直接返回
    return;
  }
    // 創建一個PropertySource,用於裝入所有的配置
  CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME);
......

  while (iterator.hasNext()) {
    int order = iterator.next();
    for (String namespace : NAMESPACE_NAMES.get(order)) {
        // 獲取一個config,如果沒有,則會創建一個config(後續分析,很重要哦)
      Config config = ConfigService.getConfig(namespace);
        // 用CompositePropertySource包裝config爲一個PropertySource,後續加入值environment
      composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
    }
  }

  // clean up
  NAMESPACE_NAMES.clear();

  // 如果存在ApolloBootstrapPropertySources,則要保證ApolloBootstrapPropertySources在第一個,並且將上述的composite設置在其後,否則就是將composite設置在第一個
  if (environment.getPropertySources()
      .contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
    ensureBootstrapPropertyPrecedence(environment);
    environment.getPropertySources()
        .addAfter(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME, composite);
  } else {
    // 將composite設置在第一個後,即便我們不適用listener方式,被通知配置的變更,也能直接從environment裏面通過getProperty的方式直接獲取配置
    environment.getPropertySources().addFirst(composite);
  }
}

從上,我們可以知道,最終的配置其實是放入environment裏面了。故而,我們可以通過environment直接獲取配置,只是不如通過監聽器的onChange方法獲取配置實時。

那麼,瞭解完兩部曲,就來細細品品其中的機制吧。一個config,爲啥就能讓你實時知道apollo的變更呢?

創建RemoteConfigRepository

之前說的 ConfigService.getConfig(namespace) ,獲取不到config,就會創建一個config的呀。此時創建的就是RemoteConfigRepository。看其構造方法,除了各種屬性賦值後,還會調用三個方法,它們很關鍵哈。

public RemoteConfigRepository(String namespace) {
  m_namespace = namespace;
  m_configCache = new AtomicReference<>();
  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();
  // 首次同步apollo
  this.trySync();
  // 定時刷新配置(大部分情況返回304,定時刷新在於可以防止長輪詢失敗)
  this.schedulePeriodicRefresh();
  // 長輪詢刷新配置(最主要的實時獲取配置的途徑)
  this.scheduleLongPollingRefresh();
}
首次同步apollo
@Override
protected synchronized void sync() {
  Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig");

  try {
    ApolloConfig previous = m_configCache.get();
    // 根據你設定的apollo地址,appId,maxRetries等信息,發送get請求,獲取當前apollo配置信息
    ApolloConfig current = loadApolloConfig();

    // 更新本地的apollo配置信息
    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();
  }
}
開啓定時刷新配置
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是同一個方法
          trySync();
          Tracer.logEvent("Apollo.Client.Version", Apollo.VERSION);
        }
          //時間間隔默認是5分鐘
      }, m_configUtil.getRefreshInterval(), m_configUtil.getRefreshInterval(),
      m_configUtil.getRefreshIntervalTimeUnit());
}
開啓長輪詢刷新配置
public boolean submit(String namespace, RemoteConfigRepository remoteConfigRepository) {
  boolean added = m_longPollNamespaces.put(namespace, remoteConfigRepository);
  m_notifications.putIfAbsent(namespace, INIT_NOTIFICATION_ID);
  if (!m_longPollStarted.get()) {
    // 開啓拉取  
    startLongPolling();
  }
  return added;
}

private void doLongPollingRefresh(String appId, String cluster, String dataCenter) {
  final Random random = new Random();
  ServiceDTO lastServiceDto = null;
  while (!m_longPollingStopped.get() && !Thread.currentThread().isInterrupted()) {
    if (!m_longPollRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) {
      // 等待5秒
      try {
        TimeUnit.SECONDS.sleep(5);
      } catch (InterruptedException e) {
      }
    }
      
      // 獲取各種信息,發起請求,看apollo中配置有無變更
      ......
          
      // 配置發生變化,則主動獲取新配置,且調用ConfigChangeListener的實現類的onChange方法    
      if (response.getStatusCode() == 200 && response.getBody() != null) {
        updateNotifications(response.getBody());
        updateRemoteNotifications(response.getBody());
        transaction.addData("Result", response.getBody().toString());
        notify(lastServiceDto, response.getBody());
      }
       ......
    } finally {
      transaction.complete();
    }
  }
}

notify方法會最終進入fireRepositoryChange

fireRepositoryChange

該方法是之前介紹的sync()方法裏面提到的,兩類刷新配置的任務都會執行此方法,來通知各個監聽器,有哪些配置變更。
1.獲取新配置

2.統計各種配置的變更

3.通知各個監聽器

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);
    }
  }
}

@Override
public synchronized void onRepositoryChange(String namespace, Properties newProperties) {
  if (newProperties.equals(m_configProperties.get())) {
    return;
  }

  ConfigSourceType sourceType = m_configRepository.getSourceType();
  Properties newConfigProperties = new Properties();
  newConfigProperties.putAll(newProperties);

  // 整理配置信息,確定配置的變更類型
  Map<String, ConfigChange> actualChanges = updateAndCalcConfigChanges(newConfigProperties, sourceType);

  //check double checked result
  if (actualChanges.isEmpty()) {
    return;
  }

  // 調用各個ConfigChangeListener實現類的onChange方法,發送配置信息
  this.fireConfigChange(new ConfigChangeEvent(m_namespace, actualChanges));

  Tracer.logEvent("Apollo.Client.ConfigChanges", m_namespace);
}

private Map<String, ConfigChange> updateAndCalcConfigChanges(Properties newConfigProperties,
    ConfigSourceType sourceType) {
  // 初次統計變化的配置  
  List<ConfigChange> configChanges =
      calcPropertyChanges(m_namespace, m_configProperties.get(), newConfigProperties);

  ImmutableMap.Builder<String, ConfigChange> actualChanges =
      new ImmutableMap.Builder<>();

  /** === Double check since DefaultConfig has multiple config sources ==== **/

  //1. 爲配置設置舊值
  for (ConfigChange change : configChanges) {
    change.setOldValue(this.getProperty(change.getPropertyName(), change.getOldValue()));
  }

  //2. 更新 m_configProperties
  updateConfig(newConfigProperties, sourceType);
  clearConfigCache();

  //3. 遍歷所有的新配置,最後確認各個配置的type(ADDED/MODIFIED/DELETED)
  for (ConfigChange change : configChanges) {
    change.setNewValue(this.getProperty(change.getPropertyName(), change.getNewValue()));
    switch (change.getChangeType()) {
      case ADDED:
        if (Objects.equals(change.getOldValue(), change.getNewValue())) {
          break;
        }
        if (change.getOldValue() != null) {
          change.setChangeType(PropertyChangeType.MODIFIED);
        }
        actualChanges.put(change.getPropertyName(), change);
        break;
      case MODIFIED:
        if (!Objects.equals(change.getOldValue(), change.getNewValue())) {
          actualChanges.put(change.getPropertyName(), change);
        }
        break;
      case DELETED:
        if (Objects.equals(change.getOldValue(), change.getNewValue())) {
          break;
        }
        if (change.getNewValue() != null) {
          change.setChangeType(PropertyChangeType.MODIFIED);
        }
        actualChanges.put(change.getPropertyName(), change);
        break;
      default:
        //do nothing
        break;
    }
  }
  return actualChanges.build();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章