随着业务的发展,应用系统中的配置通常会越来越多,常见的一些应用配置大致会有数据源配置,数据源组件配置,业务组件配置等,对于这类配置都会比较稳定且较少变化,通常会放在文件中随应用一起发布。但实际中会有某些配置信息变化有一定频率和规律,并且希望能够做到尽量实时,比如一些营销类,或活动类应用系统,若使用传统的配置文件,加上重新发布应用可能会有些不方便,因此,才有了分布式配置管理平台,旨在能更好地解决这类问题。本文将介绍相关细节,及一个轻量的开源实现diablo。
-
分布式配置平台的一些应用场景
分布式配置,也即配置中心。通常有以下的场景或需求,可以需要考虑使用分布式配置:
对某些配置的更新,不想要重启应用,并且能近似实时生效;
希望将配置进行统一管理,而非放入各应用的配置文件中;
对于某些应用系统,其某些配置变更比较频繁,规律;
...。
-
分布式配置平台需要满足的一些基本特性
对于一个可靠的分布式配置平台,大致应该满足一些基本特性,如:
高可用性:服务器集群应该无单点故障,即只要集群中还有存活的节点,就能提供服务;
容错性:容错性主要针对客户端,应保证即便在配置平台不可用时,也不影响客户端的正常运行;
高性能:对于配置平台,主要操作则是获取配置,不能因为获取配置给应用带来不可接受的损失;
可靠的存储:这包括数据的备份容灾,一致性等,通过数据库和一些运维手段可以解决;
近似实时生效:对于配置的变更,客户端应用能够及时感知;
负载均衡:为了尽量提升服务器集群的性能及稳定性,应尽量保证客户端的请求能尽量均衡负载到各服务器节点;
扩展性:服务器集群应该保证做到无感扩容,以提升集群服务能力;
...。
-
分布式配置平台Diablo的设计与实现
-
Diablo架构设计
Diablo的架构设计比较简单轻量,如图:
Apps:及各类业务应用实例;
Servers:为客户端提供获取配置服务的Server集群;
Redis Storage Service:用作数据存储。
-
Diablo模型设计
应用(App):通常,对于一个用于各外部系统的平台,都可以抽象这些系统为应用,用于标识或是分组。对于应用环境,个人觉得应交由应用去处理,而不是在平台为各应用提供不同环境,比如可以通过应用名称就能区分不同环境。除此外,通常需要让应用提供一些安全方面的配置,如签名Key,加密Key等,可用于在与外部应用交互中作一些安全处理(如签名,加密等);
配置项(Config):对于配置平台,其数据模型比较简单,核心数据就是配置项。配置项除了基本的配置名称和配置值外,通常还需要有用于判定配置项是否变更的字段,可以用MD5值等。
-
对等的服务器集群
Diablo集群中的Server被视为是对等的,即各节点没有主从(Master/Slave)关系,是逻辑相等的,这样就避免了Master/Slave架构带来的问题,如数据同步延迟,数据丢失等问题。
-
高性能处理
客户端应用获取配置时,仅会从本地缓存中获取,开发人员在控制台更改配置后,会通知客户端刷新缓冲;
-
使用Redis作存储
配置平台本身并没有太多复杂的关联关系,因此使用NoSQL也能满足常用的查询。在设计存储Key上,因尽量保证Key的简洁清晰,比如,存储应用记录,可以以apps:1来标识一条记录,而存储结构最好使用hash,而不是使用JSON字符串。在使用Redis时,diablo避免使用了一些特殊函数,如管道,事务等,因为这些函数在一些Redis的高可用解决方案(如Redis Cluster,Redis Proxy等)中通常不支持,这样用户可以自由使用不同的Redis高可用方案。
-
客户端的实现
重试等待:diablo会通过重试等待等机制保证,在服务端集群不可用时,也不会影响客户端应用的正常运行,而是等待集群恢复;
请求负载:为了使服务器集群的各节点的
负载尽量均衡,在客户端进行请求处理前,服务端会为客户端分配一个可用的Server节点,后续的请求将在该节点上,这里使用的负载算法是
一致性哈希;
额外参数:通常对于客户端实现,在与服务器交互过程中,除了必要的数据外,还会携带一些额外参数,如客户端版本号(为后期作兼容性处理),语言类型(后端可针对不同语言进行处理)等。
-
配置更新实时生效
配置更新实时生效是配置平台的核心功能之一,对于获取配置的方式,大致有两种模式:
Pull模式:即客户端主动向服务端拉取配置信息,通常是客户端定时轮询拉取。这种方式最简单稳定,但是存在时间间隔问题,间隔太长,配置延迟更新越大,间隔太短对服务器会造成过多的压力;
Push模式:即当配置发生变更时,服务端主动推送更新至各个客户端。这种方式能保证配置更新实时生效,但需要在客户端/服务端建立长链接,服务端需要处理各种异常情况和协议规范,保证更新能实时推送成功,实现起来相对麻烦些。
diablo使用了特殊Pull模式,即(长轮询)Long Pulling。当客户端发起Http请求后,服务端接收处理完请求,并不会立即返回客户端,而是等待一定条件发生或超时后才返回客户端,这里的一定条件就是当有配置发生变更时,这样就有效减少了客户端请求,也达到了实时生效的目的。对于Java长连接的实现,主要使用了Servlet规范中的AsyncContext,使得服务端接收到请求后,并不会让Servlet容器立即返回客户端,而是当调用AsyncContext.complete()方法时,才会返回。
-
客户端语言类型
diablo默认实现了Java语言的客户端,希望以后能支持node,go,python等语言。由于diablo仅通过Http接口提供服务,不同语言只要遵循API接口,即可实现不同的语言版本。若读者有意实现,可参考该客户端规范,下图为客户端与服务端的交互过程:
-
diablo实践建议
服务器集群部署:对于一个高可用的服务器集群,建议可以nginx等代理服务器作转发,这样在服务器集群发生变化时,客户端应用也不用更新任何配置,如:
应用环境区分:对于需要区分不同环境的应用,可以通过不同的应用名称作区分,如app_dev,app_test。但对于生产环境,都建议与其他环境隔离,独立部署。
-
总结
以上,则是有关分布式配置管理平台设计与实现的相关细节,也欢迎issue和fork项目diablo。