高性能配置中心 duic - 设计&实现
什么是配置?
配置(Configuration)对于技术人员来说应该都不陌生,通常配置都是以 key-value
的形式存在于配置文件当中。例如线程池大小、数据库连接、逻辑开关及业务参数等等。
那么为什么我们需要提供各种各样的配置呢?其本质原因是我们无法在开发时决定软件在运行时的一切,为保证程序的灵活性我们需要在开发过程中提供各种配置,以便软件在运行时能动态调整程序的行为。
配置即程序在运行时动态调整行为的能力!
灵感
之前我们一直是使用 spring-cloud-config
来管理服务端配置,但是 spring-cloud-config
并不支持热加载,按需获取配置,且因为是 spring
体系中的项目对于其它语言或非 spring
体系的项目支持较弱。
建立在这种基础之上,我们借鉴了 spring-cloud-config
的配置管理方式以及应用环境管理方式开发了全新的分布式配置中心 duic。
简介
duic 的诞生从来都不只是为了解决服务端的配置问题,你同样可以使用它对 APP、WEB 应用的配置进行管理。因采用 HTTP 的方式拉取配置数据,对于多语言,多应用类型的配置支持非常的好。
如果你的 APP、WEB、Server 有一部分公共配置,你也可以完全使用 duic 来解决,而不需要在每个应用单独配置(冗余),修改一处配置对于所有应用都有效。并且 duic 支持多配置合并功能,你可以将公共配置与私有配置分开管理,在拉取配置时进行合并,能有效的进行配置权限管理。同时 duic 支持按需获取配置,这个功能对于 WEB、APP 非常有用,有时你可能只需要某个配置参数,而不是拉取整个配置数据。
设计目标是统一不同应用的配置管理方式,打造更人性化的配置编辑方式,提供更灵活的配置获取方式。
duic 配置管理方式与其它配置中心是完全不一样的,我们放弃采用 key-value
两个输入框来编辑配置的方式。转而采用 YAML 语法 + 在线编辑器的方式来管理编辑配置。这样可以轻松支持结构化配置并且你可以得到高度可视化配置编辑方式,如下图:
这种管理方式所带来的好处是:
- 配置可读性高
- 支持语法高亮
- 支持数据类型
- 支持文档注释
- 结构化配置为按需获取配置提供了基础
通常配置都是以key-value
的形式存在,比如 VIP 的价格vip.price=100
,是否启用某个功能game.enabled=true
。大多数情况是如此,但是实际情况中还是有很多场景需要结构化的配置支持,目前使用较多来处理这种场景的方法是使用key=json string
来解决。例:将图标和点击的 URL 采用 JSON 的方式配置,可以在数据结构中维持配置的关系且配置易于理解。
- {
- "logo": {
- "top": {
- "icon": "icon url",
- "url": "link url"
- },
- "special": {
- "icon": "icon url",
- "url": "link url"
- },
- "goddess": {
- "icon": "icon url",
- "url": "link url"
- }
- }
- }
但是这种方式的缺点依然很明显,使用时必须要手动调用 JSON 解析 API 将字符串转换为 JSON 对象,其次是无法按需取值,比如我只想要 logo.top
的配置,但是也必须要将整个 logo
转换为 JSON 之后才能获取 top
的配置。而 duic 提供了按需获取配置接口,可以直接获取 logo.top
的配置项。
特性
集中配置管理,多应用多环境配置
配置实时更新
支持配置数据类型/数据格式
HTTP 方式拉取配置
配置状态检查
配置状态监控
多配置合并
按需获取配置
配置权限管理
支持配置 IP 访问限制
支持配置 TOKEN 校验
支持 MySQL 存储配置
支持 Oracle 存储配置
支持 PostgreSQL 存储配置
支持 MongoDB 存储配置
支持 Docker 部署
完美支持 Spring Boot
- 如果你的项目是采用 spring-boot 开发,那 duic 不仅能管理你业务的配置,还能管理所有 spring-boot 相关的配置,配置方式与
application.yml
文件编辑无差异。
- 如果你的项目是采用 spring-boot 开发,那 duic 不仅能管理你业务的配置,还能管理所有 spring-boot 相关的配置,配置方式与
比如你可以像下图一样设置应用日志级别信息,当你修改日志级别后在应用中能实时反应。
系统架构
部署
基本部署方案
duic 支持 MySQL、PostgreSQL、Oracle、MongoDB 存储配置信息,你可以灵活的选择配置存储方式。依照上图部署 duic 就能使用配置中心的全部功能,部署的方式和网络组织架构都非常的简单。
在设计时为了能简化部署,将 Admin 与获取配置相关的 API 集成在一个服务当中,降低部署的复杂性。
高可用部署架构方案
duic 对于数据库的压力非常的小,所以基本上数据库你只需要 master/salve 部署模式即可实现服务高可用。duic 服务你可以根据自身业务的压力情况部署 N 个。前面可以采用 nginx 做负载均衡利用 keepalived 避免单点问题。这是我们目前使用 duic 高可用的部署方案(仅供参考)。
实现
duic 是完全采用 Reactive Programming 实现。使用技术主要有 kotlin、spring-webflux、spring-boot、vue.js、gradle。
配置实时更新
客户端启动时获取配置及配置状态。
使用
/apps/watches/{name}/{profiles}
接口监控服务端配置状态变化。- 调用
watches
接口时需要传入客户端当前状态,服务端根据客户端传入的状态判断。 - 如果与服务端的配置状态不一致会立即响应最新的状态。
- 如果状态一致请求则会延迟返回(最长30秒)。在30秒内配置状态发生变化,服务端会立即响应。在30秒配置状态未发生变化,服务端也会响应当前最新状态给客户端。
- 调用
- 接收到服务端的状态响应时客户端需要判断状态是否与客户端当前的一致,如果一致则继续
watch
配置状态,如果不一致则重载配置
配置重载完全是由客户端发起的,这是长轮询(Long Polling)实现,利用 HTTP 长连接无需担心重复建立链接问题,带来的好处就是实现简单可用性高。
配置&内存
在 duic 服务启动时会将数据库存储的配置数据全部加载在 JVM 内存中,当你通过 HTTP 接口访问配置数据时,duic 会直接将内存中的配置返回,中间不会有任何查询数据库的操作,所以你使用 duic 获取配置时响应速度非常的快(具体请看后面的性能测试报告)。
集群管理
在 duic 服务启动时会将启动服务的主机名与端口注册到数据库的 DUIC_SERVER
表中。当有人在控制台修改配置时,duic 会通知 DUIC_SERVER
表中的所有服务重载最新配置。同时 duic 内部还会轮询机制保证 JVM 内存永远都是最新的配置数据。
RESTful API
获取配置状态
GET /apps/states/{name}/{profiles}
监控配置状态
GET /apps/watches/{name}/{profiles}
获取配置
GET /apps/{name}/{profiles}
按需获取配置
GET /apps/{name}/{profiles}/{key}
性能报告
服务器配置:E5-2620V3(12核24线程 主频2.4GHz) / 16G
操作系统:Ubuntu 16.04
测试工具: wrk
Java 版本:1.8.0_161
JVM 参数
java -server -XX:+UseG1GC -Xms8g -Xmx8g -XX:MetaspaceSize=128m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs/ -XX:+PrintGCDateStamps -verbose:gc -XX:+PrintGCDetails -Xloggc:logs/gc.log
测试结果,每秒 duic 可达到 24760tps,服务器 CPU 已经跑到 93%,在这台设备中性能已经无法在继续提高了,这是我目前手上最好的服务器配置。
Running 1m test @ http://192.168.31.164:7777/api/v1/apps/meme/test-public
12 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 3.91ms 1.01ms 38.21ms 93.70%
Req/Sec 2.07k 92.01 2.71k 83.65%
1486594 requests in 1.00m, 4.52GB read
Requests/sec: 24760.93
Transfer/sec: 77.10MB
CPU 报告
GC 报告
结语
duic 配置中心源码仓库 https://github.com/zhudyos/duic 欢迎大家提供任何建议,意见和吐槽。