Apollo使用笔记

Apollo使用笔记

一、简介

Apollo 项目于 2016 年在携程框架研发部诞生,初衷是为了解决公司内部配置管理尤其是中间件公共配置的管理难题,秉持着开源开放的精神,项目从第一行代码开始就在开源社区上开源,可以说是一个完全开放的项目。

1. 官方文档

https://www.apolloconfig.com/#/zh/README

2. 代码仓库

https://gitee.com/apolloconfig

  • Apollo源码

git clone https://gitee.com/apolloconfig/apollo.git

  • Apollo使用示例

git clone https://gitee.com/apolloconfig/apollo-use-cases.git

3. 官方demo环境

二、部署架构

1.系统架构图

系统架构图

  • Config Service提供配置的读取、推送等功能,服务对象是Apollo客户端
  • Admin Service提供配置的修改、发布等功能,服务对象是Apollo Portal(管理界面)
  • Config Service和Admin Service都是多实例、无状态部署,所以需要将自己注册到Eureka中并保持心跳
  • 在Eureka之上我们架了一层Meta Server用于封装Eureka的服务发现接口
  • Client通过域名访问Meta Server获取Config Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Client侧会做load balance、错误重试
  • Portal通过域名访问Meta Server获取Admin Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Portal侧会做load balance、错误重试
  • 为了简化部署,我们实际上会把Config Service、Eureka和Meta Server三个逻辑角色部署在同一个JVM进程中

2. 部署架构图

  • 单机
    单机部署架构
  • 多机器、单环境
    在这里插入图片描述
  • 多机器、双环境
    在这里插入图片描述

3. 快速部署

  • 版本要求 JDK 1.8+ 、MySQL 5.6.5+

docker环境搭建

  • 安装
git clone https://gitclone.com/github.com/apolloconfig/apollo-quick-start.git
cd ./apollo-quick-start
docker-compose -f docker-compose.yml up

Gracefully stopping… (press Ctrl+C again to force)

  • 启动和关闭
docker-compose -f docker-compose.yml start
docker-compose -f docker-compose.yml stop
  • 卸载
docker-compose -f docker-compose.yml down -v
  • 访问
  • http://localhost:8070/
  • User/Password: apollo/admin

三、使用

1. 注解使用

1.1 @Value
    @Value("${base.url}")
    private String url;

Spring 注解,支持热更新 (前提确保apollo.autoUpdateInjectedSpringProperties=true默认true)

1.2 @ApolloJsonValue
keyvalue
student{“name”:“zs”, “age”:30}
    @ApolloJsonValue("${student}")
    private Student student;

Apollo内部注解,支持热更新 (前提确保apollo.autoUpdateInjectedSpringProperties=true默认true)

1.3 @ConfigurationProperties
keyvalue
student.namezs
student.age30
@Data
@Component
@ConfigurationProperties(prefix = "student")
public class Student {
    private String name;
    private Integer age;
}

spring boot注解,不支持热更新,需结合spring cloud注解@RefreshScope和Apollo注解@ApolloConfigChangeListener自行实现更新逻辑。示例如下

  • 方式1@ApolloConfigChangeListener

注意:

  1. 此处@ApolloConfigChangeListener(value = {"student.properties"}, interestedKeyPrefixes = "student.")标注的属性配置类,不要增加@RefreshScope注解;

  2. 务必增加@ApolloConfigChangeListener value属性即namespace,否则onChange方法可能监听不到事件源

@Data
@Component
@ConfigurationProperties(prefix = "student")
public class Student {
    private String name;
    private Integer age;
}
@Configuration
public class CustomPropertiesRefresher implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @ApolloConfigChangeListener(value = {"student.properties"}, interestedKeyPrefixes = "student.")
    public void onChange(ConfigChangeEvent changeEvent) {
        System.out.println(changeEvent);
        /**
         * @see org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#onApplicationEvent
         */
        this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
    }
    
}

  • 方式2 @RefreshScope + @ApolloConfigChangeListener
@Data
@Component
@RefreshScope
@ConfigurationProperties(prefix = "student")
public class Student {
    private String name;
    private Integer age;
}
@Configuration
public class CustomPropertiesRefresher {

    private final RefreshScope refreshScope;

    public CustomPropertiesRefresher(RefreshScope refreshScope) {
        this.refreshScope = refreshScope;
    }

    @ApolloConfigChangeListener(value = {"student.properties"}, interestedKeyPrefixes = "student.")
    public void onChange(ConfigChangeEvent changeEvent) {
        // 刷新spring bean实例(与此前的bean对象不是同一个)
        refreshScope.refresh("student");
    }

}
  • 比较和总结

方式1 采用EnvironmentChangeEvent事件监听机制(观察者模式)扩展性较好;

方式2 采用 @RefreshScope重新初始化bean对象,较重,若仅刷新指定属性配置类,需要讲bean name写死(不推荐);

但,方式2并非无用武之地,比如修改配置属性后,需要重新初始化bean实例(例如redis集群实例)的场景就很适合该方式。

  • e配置监听namespace

@ApolloConfigChangeListener监听的namespace也可在application.yml中配置,多个namespace可使用逗号分割

apollo:
  meta: http://81.68.181.139:8080
  bootstrap:
    enabled: true
    eagerLoad:
      enabled: true
    # will inject 'application' and 'TEST1.apollo' namespaces in bootstrap phase
    namespaces: application,TEST1.apollo,application.yaml

listeners: "application,TEST1.apollo,application.yaml"

@ApolloConfigChangeListener(value = "${listeners}", interestedKeyPrefixes = {"student."})

四、扩展

1. 更改缓存目录位置

Apollo默认会在本地磁盘缓存配置文件,默认缓存目录为/opt/data;你可以通过操作系统的System Environment APOLLO_CACHE_DIR(1.9.0+) 或者 APOLLO_CACHEDIR(1.9.0之前)来指定默认缓存目录位置,也可以在Spring Boot的application.propertiesbootstrap.properties中指定apollo.cache-dir=/opt/data/some-cache-dir(1.9.0+) 或者 apollo.cacheDir=/opt/data/some-cache-dir(1.9.0之前)指定缓存目录位置;

2. 属性值加密

对于数据库密码、appKey等敏感信息仍然存在泄露风险,可以根据需要考虑结合jasypt使用,需要注意的是,使用jasypt加密的属性值热更新会失效,需要自行处理。

  • 对于Mac/Linux,默认文件位置为/opt/data/{appId}
  • 对于Windows,默认文件位置为C:\opt\data\{appId}
              <dependency>
                    <groupId>com.github.ulisesbocchio</groupId>
                    <artifactId>jasypt-spring-boot-starter</artifactId>
                    <version>3.0.4</version>
                </dependency>

3. 访问密钥

Apollo从1.6.0版本开始增加访问密钥机制,从而只有经过身份验证的客户端才能访问敏感配置。如果应用开启了访问密钥,客户端需要配置密钥,否则无法获取配置。

apollo.accesskey.secret=1cf998c4e2ad4704b45a98a509d15719

五、原理

1. 客户端设计架构

client-architecture.png

上图简要描述了Apollo客户端的实现原理:

  1. 客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送。(通过Http Long Polling实现)
  2. 客户端还会定时从Apollo配置中心服务端拉取应用的最新配置。
    • 这是一个fallback机制,为了防止推送机制失效导致配置不更新
    • 客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified
    • 定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property: apollo.refreshInterval来覆盖,单位为分钟。
  3. 客户端从Apollo配置中心服务端获取到应用的最新配置后,会保存在内存中
  4. 客户端会把从服务端获取到的配置在本地文件系统缓存一份
    • 在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置
  5. 应用程序可以从Apollo客户端获取最新的配置、订阅配置更新通知

注意:在本地开发模式下,Apollo不会实时监测文件内容是否有变化,所以如果修改了配置,需要重启应用生效。

2.调用链

=>com\ctrip\framework\apollo\apollo-client\1.4.0\apollo-client-1.4.0.jar!\META-INF\spring.factories

=>com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer#initialize(org.springframework.context.ConfigurableApplicationContext)

=> com.ctrip.framework.apollo.ConfigService#getConfig

=>com.ctrip.framework.apollo.internals.RemoteConfigRepository#RemoteConfigRepository

// 启动时http get请求主动拉取数据
this.trySync();
// 创建单线程线程池定时拉取(频率默认5分钟)
this.schedulePeriodicRefresh();
// 开启长轮询(客户端 默认延迟2秒执行、每秒限流2次、尝试获取令牌5秒超时后再等5秒、get请求超时时间90秒,服务端超时时间要小于90秒,现在是60秒,若超时则等待1~120秒每次乘2即1,2,4,8,16...96,120),长轮询本质也是pull模式,只不过无数据变更时,阻塞直至超时或有新数据产生(保证实时性要求的同时,优化无效查询降低服务端压力,嗯不错的思路有空我也试试)
// com.ctrip.framework.apollo.internals.RemoteConfigLongPollService#startLongPolling
this.scheduleLongPollingRefresh();

配置项 com.ctrip.framework.apollo.util.ConfigUtil

=>com.ctrip.framework.apollo.internals.AbstractConfigRepository#trySync

=>com.ctrip.framework.apollo.internals.AbstractConfigRepository#fireRepositoryChange

=>com.ctrip.framework.apollo.internals.AbstractConfig#fireConfigChange

  • @Value@ApolloJsonValue自动更新

=>com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener#onChange

  @Override
  public void onChange(ConfigChangeEvent changeEvent) {
    Set<String> keys = changeEvent.changedKeys();
    if (CollectionUtils.isEmpty(keys)) {
      return;
    }
    for (String key : keys) {
      // 1. check whether the changed key is relevant
      Collection<SpringValue> targetValues = springValueRegistry.get(beanFactory, key);
      if (targetValues == null || targetValues.isEmpty()) {
        continue;
      }

      // 2. update the value
      for (SpringValue val : targetValues) {
        updateSpringValue(val);
      }
    }
  }

springValueRegistry来源于注解扫描结果com.ctrip.framework.apollo.spring.annotation.ApolloProcessor;该接口有两个实现类分别为

com.ctrip.framework.apollo.spring.annotation.SpringValueProcessorcom.ctrip.framework.apollo.spring.annotation.ApolloJsonValueProcessor分别对应@Value@ApolloJsonValue注解

  • 自定义listener更新ConfigurationProperties

=>com.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor#processMethod

=>com.example.demo.listener.CustomPropertiesRefresher#onChange

	@ApolloConfigChangeListener(value = {"student.properties"}, interestedKeyPrefixes = "student.")    
	public void onChange(ConfigChangeEvent changeEvent) {
        System.out.println(changeEvent);
        /**
         * @see org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#onApplicationEvent
         */
        this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
    }

=>org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#onApplicationEvent

=>org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#rebind(java.lang.String)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬山境KL攻城狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值