为什么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();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章