1. 概览
1.1 什么是配置
随着程序功能的日益复杂,程序的配置日益增多:各种功能的==开关==、==参数==的配置、服务器的地址等
对程序配置的期望值也越来越高:配置修改后==实时生效==,==灰度发布==,分环境、分集群管理配置,完善的==权限==、==审核机制==等
在这样的大环境下,传统的通过配置文件、数据库等方式已经越来越无法满足开发人员对配置管理的需求。
配置主要有以下几个属性:
-
配置是独立于程序的只读变量
- 配置首先是独立于程序的,同一份程序在不同的配置下会有不同的行为。
- 配置对于程序是只读的,程序通过读取配置来改变自己的行为,但是程序不应该去改变配置。
-
配置伴随应用的整个生命周期
- 配置贯穿于应用的整个生命周期,应用在启动时通过读取配置来初始化,在运行时根据配置调整行为。
-
配置可以有多种加载方式
- 配置也有很多种加载方式,常见的有程序内部hard code,配置文件,环境变量,启动参数,基于数据库,redis等
-
配置需要治理
- 权限控制
- 由于配置能改变程序的行为,不正确的配置甚至能引起灾难,所以对配置的修改必须有比较完善的权限控制
- 不同环境、集群配置管理
- 同一份程序在不同的环境(开发,测试,生产)、不同的集群(如不同的数据中心)经常需要有不同的配置,所以需要有完善的环境、集群配置管理
- 权限控制
1.2 什么是配置中心
1.2.1 单机
1.2.2 多机
1.2.3 微服务化
1.2.4 配置中心
配置中心将配置从应用中剥离出来,统一管理,优雅的解决了配置的动态变更、持久化、运维成本等问题。
应用自身既不需要去添加管理配置接口,也不需要自己去实现配置的持久化。
总得来说,配置中心就是一种统一管理各种应用配置的基础服务组件。
2. Apollo简介
2.1 主流配置中心
- Spring Cloud Config
- Nacos
- Disconf
- Apollo
- ...
2.1.1 功能性能对比
目前只用过Spring Cloud Config和Apollo。
说说Spring Cloud Config感受:
- 依赖git,无配置后台
- 不同环境不同服务都有properties文件
- 修改配置需要发布项目或者手动刷新
因此,当时开发时大家几乎不依赖配置中心。【手动滑稽】
2.2 Apollo简介
Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
服务端基于 Spring Boot 和 Spring Cloud 开发,打包后可以直接运行,不需要额外安装 Tomcat 等应用容器。
Java 客户端不依赖任何框架,能够运行于所有 Java 运行时环境,同时对 Spring/Spring Boot 环境也有额外支持。
2.3 Apollo特性
-
统一管理不同环境、不同集群的配置
- 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进行配置的修改和发布,并且具备完善的授权和权限控制
-
部署简单
- 配置中心作为基础服务,可用性要求非常高,这就要求Apollo对外部依赖尽可能地少
- 目前唯一的外部依赖是MySQL,所以部署非常简单,只要安装好Java和MySQL就可以让Apollo跑起来
- Apollo还提供了打包脚本,一键就可以生成所有需要的安装包,并且支持自定义运行时参数
3. Apollo快速入门
3.1 执行流程
过程:
- 用户在配置中心对配置进行修改并发布
- 配置中心通知Apollo客户端有配置更新
- Apollo客户端从配置中心拉取最新的配置、更新本地配置并通知到应用
3.2 Demo
QuickStart
http://106.54.227.205/signin
- 账号:apollo
- 密码:admin
3.3 日常使用Apollo的姿势
Config config = ConfigService.getAppConfig();
Integer defaultRequestTimeout = 200;
Integer requestTimeout = config.getIntProperty("requestTimeout", defaultRequestTimeout)
4 Apollo架构和核心概念
4.1 Apllo架构
上图简要描述了 Apollo 的总体设计,从下往上看:
Config Service 提供配置的读取、推送等功能,服务对象是Apollo客户端
Admin Service 提供配置的修改、发布等功能,服务对象是Apollo Portal(管理界面)
通过Apollo的发布界面可以多环境、集群管理配置。
Config Service 和 Admin Service 都是多实例、无状态部署,所以需要将自己注册到
Eureka 中并保持心跳,在 Eureka 之上架了一层 Meta Server 用于封装 Eureka 的服务发现接口。Apollo提供了MetaServiceProvider SPI,用户可以注入自己的MetaServiceProvider来自定义Meta Server定位逻辑Client 通过域名访问Meta Server获取Config Service服务列表(IP+Port),而后直接通过IP+Port 访问服务,同时在 Client 侧会做 load balance、错误重试
注:Meta Server从Eureka获取Config Service和Admin Service的服务信息,相当于是一个Eureka Client
4.2 Apollo核心概念
-
application (应用)
- 这个很好理解,就是实际使用配置的应用,Apollo客户端在运行时需要知道当前应用是谁,从而可以去获取对应的配置
- 每个应用都需要有唯一的身份标识 -- appId,我们认为应用身份是跟着代码走的,所以需要在代码中配置。
-
environment (环境)
- 配置对应的环境,Apollo客户端在运行时需要知道当前应用处于哪个环境,从而可以去获取应用的配置
- 我们认为环境和代码无关,同一份代码部署在不同的环境就应该能够获取到不同环境的配置
- 所以环境默认是通过读取机器上的配置(server.properties中的env属性)指定的,不过为了开发方便,我们也支持运行时通过System Property等指定。
-
cluster (集群)
- 一个应用下不同实例的分组,比如典型的可以按照数据中心分,把上海机房的应用实例分为一个集群,把北京机房的应用实例分为另一个集群。
- 对不同的cluster,同一个配置可以有不一样的值,如zookeeper地址。
- 集群默认是通过读取机器上的配置(server.properties中的idc属性)指定的,不过也支持运行时通过System Property指定。
-
namespace (命名空间)
- 一个应用下不同配置的分组,可以简单地把namespace类比为文件,不同类型的配置存放在不同的文件中,如数据库配置文件,RPC配置文件,应用自身的配置文件等
- 应用可以直接读取到公共组件的配置namespace,如DAL,RPC等
- 应用也可以通过继承公共组件的配置namespace来对公共组件的配置做调整,如DAL的初始数据库连接数
4.3 Namespace设计
4.3.1 Namespace概念
Namespace是配置项的集合,类似于一个配置文件的概念。
Apollo在创建项目的时候,都会默认创建一个“application”的Namespace。
4.3.1 Namespace获取权限
Namespace的获取权限分为两种:
- private(私有的)
private权限的Namespace只能被所属的应用获取到。一个应用尝试获取其它应用private的Namespace,Apollo会报“404”异常。
- public(公共的)。
public权限的Namespace,能被任何应用获取到
4.3.2 Namespace类型
有三种
- 私有类型
私有类型的Namespace具有private权限。例如上文提到的“application” Namespace就是私有类型。
- 公共类型
公共类型的Namespace具有public权限。公共类型的Namespace相当于游离于应用之外的配置,且通过Namespace的名称去标识公共Namespace,所以公共的Namespace的名称必须全局唯一。
- 关联类型(继承类型)
关联类型又可称为继承类型,关联类型具有private权限。关联类型的Namespace继承于公共类型的Namespace,用于覆盖公共Namespace的某些配置。
4.3.4 例子
如下图所示,有三个应用:应用A、应用B、应用C。
- 应用A有两个私有类型的Namespace:application和NS-Private,以及一个关联类型的Namespace:NS-Public。
- 应用B有一个私有类型的Namespace:application,以及一个公共类型的Namespace:NS-Public。
- 应用C只有一个私有类型的Namespace:application
应用A获取Apollo配置
//application
Config appConfig = ConfigService.getAppConfig();
appConfig.getProperty("k1", null); // k1 = v11
appConfig.getProperty("k2", null); // k2 = v21
//NS-Private
Config privateConfig = ConfigService.getConfig("NS-Private");
privateConfig.getProperty("k1", null); // k1 = v3
privateConfig.getProperty("k3", null); // k3 = v4
//NS-Public,覆盖公共类型配置的情况,k4被覆盖
Config publicConfig = ConfigService.getConfig("NS-Public");
publicConfig.getProperty("k4", null); // k4 = v6 cover
publicConfig.getProperty("k6", null); // k6 = v6
publicConfig.getProperty("k7", null); // k7 = v7
应用B获取Apollo配置
//application
Config appConfig = ConfigService.getAppConfig();
appConfig.getProperty("k1", null); // k1 = v12
appConfig.getProperty("k2", null); // k2 = null
appConfig.getProperty("k3", null); // k3 = v32
//NS-Private,由于没有NS-Private Namespace 所以获取到default value
Config privateConfig = ConfigService.getConfig("NS-Private");
privateConfig.getProperty("k1", "default value");
//NS-Public
Config publicConfig = ConfigService.getConfig("NS-Public");
publicConfig.getProperty("k4", null); // k4 = v5
publicConfig.getProperty("k6", null); // k6 = v6
publicConfig.getProperty("k7", null); // k7 = v7
应用C获取Apollo配置
//application
Config appConfig = ConfigService.getAppConfig();
appConfig.getProperty("k1", null); // k1 = v12
appConfig.getProperty("k2", null); // k2 = null
appConfig.getProperty("k3", null); // k3 = v33
//NS-Private,由于没有NS-Private Namespace 所以获取到default value
Config privateConfig = ConfigService.getConfig("NS-Private");
privateConfig.getProperty("k1", "default value");
//NS-Public,公共类型的Namespace,任何项目都可以获取到
Config publicConfig = ConfigService.getConfig("NS-Public");
publicConfig.getProperty("k4", null); // k4 = v5
publicConfig.getProperty("k6", null); // k6 = v6
publicConfig.getProperty("k7", null); // k7 = v7
5. Apollo使用
5.0 管理员工具
5.0.1 用户管理
5.0.2 系统权限管理
5.0.3 删除应用,集群,AppNamespace
5.0.4 系统信息
5.1 项目管理
5.1.1 查看项目
5.1.2 创建项目
输入项目信息
- 部门:选择应用所在的部门
- 应用AppId:用来标识应用身份的唯一id,格式为string,需要和项目配置文件applications.properties 中配置的app.id对应
- 应用名称:应用名,仅用于界面展示
- 应用负责人:选择的人默认会成为该项目的管理员,具备项目权限管理、集群创建、Namespace创建等 权限
5.1.3 修改项目信息
5.1.4 项目权限管理
5.2 配置管理
5.2.1 添加发布配置项
-
通过表格模式添加配置
- 通过文本模式编辑
5.2.2 发布配置项
5.2.3 修改配置
5.2.4 删除配置
5.2.5 回滚配置
5.2.6 变更和发布历史
5.3 多项目配置
5.3.1 关联公共Namespace
5.4 集群管理
5.5 多环境管理
5.6 灰度发布
灰度发布是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用 产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。
5.6.1 Apollo实现的功能
- 对于一些对程序有比较大影响的配置,可以先在一个或者多个实例生效,观察一段时间没问题后再全量发布 配置。
- 对于一些需要调优的配置参数,可以通过灰度发布功能来实现A/B测试。可以在不同的机器上应用不同的配 置,不断调整、测评一段时间后找出较优的配置再全量发布配置。
6. 踩坑实录(不完全统计)
6.1 未上线配置提前发布到线上
6.2 多集群发布时,没有全部发布。
生产环境的apollo配置,有3个集群:default、bd、bj2。假设有A、B、C三个配置参数,default集群配置了A、B、C三个参数,bd、bj2配置了C一个参数。当apollo配置发布后,bd集群的机器也会到apollo里去找A、B、C三个参数,即使A、B两个参数没有配置。
6.3 配置格式校验
qa环境进行校验,不要直接上线
8. 参考
https://github.com/ctripcorp/apollo
https://www.jianshu.com/p/edce8e8c139e
https://www.bilibili.com/video/BV1eE41187sS?t=5