携程Apollo配置中心动态生效实现原理(转载)

一、结论:

Apollo配置中心动态生效机制,是基于Http长轮询请求和Spring扩展机制实现的,在Spring容器启动过程中,Apollo通过自定义的BeanPostProcessor和BeanFactoryPostProcessor將参数中包含${…}占位符和@Value注解的Bean注册到Apollo框架中定义的注册表中。然后通过Http长轮询不断的去获取服务端的配置信息,一旦配置发生变化,Apollo会根据变化的配置的Key找到对应的Bean,然后修改Bean的属性,从而实现了配置动态生效的特性。

需要注意的是,Apollo在配置变化后,只能修改Bean的属性,例如我们数据源的属性发生变化,新创建的Connection对象是没问题的,但是连接池中已经创建的Connection对象相关信息是不能动态修改的,所以依然需要重启应用。

二、分析过程

    2.1、Spring中的重要概念

          在了解Apollo配置中心实现原理之前,我们需要先熟悉一下Spring框架中的几个重要的概念:
         1、BeanDefinition
             用于描述Bean的配置信息,Bean配置一般有三种方式:
               (1)XML配置文件
              (2)@Service、@Component等注解
              (3)Java Config方式    
           对应的BeanDefinition实现类如下图,Spring容器启动时,会把所有的Bean配置信息转换为BeanDefinition对象。

       2、BeanDefinitionRegistry
             BeanDefinition容器,所有的Bean定义都注册在BeanDefinitionRegistry对象中。

       3、PropertySource
           用于存放Spring配置资源信息,例如spring项目中properties或者yaml文件配置信息均会保存在PropertySource对象中。Spring支持使用@PropertySource注解,將配置信息加载到Environment对象中。

       4、ImportBeanDefinitionRegistrar
            ImportBeanDefinitionRegistrar是一个接口,该接口的实现类作用于在Spring解析Bean配置生成BeanDefinition对象阶段。在Spring解析Configuration注解时,向Spring容器中增加额外的BeanDefinition。

       5、BeanFactoryPostProcessor
            Bean工厂后置处理器,用于在BeanDefinition对象注册完成后,修改Bean工厂信息,例如增加或者修改BeanDefinition对象。

      6、BeanDefinitionRegistryPostProcessor
          它是一个特殊的BeanFactoryPostProcessor,用于在BeanDefinition对象注册完成后,访问、新增或者修改BeanDefinition信息。

      7、PropertySourcesPlaceholderConfigurer
         它是一个特殊的BeanFactoryPostProcessor,用于解析Bean配置中的${…}参数占位符。

      8、BeanPostProcessor
         Bean后置处理器,bean初始化方法调用前后,执行拦截逻辑,可以对原有的Bean进行包装或者根据标记接口创建代理对象。

2.2、Spring框架启动过程回顾

      Spring框架启动大致会经过以下几个阶段:
         1、解析Bean配置信息,將配置信息转换为BeanDefinition对象,注册到BeanDefinitionRegistry中。

         2、执行所有的BeanFactoryPostProcessor的postProcessBeanFactory()方法对Bean工厂信息进行修改,包括修改或新增BeanDefinition对象。

        注意:如果需要控制BeanFactoryPostProcessor的执行顺序需要实现PriorityOrdered接口,getOrder()方法返回的值越小,执行优先级越高。

        3、通过BeanDefinition对象实例化所有Bean,注入依赖。

        4、执行所有BeanPostProcessor对象的postProcessBeforeInitialization()方法。

        5、执行Bean的初始化方法,例如InitializingBean接口的afterPropertiesSet方法,或init-method属性指定的方法。

            执行所有BeanPostProcessor对象的postProcessAfterInitialization()方法

2.3、Apollo原理解析

       Apollo框架使用非常简单,如果是Spring Boot项目,只需要在启动类上增加@EnableApolloConfig注解即可。例如:

@SpringBootApplication
@EnableApolloConfig
public class Application {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        SpringApplication.run(Application.class, args);
    }
}

 那么@EnableApolloConfig注解到底做了什么事情了,我们可以看下EnableApolloConfig注解的定义,代码如下

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ApolloConfigRegistrar.class)
public @interface EnableApolloConfig {
  /**
   * Apollo namespaces to inject configuration into Spring Property Sources.
   *
   * @return Namespace 名字的集合
   */
  String[] value() default {ConfigConsts.NAMESPACE_APPLICATION};

  /**
   * The order of the apollo config, default is {@link Ordered#LOWEST_PRECEDENCE}, which is Integer.MAX_VALUE.
   * If there are properties with the same name in different apollo configs, the apollo config with smaller order wins.
   * @return 优先级
   */
  int order() default Ordered.LOWEST_PRECEDENCE;
}

如上面代码所示,在EnableApolloConfig注解中,通过@Import注解导入了一个ApolloConfigRegistrar,接下来我们就来看一下:

public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar {

  private ApolloConfigRegistrarHelper helper = ServiceBootstrap.loadPrimary(ApolloConfigRegistrarHelper.class);

  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    helper.registerBeanDefinitions(importingClassMetadata, registry);
  }
}

接下来,看看 helper.registerBeanDefinitions(importingClassMetadata, registry) 的实现,既是 DefaultApolloConfigRegistrarHelper 的方法,代码如下:

 @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    // 解析 @EnableApolloConfig 注解
    AnnotationAttributes attributes = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(EnableApolloConfig.class.getName()));
    String[] namespaces = attributes.getStringArray("value");
    int order = attributes.getNumber("order");
    // 添加到 PropertySourcesProcessor 中
    PropertySourcesProcessor.addNamespaces(Lists.newArrayList(namespaces), order);

    Map<String, Object> propertySourcesPlaceholderPropertyValues = new HashMap<>();
    // to make sure the default PropertySourcesPlaceholderConfigurer's priority is higher than PropertyPlaceholderConfigurer
    propertySourcesPlaceholderPropertyValues.put("order", 0);
    // 注册 PropertySourcesPlaceholderConfigurer 到 BeanDefinitionRegistry 中,替换 PlaceHolder 为对应的属性值,
    // 参考文章 https://leokongwq.github.io/2016/12/28/spring-PropertyPlaceholderConfigurer.html
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
        PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues);
    //【差异】注册 PropertySourcesProcessor 到 BeanDefinitionRegistry 中,因为可能存在 XML 配置的 Bean ,用于 PlaceHolder 自动更新机制
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class.getName(),
        PropertySourcesProcessor.class);
    // 注册 ApolloAnnotationProcessor 到 BeanDefinitionRegistry 中,解析 @ApolloConfig 和 @ApolloConfigChangeListener 注解。
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
        ApolloAnnotationProcessor.class);
    // 注册 SpringValueProcessor 到 BeanDefinitionRegistry 中,用于 PlaceHolder 自动更新机制
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(),
        SpringValueProcessor.class);
    //【差异】注册 SpringValueDefinitionProcessor 到 BeanDefinitionRegistry 中,因为可能存在 XML 配置的 Bean ,用于 PlaceHolder 自动更新机制
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class.getName(),
        SpringValueDefinitionProcessor.class);
    // 注册 ApolloJsonValueProcessor 到 BeanDefinitionRegistry 中,解析 @ApolloJsonValue 注解。
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(),
        ApolloJsonValueProcessor.class);
  }

如上面代码所示,ApolloConfigRegistrar实现了ImportBeanDefinitionRegistrar接口,前面有提到过,ImportBeanDefinitionRegistrar接口的实现类作用于在Spring解析Bean配置生成BeanDefinition对象阶段,在Spring解析Configuration注解时,向Spring容器中增加额外的BeanDefinition。

ApolloConfigRegistrar中注册了几个BeanDefinition,具体如下:
     1、PropertySourcesPlaceholderConfigurer -------->BeanFactoryPostProcessor
     2、PropertySourcesProcessor -------->BeanFactoryPostProcessor
     3、ApolloAnnotationProcessor -------->BeanPostProcessor
     4、SpringValueProcessor -------->BeanFactoryPostProcessor和BeanPostProcessor
     5、SpringValueDefinitionProcessor-------->BeanDefinitionRegistryPostProcessor(即BeanFactoryPostProcessor)
     6、ApolloJsonValueProcessor -------->BeanPostProcessor

这些类要么实现了BeanFactoryPostProcessor接口,要么实现了BeanPostProcessor接口,前面有提到过BeanFactoryPostProcessor和BeanPostProcessor是Spring提供的扩展机制,BeanFactoryPostProcessor一定是在BeanPostProcessor之前执行。

接下来我们就来看一下这些自定义的BeanFactoryPostProcessor和BeanPostProcessor的执行顺序,以及它们具体做了什么事情。

自定义BeanFactoryPostProcessor
   1、SpringValueDefinitionProcessor

      对所有的BeanDefinition进行遍历,將属性中包含${…}参数占位符的属性添加到Apollo 属性注册表。Apollo 属性注册表具体结构如下:

  2、PropertySourcesProcessor
    (1)根据命名空间从配置中心获取配置信息,创建RemoteConfigRepository和LocalFileConfigRepository对象。RemoteConfigRepository表示远程配置中心资源,LocalFileConfigRepository表示本地缓存配置资源。

   (2)LocalFileConfigRepository对象缓存配置信息到C:\opt\data 或者/opt/data目录。

   (3)RemoteConfigRepository开启HTTP长轮询请求定时任务,默认2s请求一次。

   (4)將本地缓存配置信息转换为PropertySource对象(Apollo自定义了Spring的PropertySource),加载到Spring的Environment对象中。

   (5)將自定义的ConfigPropertySource注册为观察者。一旦RemoteConfigRepository发现远程配置中心信息发生变化,ConfigPropertySource对象会得到通知。

3、PropertySourcesPlaceholderConfigurer
   加载本地Properties文件,將${…}参数占位符替换为具体的值。

4、SpringValueProcessor
   仅仅是为了获取SpringValueDefinitionProcessor中获取的 包含${…}参数占位符的BeanDefinition。(从面向对象设计原则的角度,不符合单一责任原则,可以注册到Guice容器里,然后从Guice容器获取。)

自定义BeanPostProcessor
5、ApolloJsonValueProcessor
   处理ApolloJsonValue注解,属性或者方法中包含ApolloJsonValue注解的Bean,属性值也会根据配置中心配置的修改发生变化,因此也需要添加到配置中心可配的容器中

6、ApolloAnnotationProcessor
   处理ApolloConfigChangeListener注解,ApolloConfigChangeListener注解用于注册一个配置变化监听器。

7、SpringValueProcessor
    处理Spring中的Value注解,將属性或者方法中包含Value注解的Bean信息添加到Apollo属性注册表。

长轮询

8、在 RemoteConfigRepository  类的构造函数初始化,方法如下:

 ## RemoteConfigRepository 的方法

private void scheduleLongPollingRefresh() {
    remoteConfigLongPollService.submit(m_namespace, this);
  }


## 进入 RemoteConfigLongPollService 的submit 方法

  public boolean submit(String namespace, RemoteConfigRepository remoteConfigRepository) {
    // 添加到 m_longPollNamespaces 中
    boolean added = m_longPollNamespaces.put(namespace, remoteConfigRepository);
    // 添加到 m_notifications 中
    m_notifications.putIfAbsent(namespace, INIT_NOTIFICATION_ID);
    // 若未启动长轮询定时任务,进行启动
    if (!m_longPollStarted.get()) {
      startLongPolling();
    }
    return added;
  }

整个过程如下图所示:

2.4 、springboot集成Apollo加载分析

      springboot 启动后,会加载 ApolloApplicationContextInitializer 这个类,进而调用 initialize()方法,源码位于 apollo-client ,调用过程如下所示:

  /**
   * springboot 加载 Apollo 的入口
   * @param context
   */
  @Override
  public void initialize(ConfigurableApplicationContext context) {
    ConfigurableEnvironment environment = context.getEnvironment();

    String enabled = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, "false");
    if (!Boolean.valueOf(enabled)) {
      logger.debug("Apollo bootstrap config is not enabled for context {}, see property: ${
   
   {}}", context, PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
      return;
    }
    logger.debug("Apollo bootstrap config is enabled for context {}", context);

    initialize(environment);
  }


  /**
   * Initialize Apollo Configurations Just after environment is ready.
   *
   * @param environment
   */
  protected void initialize(ConfigurableEnvironment environment) {

    if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
      //already initialized
      return;
    }

    String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);
    logger.debug("Apollo bootstrap namespaces: {}", namespaces);
    List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);

    CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
    for (String namespace : namespaceList) {
      // 获取配置
      Config config = ConfigService.getConfig(namespace);

      composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
    }

    environment.getPropertySources().addFirst(composite);
  }

这里注意  Config config = ConfigService.getConfig(namespace); 代码

1.调用ConfigService.getConfig

/**
   * Get the config instance for the namespace. 1.调用ConfigService.getService
   *
   * @param namespace the namespace of the config
   * @return config instance
   */
  public static Config getConfig(String namespace) {
    return s_instance.getManager().getConfig(namespace);
  }

2.   s_instance.getManager() 指向的 ConfigManager 接口,由 DefaultConfigManager.getConfig()实现:

  /**
   * 2.DefaultConfigManager.getConfig
   * @param namespace the namespace
   * @return
   */
  @Override
  public Config getConfig(String namespace) {
    Config config = m_configs.get(namespace);

    if (config == null) {
      synchronized (this) {
        config = m_configs.get(namespace);

        if (config == null) {
          ConfigFactory factory = m_factoryManager.getFactory(namespace);

          config = factory.create(namespace);
          m_configs.put(namespace, config);
        }
      }
    }

    return config;
  }

3.factory.create(namespace) 的 factory 指向 ConfigFactory 接口,由 DefaultConfigFactory.create(String namespace) 来实现:

 /**
   * 3.DefaultConfigFactory.create(String namespace)
   * @param namespace the namespace
   * @return
   */
  @Override
  public Config create(String namespace) {
    ConfigFileFormat format = determineFileFormat(namespace);
    if (ConfigFileFormat.isPropertiesCompatible(format)) {
      return new DefaultConfig(namespace, createPropertiesCompatibleFileConfigRepository(namespace, format));
    }
    // 4.createLocalConfigRepository-->new LocalFileConfigRepository(namespace, createRemoteConfigRepository(namespace));
    return new DefaultConfig(namespace, createLocalConfigRepository(namespace));
  }

4.createLocalConfigRepository-->new LocalFileConfigRepository(namespace, createRemoteConfigRepository(namespace)); 这里有两种构造函数,具体根据场景执行。

 /**
   * 4.createLocalConfigRepository-->new LocalFileConfigRepository(namespace, createRemoteConfigRepository(namespace));
   * @param namespace
   * @return
   */
  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);
    }
    //5.调用 LocalFileConfigRepository的构造方法 --> RemoteConfigRepository
    return new LocalFileConfigRepository(namespace, createRemoteConfigRepository(namespace));
  }

//============================================================================

 public LocalFileConfigRepository(String namespace) {
    this(namespace, null);
  }

// ==========================================================================

  public LocalFileConfigRepository(String namespace, ConfigRepository upstream) {
    m_namespace = namespace;
    m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
    this.setLocalCacheDir(findLocalCacheDir(), false);
    //
    this.setUpstreamRepository(upstream);
    // 尝试同步
    this.trySync();
  }

5.这时,看到 createRemoteConfigRepository(namespace),调用 LocalFileConfigRepository的构造方法 --> RemoteConfigRepository

 // DefaultConfigFactory 类
 RemoteConfigRepository createRemoteConfigRepository(String namespace) {
    return new RemoteConfigRepository(namespace);
  }

6.调用RemoteConfigRepository构造方法,执行 同步操作及 定时操作

 /**
   * Constructor.  RemoteConfigRepository 类
   *
   * @param namespace the namespace
   */
  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();
    this.trySync(); // 尝试同步配置
    this.schedulePeriodicRefresh(); // 初始化定时刷新配置的任务
    //长轮询的开启入口
    this.scheduleLongPollingRefresh(); // 注册自己到 RemoteConfigLongPollService 中,实现配置更新的实时通知
  }

   最后,开始 长轮询 的执行方式。


总结:
     Apollo配置中心动态生效机制,是基于Http长轮询请求和Spring扩展机制实现的,在Spring容器启动过程中,Apollo通过自定义的BeanPostProcessor和BeanFactoryPostProcessor將参数中包含${…}占位符和@Value注解的Bean注册到Apollo框架中定义的注册表中。然后通过Http长轮询不断的去获取服务端的配置信息,一旦配置发生变化,Apollo会根据变化的配置的Key找到对应的Bean,然后修改Bean的属性,从而实现了配置动态生效的特性。

需要注意的是,Apollo在配置变化后,只能修改Bean的属性,例如我们数据源的属性发生变化,新创建的Connection对象是没问题的,但是连接池中已经创建的Connection对象相关信息是不能动态修改的,所以依然需要重启应用

三、感受

  结论讲得蛮好的,特此摘录下来,作为笔记用。。。。。。同时,自己dugger进行调试。。。。。

https://blog.csdn.net/jack_shuai/article/details/100576523?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~top_click~default-1-100576523.nonecase&utm_term=apollo%E9%85%8D%E7%BD%AE%E5%AE%9E%E6%97%B6%E6%9B%B4%E6%96%B0%E5%8E%9F%E7%90%86&spm=1000.2123.3001.4430

https://blog.csdn.net/fedorafrog/article/details/103919805?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~sobaiduend~default-3-103919805.nonecase&utm_term=apollo%E9%85%8D%E7%BD%AE%E5%AE%9E%E6%97%B6%E6%9B%B4%E6%96%B0%E5%8E%9F%E7%90%86&spm=1000.2123.3001.4430

https://my.oschina.net/u/4277049/blog/3638370

猜你喜欢

转载自blog.csdn.net/baidu_28068985/article/details/109740937