Apollo配置中心

一、 简介

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

二、配置的属性

既然Apollo定位于配置中心,那么在这里有必要先简单介绍一下什么是配置。

按照我们的理解,配置有以下几个属性:

  • 配置是独立于程序的只读变量
    • 配置首先是独立于程序的,同一份程序在不同的配置下会有不同的行为。
    • 其次,配置对于程序是只读的,程序通过读取配置来改变自己的行为,但是程序不应该去改变配置。
    • 常见的配置有:DB Connection Str、Thread Pool Size、Buffer Size、Request Timeout、Feature Switch、Server Urls等。
  • 配置伴随应用的整个生命周期
    • 配置贯穿于应用的整个生命周期,应用在启动时通过读取配置来初始化,在运行时根据配置调整行为。
  • 配置可以有多种加载方式
    • 配置也有很多种加载方式,常见的有程序内部hard code,配置文件,环境变量,启动参数,基于数据库等
  • 配置需要治理
    • 权限控制
      • 由于配置能改变程序的行为,不正确的配置甚至能引起灾难,所以对配置的修改必须有比较完善的权限控制
    • 不同环境、集群配置管理
      • 同一份程序在不同的环境(开发,测试,生产)、不同的集群(如不同的数据中心)经常需要有不同的配置,所以需要有完善的环境、集群配置管理
    • 框架类组件配置管理
      • 还有一类比较特殊的配置 - 框架类组件配置,比如CAT客户端的配置。
      • 虽然这类框架类组件是由其他团队开发、维护,但是运行时是在业务实际应用内的,所以本质上可以认为框架类组件也是应用的一部分。
      • 这类组件对应的配置也需要有比较完善的管理方式。

三、Apollo 特点

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

四、Apollo 核心概念

1、基本概念

  1. application (应用)

    • 这个很好理解,就是实际使用配置的应用,Apollo客户端在运行时需要知道当前应用是谁,从而可以去获取对应的配置
    • 每个应用都需要有唯一的身份标识 -- appId,我们认为应用身份是跟着代码走的,所以需要在代码中配置,具体信息请参见Java客户端使用指南
      Apollo 1.0.0+支持通过Spring Boot的application.properties文件配置,如app.id=YOUR-APP-ID
  2. environment (环境)

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

    • 我们认为环境和代码无关,同一份代码部署在不同的环境就应该能够获取到不同环境的配置

    • 环境配置我们推荐:用户也可以创建一个 apollo-env.properties,放在程序的classpath下,或者放在spring boot应用的config目录下,使用该配置方式,那么就必须要正确配置Environment

      env=DEV
      dev.meta=http://1.1.1.1:8080
      fat.meta=http://apollo.fat.xxx.com
      uat.meta=http://apollo.uat.xxx.com
      pro.meta=http://apollo.xxx.com
      

      具体信息请参见Java客户端使用指南

  3. cluster (集群)

    • 一个应用下不同实例的分组,比如典型的可以按照数据中心分,把上海机房的应用实例分为一个集群,把北京机房的应用实例分为另一个集群。
    • 对不同的cluster,同一个配置可以有不一样的值,如zookeeper地址。
    • 集群默认是通过读取机器上的配置(server.properties中的idc属性)指定的,不过也支持运行时通过System Property指定,具体信息请参见Java客户端使用指南

    重点:通过Spring Boot的配置文件 可以在Spring Boot的 application.propertiesbootstrap.properties中指定 apollo.cluster=SomeCluster

  4. namespace (命名空间)

    • 一个应用下不同配置的分组,可以简单地把namespace类比为文件,不同类型的配置存放在不同的文件中,如数据库配置文件,RPC配置文件,应用自身的配置文件等
    • 应用可以直接读取到公共组件的配置namespace,如DAL,RPC等
    • 应用也可以通过继承公共组件的配置namespace来对公共组件的配置做调整,如DAL的初始数据库连接数

2、namespace

2.1 Namespace的获取权限分类

Namespace的获取权限分为两种:

  • private (私有的):private权限的Namespace,只能被所属的应用获取到。一个应用尝试获取其它应用private的Namespace,Apollo会报“404”异常。

  • public (公共的):public权限的Namespace,能被任何应用获取。

    这里的获取权限是相对于Apollo客户端来说的。

2.2 Namespace的类型

namespace类型有三种:

  • 私有类型
  • 公共类型
  • 关联类型(继承类型)

image.png

五、安装部署

1、 创建数据库

Apollo服务端共需要两个数据库:ApolloPortalDBApolloConfigDB,我们把数据库、表的创建和样例数据都分别准备了sql文件,只需要导入数据库即可。

注意:如果你本地已经创建过Apollo数据库,请注意备份数据。我们准备的sql文件会清空Apollo相关的表。

1.1 创建ApolloPortalDB

通过各种MySQL客户端导入sql/apolloportaldb.sql即可。

下面以MySQL原生客户端为例:

source /your_local_path/sql/apolloportaldb.sql

导入成功后,可以通过执行以下sql语句来验证:

select `Id`, `AppId`, `Name` from ApolloPortalDB.App;
Id AppId Name
1 SampleApp Sample App

1.2 创建ApolloConfigDB

通过各种MySQL客户端导入sql/apolloconfigdb.sql即可。

下面以MySQL原生客户端为例:

source /your_local_path/sql/apolloconfigdb.sql

导入成功后,可以通过执行以下sql语句来验证:

select `NamespaceId`, `Key`, `Value`, `Comment` from ApolloConfigDB.Item;
NamespaceId Key Value Comment
1 timeout 100 sample timeout配置

2、Apollo包

portal:后台配置管理页面;

config:提供配置的读取、推送等功能;

admin:提供配置的修改、发布等功能

下载:

image.png

3、修改连接信息

3.1 adminservice

解压:unzip -d ./apollo-adminservice apollo-adminservice-2.0.1-github.zip

image.png

image.png

image.png

3.2 configService

同样的方式更改数据库链接。

3.3 portal

同样的方式更改数据库链接。

修改开发环境/apollo-portal/config/apollo-env.properties ,我们可以更改一下开发环境

#local.meta=http://localhost:8080

#指定apollo的eureka注册中心
dev.meta=http://localhost:8080
#fat.meta=http://fill-in-fat-meta-server:8080
#uat.meta=http://fill-in-uat-meta-server:8080
#lpt.meta=${lpt_meta}
#pro.meta=http://fill-in-pro-meta-server:8080

4、启动顺序

configService

nohup java -jar apollo-configservice-2.0.1.jar > configservice.log &

adminservice

nohup java -jar apollo-adminservice-2.0.1.jar > adminservice.log &

portal

nohup java -jar apollo-portal-2.0.1.jar > portal.log &

5、访问

http://localhost:8070/ u/p apollo/admin

6、使用

6.1 创建应用

image.png

image.png

这里的部门可以从表里面更改;<font color="red">页面有缓存可以多刷几次</font>

添加项目

image.png

应用创建完成

image.png

6.2 创建项目

  1. 引入依赖

    <dependency>
        <groupId>com.ctrip.framework.apollo</groupId>
        <artifactId>apollo-client</artifactId>
        <version>2.0.1</version>
    </dependency>
    
  2. 配置文件

    # 应用名称
    spring.application.name=msb-vip-apollo
    # 应用服务 WEB 访问端口
    server.port=8080
    
    app.id=10001
    apollo.meta=http://192.168.0.102:8080
    #启用 apollo
    apollo.bootstrap.enabled=true
    #配置分组
    apollo.bootstrap.namespaces=application
    
  3. 提供测试类

    @RestController
    public class MainController {
    	@Value("${name}")
    	private String name;
    	@GetMapping("/")
    	public String getName() {
    		return name;
    	}
    }
    
  4. 指定环境

image.png

6.3 秘钥管理

  1. 添加秘钥

    image.png

  2. 项目中测试

    我们可以先测试一下看看是否可以拉取到数据,可以发现他的长轮询失败了,并且是以2的指数倍时间间隔,去访问

    http://192.168.179.101:8080/notifications/v2?cluster=default&appId=10001&ip=192.168.31.105&notifications=[{"names,这里我们可以看到时访问8080端口,我们以前看的是Eureka,其实我们项目对Eureka进行了封装。

    image.png

    查看是否能访问配置信息?

    我们访问后是能访问到的,这是由于缓存机制

    我们clean一下来看一下效果?

    他依旧能访问

    本地缓存

    /opt/data/{appId}/config-cache 
    Windows下
    C:\opt\data{appId}\config-cache
    
  3. 项目中配置秘钥 ,再启动就可以了

    image.png

    重点:我们这里时间必须同步,如果不同步就会报401

    设置时间:date -s "20221021 18:30:50"

    image.png

    网络同步时间:

    安装工具

    yum -y install ntp ntpdate

    同步网络时间
    ntpdate cn.pool.ntp.org

6.5 灰度发布

  1. 什么叫灰度发布

    灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。

    即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。

    而我们配置的发布,就是某个项目利用这个配置:例如迁移数据的数据库地址,如果没有问题,则全部迁移过去

  2. 发布过程

    image.png

    新增灰度发布后就会出现 主版本和灰度版本

    image.png

    **增加灰度配置和主配置名称要一样

    image.png
    新部署一个项目:将项目部署到服务器 java -Denv=DEV -jar msb-apollo-vip-0.0.1-SNAPSHOT.jar
    然后需要新建灰度规则,我们需要把项目打包,然后在服务器中部署一下,进行启动

    创建灰度规则

    image.png

    灰度发布

    image.png

6.6 Namespace

相当于配置的分组,他有公共配置和私有配置

创建私有配置

image.png

可以给对应权限添加用户

image.png

配置对应的信息

image.png

项目更改:

image.png

代码更改

image.png

6.7 集群

  • 创建集群

    image.png

  • 设置参数

    image.png

  • 代码更改

    image.png

六、整体架构

1、架构图

image.png

  • 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、为什么使用Eureka

为什么我们采用Eureka作为服务注册中心,而不是使用传统的zk、etcd呢?我大致总结了一下,有以下几方面的原因:

  • 它提供了完整的Service Registry和Service Discovery实现
    • 首先是提供了完整的实现,并且也经受住了Netflix自己的生产环境考验,相对使用起来会比较省心。
  • 和Spring Cloud无缝集成
    • 我们的项目本身就使用了Spring Cloud和Spring Boot,同时Spring Cloud还有一套非常完善的开源代码来整合Eureka,所以使用起来非常方便。
    • 另外,Eureka还支持在我们应用自身的容器中启动,也就是说我们的应用启动完之后,既充当了Eureka的角色,同时也是服务的提供者。这样就极大的提高了服务的可用性。
    • 这一点是我们选择Eureka而不是zk、etcd等的主要原因,为了提高配置中心的可用性和降低部署复杂度,我们需要尽可能地减少外部依赖。
  • Open Source
    • 最后一点是开源,由于代码是开源的,所以非常便于我们了解它的实现原理和排查问题。

3、服务端设计

3.1 配置发布后的实时推送设计

在配置中心中,一个重要的功能就是配置发布后实时推送到客户端。下面我们简要看一下这块是怎么设计实现的。

image.png

上图简要描述了配置发布的大致过程:

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

3.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,参见DatabaseMessageSender
  2. Config Service有一个线程会每秒扫描一次ReleaseMessage表,看看是否有新的消息记录,参见ReleaseMessageScanner
  3. Config Service如果发现有新的消息记录,那么就会通知到所有的消息监听器(ReleaseMessageListener),如NotificationControllerV2,消息监听器的注册过程参见ConfigServiceAutoConfiguration
  4. NotificationControllerV2得到配置发布的AppId+Cluster+Namespace后,会通知对应的客户端

示意图如下:

image.png

3.3 Config Service通知客户端的实现方式

上一节中简要描述了NotificationControllerV2是如何得知有配置发布的,那NotificationControllerV2在得知有配置发布后是如何通知到客户端的呢?

实现方式如下:

  1. 客户端会发起一个Http请求到Config Service的 notifications/v2接口,也就是NotificationControllerV2,参见RemoteConfigLongPollService
  2. NotificationControllerV2不会立即返回结果,而是通过Spring DeferredResult把请求挂起
  3. 如果在60秒内没有该客户端关心的配置发布,那么会返回Http状态码304给客户端
  4. 如果有该客户端关心的配置发布,NotificationControllerV2会调用DeferredResult的setResult方法,传入有配置变化的namespace信息,同时该请求会立即返回。客户端从返回的结果中获取到配置变化的namespace后,会立即请求Config Service获取该namespace的最新配置。

4、客户端设计

image.png

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

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

5、可用性考虑

场景 影响 降级 原因
某台Config Service下线 无影响 Config Service无状态,客户端重连其它Config Service
所有Config Service下线 客户端无法读取最新配置,Portal无影响 客户端重启时,可以读取本地缓存配置文件。如果是新扩容的机器,可以从其它机器上获取已缓存的配置文件,具体信息可以参考Java客户端使用指南 - 1.2.3 本地缓存路径
某台Admin Service下线 无影响 Admin Service无状态,Portal重连其它Admin Service
所有Admin Service下线 客户端无影响,Portal无法更新配置
某台Portal下线 无影响 Portal域名通过SLB绑定多台服务器,重试后指向可用的服务器
全部Portal下线 客户端无影响,Portal无法更新配置
某个数据中心下线 无影响 多数据中心部署,数据完全同步,Meta Server/Portal域名通过SLB自动切换到其它存活的数据中心
数据库宕机 客户端无影响,Portal无法更新配置 Config Service开启配置缓存后,对配置的读取不受数据库宕机影响

七、源码解读

image.png

posted @ 2022-11-15 21:45  天门道人  阅读(703)  评论(0编辑  收藏  举报