Apollo源码阅读(五)Apollo客户端-更新配置的实现方式

2,128 阅读3分钟

一、RemoteConfigRepository同步配置流程回顾

  • 上次介绍了客户端获取配置的流程:juejin.cn/post/686931…
  • 回顾一下RemoteConfigRepository的大流程,主要分成下面四步
@Override
protected synchronized void sync() {
    // 1. 当前缓存的配置对象
    ApolloConfig previous = m_configCache.get();
    // 2. 调ConfigService获取当前配置信息,如果返回304则用当前的配置对象返回
    ApolloConfig current = loadApolloConfig();
    if (previous != current) {
      // 3. 如果当前缓存里的配置与拉取的配置不相等,更新缓存里的配置对象
      m_configCache.set(current);
      // 4. 通知所有RepositoryChangeListener(LocalFileConfigRepository)
      this.fireRepositoryChange(m_namespace, this.getConfig());
    }
}

二、LocalFileConfigRepository#onRepositoryChange更新本地配置

// 当RemoteConfigRepository中的配置发生变化,这里接收到通知
@Override
public void onRepositoryChange(String namespace, Properties newProperties) {
  if (newProperties.equals(m_fileProperties)) {
    return;
  }
  Properties newFileProperties = propertiesFactory.getPropertiesInstance();
  newFileProperties.putAll(newProperties);
  // 1. 更新本地缓存文件
  updateFileProperties(newFileProperties, m_upstream.getSourceType());
  // 2. 通知下游其他RepositoryChangeListener(DefaultConfig)
  this.fireRepositoryChange(namespace, newProperties);
}

三、DefaultConfig#onRepositoryChange

/**
 * LocalFileConfigRepository#onRepositoryChange(java.lang.String, java.util.Properties)
 * 在本地缓存配置发生更新后,这里收到消息
 * @param namespace the namespace of this repository change
 * @param newProperties the properties after change
 */
@Override
public synchronized void onRepositoryChange(String namespace, Properties newProperties) {
  ConfigSourceType sourceType = m_configRepository.getSourceType();
  Properties newConfigProperties = propertiesFactory.getPropertiesInstance();
  newConfigProperties.putAll(newProperties);
  // 1、计算实际发生变化的配置,更新缓存里的properties
  Map<String, ConfigChange> actualChanges = updateAndCalcConfigChanges(newConfigProperties, sourceType);
  // 2. 通知所有ConfigChangeListener
  this.fireConfigChange(new ConfigChangeEvent(m_namespace, actualChanges));
}

四、ConfigChangeListener真正触发配置更新的地方

public interface ConfigChangeListener {
  /**
   * Invoked when there is any config change for the namespace.
   * @param changeEvent the event for this change
   */
  public void onChange(ConfigChangeEvent changeEvent);
}

4-1 AutoUpdateConfigChangeListener负责更新@Value和@ApolloJsonValue注解的方法和属性

  • SpringValueRegistry中找到key对应的values
  • 解析注解,反射更新
 @Override
  public void onChange(ConfigChangeEvent changeEvent) {
    Set<String> keys = changeEvent.changedKeys();
    for (String key : keys) {
      // 1. 从springValueRegistry获取抽象配置SpringValue
      Collection<SpringValue> targetValues = springValueRegistry.get(beanFactory, key);
      // 2. 执行更新
      for (SpringValue val : targetValues) {
        updateSpringValue(val);
      }
    }
  }
  private void updateSpringValue(SpringValue springValue) {
    // 解析@Value和@ApolloJsonValue获得配置的真正value
    Object value = resolvePropertyValue(springValue);
    // 使用value执行反射method.invoke(bean,value)或field.set(bean,value)
    springValue.update(value);
  }
  

4-2 @ApolloConfigChangeListener+RefreshScope更新@ConfigurationProperties修饰的配置类

  • @ConfigurationProperties统一管理同一类配置,比如官方demo里
@ConfigurationProperties(prefix = "redis.cache")
@Component("sampleRedisConfig")
@RefreshScope
public class SampleRedisConfig {
  private int expireSeconds;
  private String clusterNodes;
  private int commandTimeout;
}
  • 而AutoUpdateConfigChangeListener不支持这样的配置类的更新,apollo是通过@ApolloConfigChangeListener+RefreshScope的方式来处理的,RefreshScope会根据beanName删除缓存中的bean实例,然后下次getBean时重新创建一个bean实例
@Component
public class SpringBootApolloRefreshConfig {
  private final RefreshScope refreshScope;
  public SpringBootApolloRefreshConfig(final RefreshScope refreshScope) {
    this.refreshScope = refreshScope;
  }
  /**
   * 通过RefreshScope的refresh方法处理ConfigurationProperties注释的配置类
   * 主要是先通过beanName删除了ioc容器中缓存的bean实例,当下次调用bean的方法时触发getBean获取新的一个bean
   * @param changeEvent
   */
  @ApolloConfigChangeListener(value = {ConfigConsts.NAMESPACE_APPLICATION},
      interestedKeyPrefixes = {"redis.cache."})
  public void onChange(ConfigChangeEvent changeEvent) {
    refreshScope.refresh("sampleRedisConfig");
  }
}
  • @ApolloConfigChangeListener收到通知其实也是通过ConfigChangeListener实现的,这里ConfigChangeListener是通过匿名内部类,在ApolloAnnotationProcessor的BeanPostProcessor阶段往Config对象注入的
  • juejin.cn/post/686931… 的4-1中提到过
 /**
   * 1、从method上找到ApolloConfigChangeListener注解
   * 2、创建ConfigChangeListener---目的是当配置发生变化时,反射调用ApolloConfigChangeListener注解的方法
   * 3、注册ConfigChangeListener到每个Namespace的Config上
   */
  @Override
  protected void processMethod(final Object bean, String beanName, final Method method) {
    ApolloConfigChangeListener annotation = AnnotationUtils
        .findAnnotation(method, ApolloConfigChangeListener.class);
    ReflectionUtils.makeAccessible(method);
    String[] namespaces = annotation.value();
    String[] annotatedInterestedKeys = annotation.interestedKeys();
    String[] annotatedInterestedKeyPrefixes = annotation.interestedKeyPrefixes();
    // 监听Apollo配置发生变化的Listener,当有配置发生变化会调用ConfigChangeListener的onChange方法
    ConfigChangeListener configChangeListener = new ConfigChangeListener() {
      @Override
      public void onChange(ConfigChangeEvent changeEvent) {
        ReflectionUtils.invokeMethod(method, bean, changeEvent);
      }
    };

    Set<String> interestedKeys = annotatedInterestedKeys.length > 0 ? Sets.newHashSet(annotatedInterestedKeys) : null;
    Set<String> interestedKeyPrefixes = annotatedInterestedKeyPrefixes.length > 0 ? Sets.newHashSet(annotatedInterestedKeyPrefixes) : null;
    // 循环每个namespace,向他们的Config对象注册ConfigChangeListener
    for (String namespace : namespaces) {
      Config config = ConfigService.getConfig(namespace);
      if (interestedKeys == null && interestedKeyPrefixes == null) {
        config.addChangeListener(configChangeListener);
      } else {
        config.addChangeListener(configChangeListener, interestedKeys, interestedKeyPrefixes);
      }
    }
  }