Apollo客户端使用与配置解析

前言:

本文主要是分享Apollo Client客户端使用过程中,遇到的问题、解决问题及分析代码逻辑的过程。其中一个重要问题就是关于apollo.bootstrap.enabled = true的使用及注意事项。

一、准备工作

1.1 环境要求

本文是基于Apollo v1.1.1版本,springboot项目客户端引入的是:

 <dependency>

    <groupId>com.ctrip.framework.apollo</groupId>

    <artifactId>apollo-client</artifactId>

    <version>1.1.0</version>

</dependency>

<dependency>

    <groupId>com.ctrip.framework.apollo</groupId>

    <artifactId>apollo-core</artifactId>

    <version>1.1.0</version>

</dependency>

 

1.2 必选设置

Apollo客户端依赖于AppId(项目ID),Apollo Meta Server(配置中心Eureka地址)

1.2.1 AppId

项目application.properties文件内容:

app.id=demo-test

apollo.bootstrap.enabled = true

 

注:app.id是用来标识应用身份的唯一id,格式为string。

apollo.bootstrap.enabled官方解释为注入默认application namespace的配置示例

 

1.2.2 Apollo Meta Server

 apollo.meta(配置中心Eureka地址) 配置如下:

            Apollo默认会读取系统上/opt/settings/server.properties(linux)或

C:\ opt \settings\server.properties(windows)文件(手动新建目录与文件)

apollo.meta=http://localhost:8089       

1.3 配置中心添加配置

 

1.4 springboot项目客户端代码

启动类:

package com.test.apollodemo;

 

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.CommandLineRunner;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.context.annotation.ComponentScan;

import org.springframework.stereotype.Component;

import com.test.apollodemo.configbean.MysqlConfig2;

import com.test.apollodemo.configbean.MysqlConfigBean;

 

 

@Component

@ComponentScan("com.test")

@SpringBootApplication

public class SpringBootConsoleApplication implements CommandLineRunner {

       @Autowired

       MysqlConfig2 mysqlConfig2;

      

       public static void main(String[] args) throws Exception {

              SpringApplication.run(SpringBootConsoleApplication.class, args);

       }

      

       @Override

       public void run(String... args) throws Exception {

 

              while(true) {

                     System.out.println("+++++++++++++++++++++++++");

                     System.out.println(mysqlConfig2.getMysqlConfigBean().getUrl());

                     System.out.println(mysqlConfig2.getMysqlConfigBean().getDes());

                     Thread.sleep(3000);

              }     

       }

}

实体类:

package com.test.apollodemo.configbean;

 

import org.springframework.beans.factory.annotation.Value;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import com.ctrip.framework.apollo.model.ConfigChange;

import com.ctrip.framework.apollo.model.ConfigChangeEvent;

import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;

import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;

@Configuration

@EnableApolloConfig("mysqlcfg")

public class MysqlConfig2 {

      

       @Bean

         public MysqlConfigBean getMysqlConfigBean() {

           return new MysqlConfigBean();

         }

      

       public class MysqlConfigBean{

           @Value("${url:}")

              private String url;

           @Value("${username:}")

           private String username;

           @Value("${password:}")

           private String password;

           @Value("${des:我是默认值}")

           private String des;

          

           public String getDes() {

                     return des;

              }

              public void setDes(String des) {

                     this.des = des;

              }

              public String getUrl() {

                     return url;

              }

              public void setUrl(String url) {

                     this.url = url;

              }

              public String getUsername() {

                     return username;

              }

              public void setUsername(String username) {

                     this.username = username;

              }

              public String getPassword() {

                     return password;

              }

              public void setPassword(String password) {

                     this.password = password;

              }

       }     

       /**

     * @ApolloConfigChangeListener用来自动注册ConfigChangeListener

     */

    @ApolloConfigChangeListener("mysqlcfg")

    private void someOnChange(ConfigChangeEvent changeEvent) {

    for(String changeKey:changeEvent.changedKeys()) {

            ConfigChange change = changeEvent.getChange(changeKey);

            System.out.println(String.format("%%%%%%%%%%Found datasource change - key: %s, oldValue: %s, newValue: %s, changeType: %s", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType()));

    }  

   }

}

1.5 springboot项目执行结果

 

1.6 java项目案例

1.6.1 代码案例

package com.demo.apollo;

 

import java.io.IOException;

import com.ctrip.framework.apollo.Config;

import com.ctrip.framework.apollo.ConfigChangeListener;

import com.ctrip.framework.apollo.ConfigService;

import com.ctrip.framework.apollo.model.ConfigChange;

import com.ctrip.framework.apollo.model.ConfigChangeEvent;

 

public class Apollo {

    public static void main(String[] args) throws InterruptedException, IOException {

//src目录下新建:META-INF\app.properties,

//app.id配置如下:app.id=demo-test3//项目ID(唯一)

        Config config = ConfigService.getConfig("mysqlcfg");

 

        String someKey = "url";

        String someDefaultValue = "我是默认值";

        String value = config.getProperty(someKey, someDefaultValue);

        System.out.println(value);

       

       

        config.addChangeListener(new ConfigChangeListener() {      

            @Override

            public void onChange(ConfigChangeEvent changeEvent) {

                // TODO Auto-generated method stub

                System.out.println("Changes for namespace " + changeEvent.getNamespace());

                for (String key : changeEvent.changedKeys()) {

                    ConfigChange change = changeEvent.getChange(key);

                    System.out.println(String.format("Found change - key: %s, oldValue: %s, newValue: %s, changeType: %s", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType()));

                }

            }

        });

        while(true) {

            System.out.println(config.getProperty(someKey, someDefaultValue));

            Thread.sleep(3000);

        }

    }

}

1.6.2 依赖引入

Apollo客户端核心jar包:

apollo-client-1.1.0.jar

apollo-core-1.1.0.jar

只导入核心Jar包报错如下:

Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class com.ctrip.framework.apollo.tracer.Tracer

      at com.ctrip.framework.apollo.build.ApolloInjector.getInstance(ApolloInjector.java:37)

      at com.ctrip.framework.apollo.ConfigService.getManager(ConfigService.java:25)

      at com.ctrip.framework.apollo.ConfigService.getConfig(ConfigService.java:61)

 

核心jar包依赖包:

aopalliance-1.0.jar

gson-2.8.5.jar

guava-26.0-jre.jar

guice-4.2.1.jar

javax.inject-1.jar

log4j-api-2.10.0.jar

log4j-core-2.10.0.jar

log4j-slf4j-impl-2.10.0.jar

slf4j-api-1.7.25.jar

1.6.3 日志

引入log4j2.xml日志文件:

<?xml version="1.0" encoding="utf-8"?>

<configuration>

    <properties>

        <!-- 文件输出格式 -->

        <property name="PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} |-%-5level [%T-%thread] %c [%L] -| %msg%n</property>

    </properties>

    <appenders>

        <Console name="CONSOLE" target="system_out">

            <PatternLayout pattern="${PATTERN}" />

        </Console>

    </appenders>

    <loggers>

        <logger name="com.apollo.demo" level="debug" />

        <root level="warn">

            <appenderref ref="CONSOLE" />

        </root>

    </loggers>

</configuration> 

 

 

 

二、引入问题

 本来认为执行结果是:

+++++++++++++++++++++++++++++++++++++++++

localhost
application-des 

为什么会出现这个情况?(注意:mysqlcfg不管是关联还是私用,des的值本来应该为mysqlcfg-des)

下面将分析出现这个结果的原因及解决办法。

三、代码分析

3.1 apollo-client源码分析

3.3.1 第一步-切入点

 首先分析,客户端肯定从服务端,获取了application 和mysqlcfg的配置项,所以从 客户端请求url组装进行分析:

类RemoteConfigLongPollService:

  String assembleLongPollRefreshUrl(String uri, String appId, String cluster, String dataCenter,  Map<String, Long> notificationsMap) {

    Map<String, String> queryParams = Maps.newHashMap();

    queryParams.put("appId", queryParamEscaper.escape(appId));

    queryParams.put("cluster", queryParamEscaper.escape(cluster));

   //下面这段就是告诉服务器,我要获取application和mysqlcfg的配置项

 queryParams

        .put("notifications",queryParamEscaper.escape(assembleNotifications(notificationsMap)));

 

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

    }

 

    String params = MAP_JOINER.join(queryParams);

    if (!uri.endsWith("/")) {

      uri += "/";

    }

    return uri + "notifications/v2?" + params;

  }

方法doLongPollingRefresh会调用:

url =assembleLongPollRefreshUrl(lastServiceDto.getHomepageUrl(), appId, cluster, dataCenter, m_notifications);

 

doLongPollingRefresh到startLongPolling到submit

3.3.2 第二步-反向追溯

1.类 RemoteConfigRepository:

  private void scheduleLongPollingRefresh() {

    remoteConfigLongPollService.submit(m_namespace, this);

  }

然后:

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

  }

2.继续追溯类DefaultConfigFactory:

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

  }

 

  RemoteConfigRepository createRemoteConfigRepository(String namespace) {

    return new RemoteConfigRepository(namespace);

  }

然后到:

  @Override

  public Config create(String namespace) {

    DefaultConfig defaultConfig =

        new DefaultConfig(namespace, createLocalConfigRepository(namespace));

    return defaultConfig;

  }

3. 类DefaultConfigManager:

  @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;

  }

 

4. 类ConfigService:

  public static Config getConfig(String namespace) {

    return s_instance.getManager().getConfig(namespace);

  }

 

5. 类ApolloApplicationContextInitializer:

 

  @Override

  public void initialize(ConfigurableApplicationContext context) {

    ConfigurableEnvironment environment = context.getEnvironment();

 

    initializeSystemProperty(environment);

 

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

 

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

  }

6. 接口PropertySourcesConstants:

package com.ctrip.framework.apollo.spring.config;

 

public interface PropertySourcesConstants {

  String APOLLO_PROPERTY_SOURCE_NAME = "ApolloPropertySources";

  String APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME = "ApolloBootstrapPropertySources";

  String APOLLO_BOOTSTRAP_ENABLED = "apollo.bootstrap.enabled";

  String APOLLO_BOOTSTRAP_NAMESPACES = "apollo.bootstrap.namespaces";

}

最终找出原因是跟 APOLLO_BOOTSTRAP_NAMESPACES有关

3.2 总结

3.2.1 问题解决

最终找出原因:如果配置文件没有配置apollo.bootstrap.namespaces时,系统默认namespaces为application,所以客户端会去请求application配置项。

String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);

所以在application.properties文件中添加:

apollo.bootstrap.namespaces = mysqlcfg,保证客户端不会去获取application配置项,这样结果输出正常。

 

 

 

 

 

 

 

 

 

 

3.2.2逻辑流程图

  

四、其他

关于apollo.meta参数配置优先级

 (1) JVM system property 'apollo.meta',

 (2) OS env variable 'APOLLO_META'

 (3) property 'apollo.meta' from server.properties

 (4) property 'apollo.meta' from app.properties

一般项目常用(3)的方式,配置apollo.meta。

 

 

本文纯属个人观点

猜你喜欢

转载自www.cnblogs.com/haonan-fabric/p/10131742.html
今日推荐