Skip to content

一、概述

Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用的不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。

Apollo包括服务端和客户端两部分:

服务端基于Spring Boot和Spring Cloud开发,打包后可以直接运行,不需要额外安装Tomcat等应用容器。

Java客户端不依赖任何框架,能够运行于所有Java运行时环境,同时对Spring/Spring Boot环境也有较好的支持。

1.1 特性

  • 统一管理不同环境、不同集群的配置
    • Apollo提供了一个统一界面集中式管理不同环境(environment)、不同集群(cluster)、不同命名空 间(namespace)的配置。
    • 同一份代码部署在不同的集群,可以有不同的配置,比如zookeeper的地址等
    • 通过命名空间(namespace)可以很方便地支持多个不同应用共享同一份配置,同时还允许应用对共享 的配置进行覆盖
  • 配置修改实时生效(热发布)
    • 用户在Apollo修改完配置并发布后,客户端能实时(1秒)接收到最新的配置,并通知到应用程序
  • 版本发布管理
    • 所有的配置发布都有版本概念,从而可以方便地支持配置的回滚
  • 灰度发布
    • 支持配置的灰度发布,比如点了发布后,只对部分应用实例生效,等观察一段时间没问题后再推给所有 应用实例
  • 权限管理、发布审核、操作审计
    • 应用和配置的管理都有完善的权限管理机制,对配置的管理还分为了编辑和发布两个环节,从而减少人 为的错误。
    • 所有的操作都有审计日志,可以方便地追踪问题
  • 客户端配置信息监控
    • 可以在界面上方便地看到配置在被哪些实例使用
  • 提供Java和.Net原生客户端
    • 提供了Java和.Net的原生客户端,方便应用集成
    • 支持Spring Placeholder, Annotation和Spring Boot的ConfigurationProperties,方便应用使用(需要 Spring 3.1.1+)
    • 同时提供了Http接口,非Java和.Net应用也可以方便地使用
  • 提供开放平台API
    • Apollo自身提供了比较完善的统一配置管理界面,支持多环境、多数据中心配置管理、权限、流程治理 等特性。不过Apollo出于通用性考虑,不会对配置的修改做过多限制,只要符合基本的格式就能保存, 不会针对不同的配置值进行针对性的校验,如数据库用户名、密码,Redis服务地址等
    • 对于这类应用配置,Apollo支持应用方通过开放平台API在Apollo进行配置的修改和发布,并且具备完善 的授权和权限控制

1.2 操作流程

1、在Apollo配置中心修改配置

2、应用程序通过Apollo客户端从配置中心拉取配置信息

用户通过Apollo配置中心修改或发布配置后,会有两种机制来保证应用程序来获取最新配置:一种是Apollo配置中 心会向客户端推送最新的配置;另外一种是Apollo客户端会定时从Apollo配置中心拉取最新的配置,通过以上两种 机制共同来保证应用程序能及时获取到配置。

  1. 客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送

  2. 客户端还会定时从Apollo配置中心服务端拉取应用的最新配置。 这是一个fallback机制,为了防止推送机制失效导致配置不更新

客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified 定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property: apollo.refreshInterval 来覆盖,单位为分钟。

  1. 客户端从Apollo配置中心服务端获取到应用的最新配置后,会保存在内存中

  2. 客户端会把从服务端获取到的配置在本地文件系统缓存一份 在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置

  3. 应用程序从Apollo客户端获取最新的配置、订阅配置更新通知

1.3 架构模块

img

Config Service提供配置的读取、推送等功能,服务对象是Apollo客户端

Admin Service提供配置的修改、发布等功能,服务对象是Apollo Portal(管理界面)

Eureka提供服务注册和发现,为了简单起见,目前Eureka在部署时和Config Service是在一个JVM进程中的

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进程中

1.4 执行流程

  1. Apollo启动后,Config/Admin Service会自动注册到Eureka服务注册中心,并定期发送保活心跳。
  2. Apollo Client和Portal管理端通过配置的Meta Server的域名地址经由Software Load Balancer(软件负载均衡 器)进行负载均衡后分配到某一个Meta Server
  3. Meta Server从Eureka获取Config Service和Admin Service的服务信息,相当于是一个Eureka Client
  4. Meta Server获取Config Service和Admin Service(IP+Port)失败后会进行重试
  5. 获取到正确的Config Service和Admin Service的服务信息后,Apollo Client通过Config Service为应用提供配 置获取、实时更新等功能;Apollo Portal管理端通过Admin Service提供配置新增、修改、发布等功能

1.5 核心概念

  1. application (应用)

    实际使用配置的应用,Apollo客户端在运行时需要知道当前应用是谁,从而可以去获取 对应的配置 关键字:appId

  2. environment (环境)

    配置对应的环境,Apollo客户端在运行时需要知道当前应用处于哪个环境,从而可以去获取应用的配置 关键字:env

  3. cluster (集群)

    一个应用下不同实例的分组,比如典型的可以按照数据中心分,把上海机房的应用实例分为一个集群,把北京机房的应用实例分为另一个集群。 关键字:cluster

  4. namespace (命名空间)

    一个应用下不同配置的分组,可以简单地把namespace类比为文件,不同类型的配置存放在不同的文件中, 如数据库配置文件,RPC配置文件,应用自身的配置文件等 关键字:namespaces

    D00D72FC-A1D5-4ec6-8C87-ADDB30DFA0CB

1.6 部署方案

Apollo-adminservice和Apollo-confifigservice两个服务分别在线上环境(pro),仿真环境(uat)和开发环境(dev)各部署一套,Apollo-portal做为管理端只部署一套,统一管理上述三套环境。

数据库方面,每添加一套环境就需要部署一套ApolloConfgService和ApolloAdminService,修改Eureka地址。

调整ApolloPortal服务配置,在管理员工具-》系统参数进行apollo.portal.envs 配置可支持的环境列表。

二、使用demo

2.1 与springboot集成

依赖

xml
<dependency> 
    <groupId>com.ctrip.framework.apollo</groupId> 
    <artifactId>apollo‐client</artifactId> 
    <version>1.1.0</version> 
</dependency>

配置

application.properties/bootstrap.properties

properties
# 项目id


<NolebasePageProperties />




app.id=account‐service
# 开启apollo.bootstrap
apollo.bootstrap.enabled = true 
# 指定namespace,多个使用,号隔开
apollo.bootstrap.namespaces = application,test

从1.2.0版本开始,如果希望把日志相关的配置(如 logging.level.root=info 或 logbackspring.xml 中的参数)也放在Apollo管理,那么可以额外配置apollo.bootstrap.eagerLoad.enabled=true 来使Apollo的加载顺序放到日志系统加载之前,不过这会导致Apollo的启动过程无法通过日志的方式输出(因为执行Apollo加载的时候,日志系统压根没有准备好呢!所以在Apollo代码中使用Slf4j的日志输出便没有任何内容)

meta连接地址

meta连接地址配置,可通过system properties配置 或添加env配置文件

sh
 -Dapollo.meta=http://localhost:8080
properties
# apollo-env.properties 结合指定环境
dev.meta=http://localhost:8080 
pro.meta=http://localhost:8081

配置文件缓存

Apollo客户端会把从服务端获取到的配置在本地文件系统缓存一份,用于在遇到服务不可用,或网络不通的 时候,依然能从本地恢复配置,不影响应用正常运行。本地配置文件会以下面的文件名格式放置于配置的本 地缓存路径下:{appId}+{cluster}+{namespace}.properties

sh
# 修改缓存路径
‐Dapollo.cacheDir=/opt/data/apollo‐config

Environment

sh
# 通过Java System Property的env来指定环境: 
-Denv=DEV

Cluster(集群)

sh
# 通过Java System Property的apollo.cluste来指定集群: 
-Dapollo.cluster=DEFAULT

启动类添加配置

java
@EnableApolloConfig

2.2 获取配置信息

添加依赖

xml
<dependency> 
    <groupId>com.ctrip.framework.apollo</groupId> 
    <artifactId>apollo‐client</artifactId> 
    <version>1.1.0</version> 
</dependency>

获取配置demo

java
//        Config appConfig = ConfigService.getAppConfig(); //读取默认namespace下的配置信息
Config appConfig = ConfigService.getConfig("spring-rocketmq");//读取指定 namespace下的配置信息
while(true){
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //获取配置信息,第一个参数:配置的key,第二个参数:默认值
    String value = appConfig.getProperty("rocketmq.name-server", null);
    System.out.printf("now: %s, rocketmq.name-server: %s%n", LocalDateTime.now().toString(), value);
}

配置属性 VM options

sh
# app.id = 应用名称
# env=环境名称
# apollo.cluster=集群名称
# 环境_meta=meta地址
# apollo.cacheDir 配置缓存地址
# apollo.cluster 集群名称
‐Dapp.id=apollo‐quickstart ‐Denv=DEV ‐Ddev_meta=http://localhost:8080 ‐Dapollo.cacheDir=/opt/data/apollo‐config ‐Dapollo.cluster=DEFAULT

三、配置页面操作

3.1 基础设置

  • 部门

    apollo默认的部门有两个,增加可以在系统参数中进行修改

    右上角管理员工具-》系统参数-》输入organizations查询已存在的部门设置,修改value对部门进行管理。

  • 用户

    apollo默认提供一个超级管理员: apollo。

    添加用户:右上角管理员工具-》用户管理-》编辑用户添加

3.2 管理项目

创建项目

在apollo-portal主页:ip+:8070

创建项目-》输入项目信息-》提交

  • 项目信息

    部门:选择应用所在的部门

    应用AppId:用来标识应用身份的唯一id,格式为string,需要和项目配置文件applications.properties 中配置的app.id对应

    应用名称:应用名,仅用于界面展示

    应用负责人:选择的人默认会成为该项目的管理员,具备项目权限管理、集群创建、Namespace创建等 权限

  • 授权

    在项目首页,可通过右上的授权按钮,给用户授权。包含修改权和发布权

删除项目

击右上角的“管理员工具--》删除应用、集群...”

3.3 配置管理

配置进行编辑后 ,都需要点击发布按钮

添加发布配置项

配置可通过表格模式(新增配置)、文本模式(编辑)新增配置信息

修改和删除配置

找到对应的配置进行修改和删除

添加namespace

Namespace作为配置的分类,可当成一个配置文件。

进入项目首页,点击左下脚的“添加Namespace”,共包括两项:关联公共Namespace(关联类型为public的namespace)和创建Namespace(创建一个namespace,类型选择为public时,可被其他namespace关联)

3.4 集群管理

点击页面左侧的“添加集群”按钮-》输入集群名称,创建集群-》切换到对应集群,修改配置并发布,对该集群下的使用单独的配置。

同步集群配置

同步集群的配置是指在同一个应用中拷贝某个环境下的集群的配置到目标环境下的目标集群。

  1. 从其他集群同步已有配置到新集群

    切换到原有集群

    展开要同步的Namespace,点击同步配置

    选择同步到的新集群,再选择要同步的配置

    同步完成后,切换到同步到的新集群,发布配置

四、配置发布

4.1 原理

流程:

  1. 用户在Portal操作配置发布
  2. Portal调用Admin Service的接口操作发布
  3. Admin Service发布配置后,发送ReleaseMessage给各个Config Service
  4. Config Service收到ReleaseMessage后,通知对应的客户端

4.2 发送ReleaseMessage

Admin Service在配置发布后,需要通知所有的Config Service有配置发布,从而Config Service可以通知对应的客户端来拉取最新的配置。 从概念上来看,这是一个典型的消息使用场景,

Admin Service作为producer(生产者)发出消息,各个Config Service作为consumer(消费者)消费消息。通过一个消息队列组件(Message Queue)就能很好的实现Admin Service和Config Service的解耦。 在实现上,考虑到Apollo的实际使用场景,以及为了尽可能减少外部依赖,没有采用外部的消息中间件,而是 通过数据库实现了一个简单的消息队列。 具体实现方式如下:

  1. Admin Service在配置发布后会往ReleaseMessage表插入一条消息记录,消息内容就是配置发布的 AppId+Cluster+Namespace

    sql
    SELECT * FROM ApolloConfigDB.ReleaseMessage

    消息发送类:DatabaseMessageSende

  2. Confifig Service有一个线程会每秒扫描一次ReleaseMessage表,看看是否有新的消息记录

    消息扫描类:ReleaseMessageScanner

  3. Confifig Service如果发现有新的消息记录,那么就会通知到所有的消息监听器

    后调用消息监听类的handleMessage方法:NotifificationControllerV2

  4. NotifificationControllerV2得到配置发布的AppId+Cluster+Namespace后,会通知对应的客户端

4.3 Config Service通知客户端

  1. 客户端会发起一个Http请求到Confifig Service的 notifications/v2 接口NotifificationControllerV2

    客户端发送请求类:RemoteConfifigLongPollService

  2. NotifificationControllerV2不会立即返回结果,而是把请求挂起。考虑到会有数万客户端向服务端发起长连,

    因此在服务端使用了async servlet(Spring DeferredResult)来服务Http Long Polling请求。

  3. 如果在60秒内没有该客户端关心的配置发布,那么会返回Http状态码304给客户端。

  4. 如果有该客户端关心的配置发布,NotifificationControllerV2会调用DeferredResult的setResult方法,传入有配置变化的namespace信息,同时该请求会立即返回。客户端从返回的结果中获取到配置变化的namespace后,会立即请求Confifig Service获取该namespace的最新配置。

4.4 客户端读取设计

客户端还会定时从 Apollo配置中心服务端拉取应用的最新配置。

  • 这是一个备用机制,为了防止推送机制失效导致配置不更新
  • 客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified
  • 定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property: apollo.refreshInterval 来覆盖,单位为分钟

五、灰度发布

灰度发布是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用 产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁 移到B上面来。

  1. 对于一些对程序有比较大影响的配置,可以先在一个或者多个实例生效,观察一段时间没问题后再全量发布 配置。
  2. 对于一些需要调优的配置参数,可以通过灰度发布功能来实现A/B测试。可以在不同的机器上应用不同的配 置,不断调整、测评一段时间后找出较优的配置再全量发布配置。

5.1 步骤

项目页面右上角创建灰度标签-》点击主版本配置,添加灰度测试的值,提交-》点击灰度规则标签,配置灰度规则(根据ip进行选择)-》没有问题后,点击灰度发布-》切换到 灰度实例列表 Tab,就能看到已经使用了灰度发布值的ip

如果灰度的配置测试下来比较理想,符合预期,那么就可以操作全量发布 。 不理想或者不需要了,可以点击 放弃灰度

5.2 历史记录

在发布历史按钮中可以查看当前namespace的主版本及灰度版本的发布历史。

六、安装

6.1 常规安装

  • 下载安装包 下载地址

  • 解压后获取apollo-confifigservice-1.3.0.jar, apollo-adminservice-1.3.0.jar, apollo-portal-1.3.0.jar

  • 准备数据库

    Apollo服务端共需要两个数据库: ApolloPortalDB 和 ApolloConfigDB ,ApolloPortalDB只需要在生产环境部署 一个即可,而ApolloConfigDB需要在每个环境部署一套。

    • 创建ApolloPortalDB,sql脚本下载,执行后 通过sql 验证

      sql
      select `Id`, `Key`, `Value`, `Comment` from `ApolloPortalDB`.`ServerConfig` limit 1;
    • 创建ApolloConfifigDB,sql脚本下载地址,通过sql验证

      sql
      select `Id`, `Key`, `Value`, `Comment` from `ApolloConfigDB`.`ServerConfig` limit 1;
  • 启动

    Apollo默认会启动3个服务,分别使用8070, 8080, 8090端口,请确保这3个端口当前没有被使用

    sh
    # 可通过-Dserver.port=8080修改默认端口
    # 启动apollo-configservice
    java ‐Xms256m ‐Xmx256m ‐Dspring.datasource.url=jdbc:mysql://localhost:3306/ApolloConfigDB? characterEncoding=utf8‐Dspring.datasource.username=root ‐ Dspring.datasource.password=itcast0430 ‐jar apollo‐configservice‐1.3.0.jar
    
    #  启动apollo-adminservice
    java ‐Xms256m ‐Xmx256m ‐Dspring.datasource.url=jdbc:mysql://localhost:3306/ApolloConfigDB? characterEncoding=utf8‐Dspring.datasource.username=root ‐ Dspring.datasource.password=itcast0430 ‐jar apollo‐adminservice‐1.3.0.jar
    
    # 启动apollo-portal
    java ‐Xms256m ‐Xmx256m ‐Ddev_meta=http://localhost:8080/ ‐Dserver.port=8070 ‐Dspring.datasource.url=jdbc:mysql://localhost:3306/ApolloPortalDB?characterEncoding=utf8 ‐ Dspring.datasource.username=root‐Dspring.datasource.password=itcast0430 ‐jar apollo‐ portal‐1.3.0.jar

    也可以使用提供的runApollo.bat快速启动三个服务(修改数据库连接地址,数据库以及密码)

    bat
    echo 
    set url="localhost:3306" 
    set username="root" 
    set password="123" 
    start "configService" java ‐Xms256m ‐Xmx256m ‐Dapollo_profile=github ‐ Dspring.datasource.url=jdbc:mysql://%url%/ApolloConfigDB?characterEncoding=utf8 ‐ Dspring.datasource.username=%username% ‐Dspring.datasource.password=%password% ‐ Dlogging.file=.\logs\apollo‐configservice.log ‐jar .\apollo‐configservice‐1.3.0.jar start "adminService" java ‐Xms256m ‐Xmx256m ‐Dapollo_profile=github ‐ Dspring.datasource.url=jdbc:mysql://%url%/ApolloConfigDB?characterEncoding=utf8 ‐ Dspring.datasource.username=%username% ‐Dspring.datasource.password=%password% ‐ Dlogging.file=.\logs\apollo‐adminservice.log ‐jar .\apollo‐adminservice‐1.3.0.jar start "ApolloPortal" java ‐Xms256m ‐Xmx256m ‐Dapollo_profile=github,auth ‐ Ddev_meta=http://localhost:8080/ ‐Dserver.port=8070 ‐ Dspring.datasource.url=jdbc:mysql://%url%/ApolloPortalDB?characterEncoding=utf8 ‐ Dspring.datasource.username=%username% ‐Dspring.datasource.password=%password% ‐ Dlogging.file=.\logs\apollo‐portal.log ‐jar .\apollo‐portal‐1.3.0.jar

    连接的Eureka地址修改

    sql
    USE ApolloConfigDBPRO; 
    UPDATE ServerConfig SET `Value` = "http://localhost:8081/eureka/" WHERE `key` = "eureka.service.url";