配置中心
配置中心简介
说到配置中心, 大家可能都不陌生。我们携程现在用的qconfig, 就是一个典型的配置中心。
但是为什么要有配置中心呢?我们平时用的配置中心的系统架构是什么?如果让我们自己本地部署一套类似qconfig的配置中心, 我们怎么进行技术选型,以及需要做哪些准备工作呢?
为什么要有配置
首先, 我们介绍一下为什么要有配置中心。我举一个,我们机票支付的时候,如果是外币的情况下, 是容许一定的金额容差的。产品提需求的时候,说这个金额超过1块CNY, 我们就直接不置收款位。
但是实际上生产之后, 产品发现我们这个1CNY的金额,设备的太小,拦截了很多本来不应该拦截的订单,让我们紧急修改一下。于是我们牺牲了吃午饭的时间,改代码,回归测试,发布…
那假如我们用了配置中心呢?
我们在qconfig上改一下配置, 领导审核一下,十秒钟搞定。
为什么要有配置中心
很多同学可能会说, 这不就是一个简单的配置么,直接用DB, 然后做个本地缓存,轮询一下就好了嘛,干嘛这么麻烦还整一套配置中心的系统,增加了依赖、耗费了更多的资源、还要找人去维护这个配置中心。
的确, 如果只是一个小型的企业或者应用,直接把配置写在DB里或者配置文件里,也是一个不错的选择。但是, 如果是中型的企业或者有多应用、集群的情况,传统的配置, 就会产生很多问题。
几个典型的问题包括:
[if !supportLists]Ø [endif]配置格式不统一。有的用xml, 有的用properties, 有的用DB
[if !supportLists]Ø [endif]配置修改麻烦,周期长
[if !supportLists]Ø [endif]集群配置版本不一致、导致未知问题
[if !supportLists]Ø [endif]配置信息缺失安全审计和版本控制容易引发生产事故
[if !supportLists]Ø [endif]配置管理散乱,没有一个集中管理
为了解决这些问题, 所以, 我们就需要一个现代的配置中心。
它能够满足以下需求:
[if !supportLists]ü [endif]交付件和配置分离
[if !supportLists]ü [endif]抽象标准化
[if !supportLists]ü [endif]集中式
[if !supportLists]ü [endif]高可用
[if !supportLists]ü [endif]实时性
[if !supportLists]ü [endif]治理
基于配置的持续交付
那现在随着配置中心的流行, 逐渐衍生出了一个概念, 叫基于配置的持续交付。
我们可以一个做配置中心的SASS服务的产商,叫做launchdarkly。里面有一篇博客, 写的挺好。
这是一个平淡无奇的周五, 你准备下班回家、去健身房、并且陪陪家人。你最后想做的一件事是,把一个新功能部署到生产上去。That would be crazy talk. “Let’s just wait for Monday…it’s too
risky…what if things break?” 我们周一再来干吧… 风险太大了, 假如出bug了咋办。
In fact, you’re smiling and cranking up themusic as you confidently stroll out of the office to the bus stop. “Naive!”some would say, but you keep up the pace with a hop in your step.
A few hours later, you’re lounging on thecouch, proudly sunk into the cushy leather. “Bzz Bzz” as your phone rattles inyour pocket. You ignore it, but the Bzzing won’t stop. With an irksome look,you begrudgingly grab your phone and see a slew of error messages from yourtracker. Oh no! This is it. The deploy failed. The gamble has turned into anightmare… Right? Nope.
Without much thought and a timid roll ofthe eyes, you pull up LaunchDarkly and flip a switch. The errors stop and youronly annoyance is the 30 seconds missed of your show.
This is the power of feature flags.
大家如果有兴趣的话, 可以去读一下。
并且,基于配置的持续交付的话, 很大程度上, 还可以帮助大家解决merge hell的问题。就是开发周期太长了、导致合代码非常痛苦、我们可以用开关、分批合代码、上功能。
配置中心在整个微服务体系中的地位
这张图, 是大概五六年以前携程SOA的架构师杨波给的一张微服务的架构图。这张图很清晰的说明了配置中心在整个微服务体系中的地位。
图中有做服务的注册与发现的Eureka、做OAuth2授权认证的SpringSecurity、做容错限流的Hystrix、做服务网关的ZUUL,以及我们本节课要说的配置中心。他的图里是用的携程开源出去的一个配置中心组件,叫apollo。Apollo也是当前最流行的配置中心之一。
业界解决方案
Apollo携程开源 23.7k star,目前最受业界认可的方案
Nacos阿里开源16.2k star, 不仅仅是配置中心, 同时还能用于服务的注册和发现,以及服务管理
Spring cloud config不太适合互联网的场景
Disconf百度开源,最后一次维护已经是2018年
QconfigQunar开源,实际并没有怎么去维护和推广
业界现在主流的还是apollo和nacos。
我们在课程里, 也将详细的介绍apollo和nacos的部署、使用、以及架构。
Apollo
我们首先讲的是现在外面最主流的配置中心Apollo。
Apollo简介
说起Apollo,跟携程还有很大的渊源。官网上的简介是这样。
Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
Apollo 是携程的框架研发部再2016年就开源的一个产品,作者是宋顺,吴其敏当时是负责架构。Apollo, 现在在整个开源社区的热度还是比较高的,我现在看已经有23.4k的star了。
Apollo有很多公司接入了, 登记在策的包括航旅纵横、转转、吉祥航空、小红书、叮当买菜,等等。
Apollo本地部署
我们按照apollo的quickstart来
[if !supportLists]1、 [endif]jdk的版本, 服务端要求1.8+, 客户端需要1.7+。由于Quick Start会在本地同时启动服务端和客户端,所以需要在本地安装Java 1.8+。我的版本是1.8.0_101
[if !supportLists]2、 [endif]mysql的版本需要5.6.5+,
SHOW VARIABLES WHERE Variable_name ='version';
我们的版本是5.7.22
[if !supportLists]3、 [endif]下载all-in-one的quick start的包。Apollo为了防止我们从github上下载太慢, 还贴心的提供了一个百度网盘的下载地址。
[if !supportLists]4、 [endif]创建数据库。Apollo服务端共需要两个数据库:ApolloPortalDB和ApolloConfigDB,我们把数据库、表的创建和样例数据都分别准备了sql文件,只需要导入数据库即可。
创建ApolloPortalDB,是用来存权限、站点信息的。
导入成功后,可以通过执行以下sql语句来验证:
select `Id`, `AppId`, `Name` from ApolloPortalDB.App;
创建ApolloConfigDB, 是用来存配置的。
导入成功后,可以通过执行以下sql语句来验证:
select `NamespaceId`, `Key`, `Value`, `Comment` from ApolloConfigDB.Item;
[if !supportLists]5、 [endif]配置数据库的连接信息
将Demo.sh里的端口号、用户名、密码, 修改正确
[if !supportLists]6、 [endif]启动apollo配置中心, 执行脚本
./demo.sh start
看到这个输出, 就表示启动成功了。
Client端
demo.sh client start
功能介绍
账号apollo 密码admin
主界面
进入sampleapp
修改配置
发布配置
其他功能
创建项目、添加集群、添加namespace、回滚、发布历史版本、授权、灰度等
修改timeout的配置为300
从client端可以看到
Apollo的核心概念
项目:Application
从上图中我们可以看出,每个项目有部门、appid、应用名称、应用负责人。
其中最主要的是appid。需要跟
Java: classpath:/META-INF/app.properties->appid
.net: app.config->AppID
环境:environment
环境可以区分为:DEV、FAT、UAT、PRO。
可以在server.properties->env里配置
server.properties的位置在windows:c:\opt\setting\server.properties 或者linux: /opt/settings/server.properties
集群:cluster
[if !supportLists]l [endif]通过添加集群,可以使同一份程序在不同的集群(如不同的数据中心)使用不同的配置
[if !supportLists]l [endif]如果不同集群使用一样的配置,则没有必要创建集群
[if !supportLists]l [endif]Apollo默认会读取机器上/opt/settings/server.properties(linux)或C:\opt\settings\server.properties(windows)文件中的idc属性作为集群名字, 如SHAJQ(金桥数据中心)、SHAOY(欧阳数据中心)
[if !supportLists]l [endif]在这里创建的集群名字需要和机器上server.properties中的idc属性一致
配置项
定位方式:
私有配置:env+app+cluster+namespace+item_key
共有配置:env+cluster+namespace+item_key
Apollo 总体架构
Apollo的基础模型如下:
[if !supportLists]1. [endif]用户在配置中心对配置进行修改并发布
[if !supportLists]2. [endif]配置中心通知Apollo客户端有配置更新
[if !supportLists]3. [endif]Apollo客户端从配置中心拉取最新的配置、更新本地配置并通知到应用
客户端的架构
上图简要描述了Apollo客户端的实现原理:
[if !supportLists]l [endif]客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送。
[if !supportLists]l [endif]客户端还会定时从Apollo配置中心服务端拉取应用的最新配置。
[if !supportLists]² [endif]这是一个fallback机制,为了防止推送机制失效导致配置不更新
[if !supportLists]² [endif]客户端定时拉取时,会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified
[if !supportLists]² [endif]定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property:
apollo.refreshInterval来覆盖,单位为分钟。
[if !supportLists]l [endif]客户端从Apollo配置中心服务端获取到应用的最新配置后,会保存在内存中
[if !supportLists]l [endif]客户端会把从服务端获取到的配置在本地文件系统缓存一份
[if !supportLists]² [endif]在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置
[if !supportLists]l [endif]应用程序从Apollo客户端获取最新的配置、订阅配置更新通知
配置更新推送实现
前面提到了Apollo客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送。
长连接实际上是通过Http Long Polling实现的,具体而言:
[if !supportLists]l [endif]客户端发起一个Http请求到服务端
[if !supportLists]l [endif]服务端会保持住这个连接60秒
[if !supportLists]² [endif]如果在60秒内有客户端关心的配置变化,被保持住的客户端请求会立即返回,并告知客户端有配置变化的namespace信息,客户端会据此拉取对应namespace的最新配置
[if !supportLists]² [endif]如果在60秒内没有客户端关心的配置变化,那么会返回Http状态码304给客户端
[if !supportLists]l [endif]客户端在收到服务端请求后会立即重新发起连接,回到第一步
考虑到会有数万客户端向服务端发起长连,在服务端我们使用了async
servlet(Spring DeferredResult)来服务Http Long Polling请求。
客户端如何发现MeterServer
Apollo支持应用在不同的环境有不同的配置,所以需要在运行提供给Apollo客户端当前环境的Apollo Meta Server信息。默认情况下,meta server和config service是部署在同一个JVM进程,所以meta server的地址就是config service的地址。
为了实现meta server的高可用,推荐通过SLB(Software Load Balancer)做动态负载均衡。Meta server地址也可以填入IP,如http://1.1.1.1:8080,http://2.2.2.2:8080,不过生产环境还是建议使用域名(走slb),因为机器扩容、缩容等都可能导致IP列表的变化。
Apollo支持以下方式配置apollo
meta server信息,按照优先级从高到低分别为:
1、通过Java SystemProperty apollo.meta
2、通过Spring Boot的配置文件
可以在Spring Boot的application.properties或bootstrap.properties中指定apollo.meta=http://config-service-url
3、通过操作系统的SystemEnvironmentAPOLLO_META
可以通过操作系统的System Environment APOLLO_META来指定
注意key为全大写,且中间是_分隔
4、通过server.properties配置文件
可以在server.properties配置文件中指定apollo.meta=http://config-service-url
对于Mac/Linux,文件位置为/opt/settings/server.properties
对于Windows,文件位置为C:\opt\settings\server.properties
5、通过app.properties配置文件
可以在classpath:/META-INF/app.properties指定apollo.meta=http://config-service-url
6、通过Java systemproperty ${env}_meta
如果当前env是dev,那么用户可以配置-Ddev_meta=http://config-service-url
使用该配置方式,那么就必须要正确配置Environment
7、通过操作系统的System
Environment ${ENV}_META (1.2.0版本开始支持)
如果当前env是dev,那么用户可以配置操作系统的System Environment DEV_META=http://config-service-url
注意key为全大写
使用该配置方式,那么就必须要正确配置Environment
8、通过apollo-env.properties文件
用户也可以创建一个apollo-env.properties,放在程序的classpath下,或者放在spring boot应用的config目录下
使用该配置方式,那么就必须要正确配置Environment,详见1.2.4.1 Environment
文件内容形如:
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
如果通过以上各种手段都无法获取到Meta Server地址,Apollo最终会fallback到http://apollo.meta作为Meta Server地址。
服务端的架构
如果没有一定的分布式架构基础的话, 其实看这个图, 是有一点难看懂的。
我们从这个图上, 可以看到7个模块,从上到下, 分别是:Client、Portal、Load
Balancer、Meta Server、Eureka、Config Service、Admin Service
其中4个是功能模块,3个是辅助模块
四个功能模块:
1、ConfigService
[if !supportLists]l [endif]提供配置获取接口
[if !supportLists]l [endif]提供配置推送接口
[if !supportLists]l [endif]服务于Apollo客户端
2、AdminService
[if !supportLists]l [endif]提供配置管理接口
[if !supportLists]l [endif]提供配置修改发布接口
[if !supportLists]l [endif]服务于管理界面Portal
3、Client
[if !supportLists]l [endif]为应用获取配置,支持实时更新
[if !supportLists]l [endif]通过MetaServer获取ConfigService的服务列表
[if !supportLists]l [endif]使用客户端软负载SLB方式调用ConfigService
4、Portal
[if !supportLists]l [endif]配置管理界面
[if !supportLists]l [endif]通过MetaServer获取AdminService的服务列表
[if !supportLists]l [endif]使用客户端软负载SLB方式调用AdminService
三个辅助服务发现模块
1、Eureka
[if !supportLists]l [endif]用于服务发现和注册
[if !supportLists]l [endif]Config/AdminService注册实例并定期报心跳
[if !supportLists]l [endif]和ConfigService住在一起部署
2、MetaServer
[if !supportLists]l [endif]Portal通过域名访问MetaServer获取AdminService的地址列表
[if !supportLists]l [endif]Client通过域名访问MetaServer获取ConfigService的地址列表
[if !supportLists]l [endif]相当于一个Eureka Proxy
[if !supportLists]l [endif]逻辑角色,和ConfigService住在一起部署
3、NginxLB
[if !supportLists]l [endif]和域名系统配合,协助Portal访问MetaServer获取AdminService地址列表
[if !supportLists]l [endif]和域名系统配合,协助Client访问MetaServer获取ConfigService地址列表
[if !supportLists]l [endif]和域名系统配合,协助用户访问Portal进行配置管理
服务端架构剖析
架构V1
如果不考虑分布式微服务架构中的服务发现问题,将CAP的取舍问题晾在一边,配置中心最基础的功能就是存储一个键值对,用户发布一个配置(configKey),然后客户端获取这个配置项(configValue);进阶的功能就是当某个配置项发生变更时,将变更告知客户端刷新旧值。
所以Apollo的最简架构如下图所示。
1、ConfigService是一个独立的微服务,服务于Client进行配置获取。
2、Client和ConfigService保持长连接,通过一种推拉结合(push & pull)的模式,在实现配置实时更新的同时,保证配置更新不丢失。
3、AdminService是一个独立的微服务,服务于Portal进行配置管理。Portal通过调用AdminService进行配置管理和发布。
4、ConfigService和AdminService共享ConfigDB,ConfigDB中存放项目在某个环境中的配置信息。ConfigService/AdminService/ConfigDB三者在每个环境(DEV/FAT/UAT/PRO)中都要部署一份。
5、Protal有一个独立的PortalDB,存放用户权限、项目和配置的元数据信息。Protal只需部署一份,它可以管理多套环境。
架构V2
为了保证高可用,ConfigService和AdminService都是无状态以集群方式部署的,这个时候就存在一个服务发现问题:Client怎么找到ConfigService?Portal怎么找到AdminService?为了解决这个问题,Apollo在其架构中引入了Eureka服务注册中心组件,实现微服务间的服务注册和发现。
1、Config/AdminService启动后都会注册到Eureka服务注册中心,并定期发送保活心跳。
2、Eureka采用集群方式部署,使用分布式一致性协议保证每个实例的状态最终一致。
架构V3
我们知道Eureka是自带服务发现的Java客户端的,如果Apollo只支持Java客户端接入,不支持其它语言客户端接入的话,那么Client和Portal只需要引入Eureka的Java客户端,就可以实现服务发现功能。发现目标服务后,通过客户端软负载(SLB,例如Ribbon)就可以路由到目标服务实例。这是一个经典的微服务架构,基于Eureka实现服务注册发现+客户端Ribbon配合实现软路由
架构V4
在携程,应用场景不仅有Java,还有很多遗留的.Net应用。Apollo的作者也考虑到开源到社区以后,很多客户应用是非Java的。但是Eureka(包括Ribbon软负载)原生仅支持Java客户端,如果要为多语言开发Eureka/Ribbon客户端,这个工作量很大也不可控。为此,Apollo的作者引入了MetaServer这个角色,它其实是一个Eureka的Proxy,将Eureka的服务发现接口以更简单明确的HTTP接口的形式暴露出来,方便Client/Protal通过简单的HTTPClient就可以查询到Config/AdminService的地址列表。获取到服务实例地址列表之后,再以简单的客户端软负载(Client SLB)策略路由定位到目标实例,并发起调用。
现在还有一个问题,MetaServer本身也是无状态以集群方式部署的,那么Client/Protal该如何发现MetaServer呢?一种传统的做法是借助硬件或者软件负载均衡器,例如在携程采用的是扩展后的NginxLB(也称Software Load Balancer),由运维为MetaServer集群配置一个域名,指向NginxLB集群,NginxLB再对MetaServer进行负载均衡和流量转发。Client/Portal通过域名+NginxLB间接访问MetaServer集群。
架构V5
V4版本已经是比较完整的Apollo架构全貌,现在还剩下最后一个环节:Portal也是无状态以集群方式部署的,用户如何发现和访问Portal?答案也是简单的传统做法,用户通过域名+NginxLB间接访问Portal集群。
所以V5版本是包括用户端的最终的Apollo架构全貌,如下图所示:
再回头看apollo的github上给出的架构图,就发现完全不难懂
Qconfig
Qconfig简介
Qconfig本地部署
启动server:
[if !supportLists]1. [endif]从github下载qconfig的代码https://github.com/qunarcorp/qconfig
[if !supportLists]2. [endif]通过maven导入到idea里。
[if !supportLists]3. [endif]导入数据库。创建名为qconfig的数据库,然后导入克隆代码中admin模块下的sql/main.sql。推荐同时导入qconfig_data,导入qconfig_data后即可将QConfig配置文件以及示例文件和应用导入。
[if !supportLists]4. [endif]修改server模块下的app-info.properties里的port,改为8080。
[if !supportLists]5. [endif]修改server模块下的qconfig_test.qconfig/mysql.properties里的jdbc.url jdbc.username jdbc.password为本地数据
[if !supportLists]6. [endif]通过tomcat启动server模块
访问URL:http://localhost:8080/
如果出现erueka界面,即表示已正常启动。
启动admin:
[if !supportLists]1. [endif]修改admin模块下的app-info.properties里的port,改为8081。
[if !supportLists]2. [endif]修改admin模块下的qconfig_test.qconfig/mysql.properties里的jdbc.url jdbc.username jdbc.password为本地数据
[if !supportLists]3. [endif]登陆http://localhost:8081/, 账号admin 密码123456.
点击b_qconfig_test
其他的就跟我们现在用的qconfig是差不多了,在此就不一一介绍了。
Qconfig总体架构
基础模型如下:
一次发布操作通过如下流程完成发布操作
用户在admin端完成发布操作
admin端通过http接口通知所有server端当前文件由新的发布版本,同时Server也有定时任务进行补偿。
Server通过已建立的长链接完成对Client的通知
架构模型如下:
在QConfig中,所有的配置以及相关的数据最终都被存放在数据库中。主要模型如下
[if !supportLists]l [endif]config_candidate_snapshot 每次的文件变更都会被存放到其中。
[if !supportLists]l [endif]Config_candidate 存放每个文件最新版本的索引。
[if !supportLists]l [endif]Config 存放每个文件最新发布版本的索引
[if !supportLists]l [endif]config_snapshot 存放被发布或被推送的文件
Qconfig 服务端架构
Server发现
Server之前通过内置的eureka进行相互发现。同时,根据eureka探活结果,返回server的entryPoint。
选择eureka的原因如下
Server被设计为只读且无状态的,不存在一致性问题。
需要尽量减小外部以来。
根据以上两点即选择了eureka。
Server数据模型
Server使用了两级缓存的方式来提升配置的获取速度。第一层缓存在内存中,主要用于针对热点数据,以降低磁盘IO。第二层指持久化在磁盘中的文件,当数据从数据库中读取会,便会持久化在磁盘中,以降低数据库瓶颈。
当Server收到数据请求时,会首先检查内存中是否存在缓存,如果没有,则会去获取文件。最后才是去获取数据库中的数据
admin与Client的Server发现
admin与Client之间,使用域名的方式访问server的entrypoint接口,由ng负责从域名到server的负载均衡等操作。
Server的entrypoint接口会返回无序的在线Server列表。
Qconfig client的架构
QConfig client与server的通信采用http协议。client与server端的交互主要有两种操作:轮询配置文件版本号、拉取最新版本的文件。目前实现中,轮询版本号是批量操作;拉取配置文件是单次操作。
[if !supportLists]l [endif]Client与Server之间通过保持长链接来保证应用更新的及时推送。
[if !supportLists]l [endif]client会将文件在内存中缓存,同时还会持久化到磁盘中,以保证极端状况下正常恢复。
[if !supportLists]l [endif]Client通过Spring的注解机制来实现注解的扫描。
我们可以得出一个结论,qconfig 和 apollo的架构, 基本上是一致的。
Nacos
Nacos简介
Nacos一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
Nacos是由阿里在2018-06月开源的。
接入的公司包括:阿里巴巴、虎牙直播、工行、爱奇艺、平安科技、贝壳找房等等。
虽然我们这里只用到了nacos的配置管理, 但是noacos绝不仅仅是配置管理, 他包括:
[if !supportLists]ü [endif]动态配置服务。
动态配置服务让您能够以中心化、外部化和动态化的方式管理所有环境的配置。动态配置消除了配置变更时重新部署应用和服务的需要。配置中心化管理让实现无状态服务更简单,也让按需弹性扩展服务更容易。
[if !supportLists]ü [endif]服务发现及管理
动态服务发现对以服务为中心的(例如微服务和云原生)应用架构方式非常关键。Nacos支持DNS-Based和RPC-Based(Dubbo、gRPC)模式的服务发现。Nacos也提供实时健康检查,以防止将请求发往不健康的主机或服务实例。借助Nacos,您可以更容易地为您的服务实现断路器。
[if !supportLists]ü [endif]动态DNS服务
通过支持权重路由,动态DNS服务能让您轻松实现中间层负载均衡、更灵活的路由策略、流量控制以及简单数据中心内网的简单DNS解析服务。动态DNS服务还能让您更容易地实现以DNS协议为基础的服务发现,以消除耦合到厂商私有服务发现API上的风险。
所以, 如果大家在一些服务的注册与发现或者动态DNS服务的场景, 听到nacos的时候不要吃惊。
Nacos本地部署
[if !supportLists]1、 [endif]下载nacos的quickstart的包
[if !supportLists]2、 [endif]startup.cmd-m standalone
[if !supportLists]3、 [endif]输入:http://127.0.0.1:8848/nacos/index.html
登陆的账号密码是nacos/nacos
Nacos配置管理功能介绍
新建配置
编辑配置
发布配置
其他的服务管理、集群管理, 跟配置中心关系不大,我们就不一一介绍了, 大家有兴趣点额话, 可以自己部署一个本地版本,去看一下。
Nacos配置管理核心概念
命名空间
用于进行租户粒度的配置隔离。不同的命名空间下,可以存在相同的 Group 或 Data ID 的配置。Namespace 的常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。
配置
在系统开发过程中,开发者通常会将一些需要变更的参数、变量等从代码中分离出来独立管理,以独立的配置文件的形式存在。目的是让静态的系统工件或者交付物(如 WAR,JAR 包等)更好地和实际的物理运行环境进行适配。配置管理一般包含在系统部署的过程中,由系统管理员或者运维人员完成。配置变更是调整系统运行时的行为的有效手段。
配置管理
系统配置的编辑、存储、分发、变更管理、历史版本管理、变更审计等所有与配置相关的活动。
配置项
一个具体的可配置的参数与其值域,通常以 param-key=param-value 的形式存在。例如我们常配置系统的日志输出级别(logLevel=INFO|WARN|ERROR) 就是一个配置项。
配置集
一组相关或者不相关的配置项的集合称为配置集。在系统中,一个配置文件通常就是一个配置集,包含了系统各个方面的配置。例如,一个配置集可能包含了数据源、线程池、日志级别等配置项。
配置集ID
Nacos 中的某个配置集的 ID。配置集 ID 是组织划分配置的维度之一。Data ID 通常用于组织划分系统的配置集。一个系统或者应用可以包含多个配置集,每个配置集都可以被一个有意义的名称标识。Data ID 通常采用类 Java 包(如 com.taobao.tc.refund.log.level)的命名规则保证全局唯一性。此命名规则非强制。
配置分组
Nacos 中的一组配置集,是组织配置的维度之一。通过一个有意义的字符串(如 Buy 或 Trade )对配置集进行分组,从而区分 Data ID 相同的配置集。当您在 Nacos 上创建一个配置时,如果未填写配置分组的名称,则配置分组的名称默认采用 DEFAULT_GROUP 。配置分组的常见场景:不同的应用或组件使用了相同的配置类型,如 database_url 配置和 MQ_topic 配置。
配置快照
Nacos 的客户端 SDK 会在本地生成配置的快照。当客户端无法连接到 Nacos Server 时,可以使用配置快照显示系统的整体容灾能力。配置快照类似于Git 中的本地 commit,也类似于缓存,会在适当的时机更新,但是并没有缓存过期(expiration)的概念。
Nacos总体架构
下图是nacos的一个逻辑架构
[if !supportLists]l [endif]服务管理:实现服务CRUD,域名CRUD,服务健康状态检查,服务权重管理等功能
[if !supportLists]l [endif]配置管理:实现配置管CRUD,版本管理,灰度管理,监听管理,推送轨迹,聚合数据等功能
[if !supportLists]l [endif]元数据管理:提供元数据CURD 和打标能力
[if !supportLists]l [endif]插件机制:实现三个模块可分可合能力,实现扩展点SPI机制
[if !supportLists]l [endif]事件机制:实现异步化事件通知,sdk数据变化异步通知等逻辑
[if !supportLists]l [endif]日志模块:管理日志分类,日志级别,日志可移植性(尤其避免冲突),日志格式,异常码+帮助文档
[if !supportLists]l [endif]回调机制:sdk通知数据,通过统一的模式回调用户处理。接口和数据结构需要具备可扩展性
[if !supportLists]l [endif]寻址模式:解决ip,域名,nameserver、广播等多种寻址模式,需要可扩展
[if !supportLists]l [endif]推送通道:解决server与存储、server间、server与sdk间推送性能问题
[if !supportLists]l [endif]容量管理:管理每个租户,分组下的容量,防止存储被写爆,影响服务可用性
[if !supportLists]l [endif]流量管理:按照租户,分组等多个维度对请求频率,长链接个数,报文大小,请求流控进行控制
[if !supportLists]l [endif]缓存机制:容灾目录,本地缓存,server缓存机制。容灾目录使用需要工具
[if !supportLists]l [endif]启动模式:按照单机模式,配置模式,服务模式,dns模式,或者all模式,启动不同的程序+UI
[if !supportLists]l [endif]一致性协议:解决不同数据,不同一致性要求情况下,不同一致性机制
[if !supportLists]l [endif]存储模块:解决数据持久化、非持久化存储,解决数据分片问题
[if !supportLists]l [endif]Nameserver:解决namespace到clusterid的路由问题,解决用户环境与nacos物理环境映射问题
[if !supportLists]l [endif]CMDB:解决元数据存储,与三方cmdb系统对接问题,解决应用,人,资源关系
[if !supportLists]l [endif]Metrics:暴露标准metrics数据,方便与三方监控系统打通
[if !supportLists]l [endif]Trace:暴露标准trace,方便与SLA系统打通,日志白平化,推送轨迹等能力,并且可以和计量计费系统打通
[if !supportLists]l [endif]接入管理:相当于阿里云开通服务,分配身份、容量、权限过程
[if !supportLists]l [endif]用户管理:解决用户管理,登录,sso等问题
[if !supportLists]l [endif]权限管理:解决身份识别,访问控制,角色管理等问题
[if !supportLists]l [endif]审计系统:扩展接口方便与不同公司审计系统打通
[if !supportLists]l [endif]通知系统:核心数据变更,或者操作,方便通过SMS系统打通,通知到对应人数据变更
[if !supportLists]l [endif]OpenAPI:暴露标准Rest风格HTTP接口,简单易用,方便多语言集成
[if !supportLists]l [endif]Console:易用控制台,做服务管理、配置管理等操作
[if !supportLists]l [endif]SDK:多语言sdk
[if !supportLists]l [endif]Agent:dns-f类似模式,或者与mesh等方案集成
[if !supportLists]l [endif]CLI:命令行对产品进行轻量化管理,像git一样好用
Nacos配置管理客户端架构
获取配置
获取配置的主要方法是 NacosConfigService 类的 getConfigInner 方法,通常情况下该方法直接从本地文件中取得配置的值,如果本地文件不存在或者内容为空,则再通过 HTTP GET 方法从远端拉取配置,并保存到本地快照中。
当通过 HTTP 获取远端配置时,Nacos提供了两种熔断策略,一是超时时间,二是最大重试次数,默认重试三次。
注册监听器
配置中心客户端对某个配置项注册监听器是很常见的需求,达到在配置项变更的时候执行回调的功能。
iconfig.addListener(dataId, group, ml);
iconfig.getConfigAndSignListener(dataId,group, 1000, ml);
Nacos 可以通过以上方式注册监听器,它们内部的实现均是调用 ClientWorker 类的 addCacheDataIfAbsent。其中 CacheData 是一个维护配置项和其下注册的所有监听器的实例,私以为这个名字取得并不好,不容易理解。
所有的 CacheData 都保存在ClientWorker 类中的原子 cacheMap 中,其内部的核心成员有:
其中,content 是配置内容,MD5值是用来检测配置是否发生变更的关键,内部还维护着一个若干监听器组成的数组,一旦发生变更则依次回调这些监听器。
配置长轮询
ClientWorker 通过其下的两个线程池完成配置长轮询的工作,一个是单线程的 executor,每隔 10ms 按照每 3000 个配置项为一批次捞取待轮询的 cacheData 实例,将其包装成为一个 LongPollingTask 提交进入第二个线程池 executorService 处理。
该长轮询任务内部主要分为四步:
[if !supportLists]1. [endif]检查本地配置,忽略本地快照不存在的配置项,检查是否存在需要回调监听器的配置项
[if !supportLists]2. [endif]如果本地没有配置项的,从服务端拿,返回配置内容发生变更的键值列表
[if !supportLists]3. [endif]每个键值再到服务端获取最新配置,更新本地快照,补全之前缺失的配置
[if !supportLists]4. [endif]检查 MD5 标签是否一致,不一致需要回调监听器
如果该轮询任务抛出异常,等待一段时间再开始下一次调用,减轻服务端压力。另外,Nacos在HTTP 工具类中也有限流器的代码,通过多种手段降低轮询或者大流量情况下的风险。下文还会讲到,如果在服务端没有发现变更的键值,那么服务端会夯住这个 HTTP 请求一段时间(客户端侧默认传递的超时是 30s),以此进一步减轻客户端的轮询频率和服务端的压力。
Nacos配置管理服务端架构
配置Dump
服务端启动时就会依赖 DumpService 的 init 方法,从数据库中 load 配置存储在本地磁盘上,并将一些重要的元信息例如 MD5 值缓存在内存中。服务端会根据心跳文件中保存的最后一次心跳时间,来判断到底是从数据库 dump 全量配置数据还是部分增量配置数据(如果机器上次心跳间隔是 6h 以内的话)。
全量 dump 当然先清空磁盘缓存,然后根据主键 ID 每次捞取一千条配置刷进磁盘和内存。增量 dump 就是捞取最近六小时的新增配置(包括更新的和删除的),先按照这批数据刷新一遍内存和文件,再根据内存里所有的数据全量去比对一遍数据库,如果有改变的再同步一次,相比于全量 dump 的话会减少一定的数据库 IO 和磁盘 IO 次数。
配置注册
Nacos 服务端是一个SpringBoot 实现的服务,注册配置主要代码位于 ConfigController 和 ConfigServletInner 中。服务端一般是多节点部署的集群,因此请求一开始只会打到一台机器,这台机器将配置插入 MySQL 中进行持久化,这部分代码很简单不再赘述。
因为服务端并不是针对每次配置查询都去访问 MySQL 的,而是会依赖 dump 功能在本地文件中将配置缓存起来。因此当单台机器保存完毕配置之后,需要通知其他机器刷新内存和本地磁盘中的文件内容,因此它会发布一个名为 ConfigDataChangeEvent 的事件,这个事件会通过 HTTP 调用通知所有集群节点(包括自身),触发本地文件和内存的刷新。
处理长轮询
上文提到,客户端会有一个长轮询任务,拉取服务端的配置变更,那么服务端是如何处理这个长轮询任务的呢?源码逻辑位于 LongPollingService 类,其中有一个 Runnable 任务名为 ClientLongPolling,服务端会将受到的轮询请求包装成一个ClientLongPolling 任务,该任务持有一个 AsyncContext 响应对象(Servlet 3.0 的新机制),通过定时线程池延后 29.5s 执行。
为什么比客户端 30s 的超时时间提前500ms 返回是为了最大程度上保证客户端不会因为网络延时造成超时
这里需要注意的是,在 ClientLongPolling 任务被提交进入线程池待执行的同时,服务端也通过一个队列 allSubs 保存了所有正在被夯住的轮询请求,这是因为在配置项被夯住的期间内,如果用户通过管理平台操作了配置项变更、或者服务端该节点收到了来自其他节点的 dump 刷新通知,那么都应立即取消夯住的任务,及时通知客户端数据发生了变更。
为了达到这个目的,LongPollingService 类继承自 Event 接口,实际上本身是个事件触发器,需要实现 onEvent 方法,其事件类型是 LocalDataChangeEvent。
当服务端在请求被夯住的期间接收到某项配置变更时,就会发布一个LocalDataChangeEvent 类型的事件通知(注意同上文中的ConfigDataChangeEvent 区别),之后会将这个变更包装成一个 DataChangeTask 异步执行,内容就是从 allSubs 中找出夯住的 ClientLongPolling 请求,写入变更强制其立即返回。
因此完整的流程如下,如果非接收请求的节点,那么忽略第一步持久化配置后开始:
Apollo 和 Nacos性能对比
性能方面, 网上有人做过评测,
Nacos和Apollo使用同样的数据库(32C128G),部署Server服务的机器使用的8C16G配置的容器,磁盘是100G SSD。
单机读场景:
客户端测试程序通过部署多台机器,每台机器开启多个线程从配置中心读取不同的配置(3000个)。Nacos QPS可以达到15000,Apollo分为读内存缓存和从数据库中读两种方式,从数据库中读能达到7500,从内存读缓存性能可以达到9000QPS。
3节点读场景:
将配置中心的压测节点数都部署成3个节点。Nacos QPS可以达到45000 QPS,Apollo读内存缓存可以达到27000 QPS。Nacos和Apollo由于读场景各个节点是独立的,基本就是单机读场景的3倍关系。
单机写场景:
同样的方式,多台机器同时在配置中心修改不同的配置。Nacos QPS可以达到1800,Apollo使用默认的数据库连接池(10)QPS只能达到800 QPS(CPU未压满),调整连接池至100可以达到1100 QPS(CPU压满)。
3节点写场景:
同样的方式,将配置中心的压测节点数都部署成3个节点。Nacos QPS可以达到6000,Apollo可以达到3300 QPS(CPU压满),此时MySQL数据库因为配置较高,未成为性能瓶颈。
总结
综上,nacos和apollo从性能上其实都能满足基本所有应用的需求。apollo在易用性和功能的完善性上(比如灰度发布、权限管理、多环境管理)比nacos要好。Nacos不仅仅是一个配置中心,同时还有服务的注册与发现的功能。nacos是后起之秀,超过apollo也是有可能的。
参考资料
《nacos github》https://github.com/alibaba/nacos
《nacos社区文档》https://nacos.io/zh-cn/docs/what-is-nacos.html
《图文解析 Nacos 配置中心的实现》https://zhuanlan.zhihu.com/p/103244639
《apollo github》https://github.com/ctripcorp/apollo
《apollo 配置中心介绍》https://ctripcorp.github.io/apollo/#/zh/design/apollo-introduction
《微服务架构~携程Apollo配置中心架构剖析》https://mp.weixin.qq.com/s/-hUaQPzfsl9Lm3IqQW3VDQ
《apollo quick start》https://ctripcorp.github.io/apollo/#/zh/deployment/quick-start