腾小云导读
对很多的开发者而言,处理运营素材反复变更等需求场景不是一件轻松的事。开发者通常需要定制化地进行数据清理、格式转换和工具开发等等。在这个时候,建设分布式配置系统就显得尤为重要。本文旨在分析分布式配置系统的必要性、可行性及其关键约束,并介绍一个在微信研发体系下的分布式配置系统研发设计。这个系统成功避免了大量的配置数据的时间消耗,希望能对广大开发爱好者有所启发。
目录
1 配置的定义
2 系统约束
2.1 数据模型
2.2 访问模型
2.3 系统约束
2.4 安全约束
3 系统的演进
3.1 单机配置文件
3.2 集中式配置文件中心
3.3 数据库配置存储
4 方案思考
4.1 物理模型
4.2 安全管理
4.3 配置系统SDK
4.4 异步化
4.5 业务插件
4.6 推与拉
4.7 快速最终一致
4.8 请求单调
4.9 灰度发布
4.10 效率提升
4.11 可用性提升
5 境外支付配置系统
01、配置的定义
软件建模的本质是对现实世界(人、事、物及规则)的映射,映射的产出物是编程系统和配置。配置为开发者提供了动态修改程序运行时行为的能力,即常说的“系统运行时飞行姿态的动态调整”,究其根源则是“人类无法掌控和预知一切,映射到软件领域上,人总是需要对系统的某些功能特性预留出一些控制的线头,以便在未来需要的时候,可以人为地拨弄这些线头从而控制系统的行为特征。”
因此,本文所指的配置特指内部运营人员产生的数据(广义的系统运营人员,包括产品、运营、研发等),作为输入参数而作用于编程系统(包括实时系统、批跑程序以及数据任务等)。
归纳而言,配置通常包含如下三种:
a. 环境配置,定义了应用程序运行时的环境相关参数,如 IP、Port 等;
b. 应用配置,定义了应用程序自身相关的参数或者信息安全控制等,如初始内存分配大小、数据库连接池大小、日志级别、账号密码等;(密码、证书这类事物不要放在配置系统中,应当走统一加解密服务)
c. 业务配置,定义了应用程序所执行的业务行为数据,比如最常见的功能开关,参与活动的商户名单等。
02、系统约束
2.1 数据模型
配置最基本的数据单元是 key=value(即配置项),比如功能开关通常就是最简单的类型,用 boolean 型值来影响程序执行链路(不考虑灰度的情况)。然而只有 key-value 类型是不足的,比如 DB 的连接配置就包含了 ip、port、username、password 等字段,在 ini 文件的实现中即是不同配置项来组成,它们在逻辑上是属于同一个配置对象,因此基于面向对象的设计思路,key=object才是更通用的配置模型,在物理实现中可以表现为 json 或者 xml,或者 protobuf message。
object 类型的数据既可以是平坦的,也可以是多层次(嵌套)的。在实际的业务应用中,平坦类型的数据有其特殊性,即其通常条目较多,最典型的数据是白名单,可能多达上万条。
线下,内部运营人员通过 excel 进行这类数据的管理,如果只是粗糙地将其打包成一个对象,那么过大的数据可能会导致系统效率的下降(不是配置的写入效率下降,就是配置读出效率下降),因此需使用 array of plain object 来表达,即 key=table 类型的数据。
2.2 访问模型
区别于产品用户产生的数据,配置系统的数据流是单向的,离线系统与实时系统结合,而读写分离的(异步写、实时读)。最终要搭建的分布式配置系统,它的系统设计,也必然是建立在这类访问模型上的。
2.3 系统约束
显然,内部运营人员作为生产者,所有的配置肯定都是文本类型的(Readable),并且数据量少(相对于用户、系统等生产数据而言),对存储空间需求少,更新频次低。换一种说法,在整个配置系统架构中,输入方就如同键盘相对于 CPU 而言是超慢速设备,它们对系统的易用性、易操作性、安全性要求更高。
用户画像系统,它满足部分配置系统的访问模型,即数据流是单向的,离线系统负责写入画像数据,而实时系统负责读数据。但是首先它的数据生产者通常是离线任务,而非运营人员;再次,它涉及的数据量是巨大的,通常需要定制的存储引擎。所以配置系统与之相比,不能相提并论。
相较而言,配置系统的消费者则是高频的读访问,对系统的吞吐量、延时、网络流量、可用性、一致性、请求单调性都有更高的要求。后续会逐一展开进行深入地探讨。
配置系统的设计应当充分考虑上述的数据模型、访问模型以及系统约束。
2.4 安全约束
正因为配置可以轻易地调整系统运行期行为,因此配置的安全性至关重要。实现安全的必要条件是:让正确的人,以正确的方式,在正确的时机,发布正确的配置。因此,配置系统不但要支持灰度发布的基本能力,还要在权限管理、权限粒度管理、配置变更审核、审计、历史版本等方面都要加强建设。
03、系统的演进
3.1 单机配置文件
在单机系统时代,大家基本上都是使用配置文件来存储配置数据(比如 ini 文件、xml 文件等)。配置文件易于理解、便于实现、可用性高,因此进入分布式集群时代,仍在广泛使用。
缺点 | 表现 |
易用性差 | 主要体现为表达的数据类型单一,比如 ini 只能管理配置项,即 key=value 类型数据;而如果使用 xml 文件来管理 key=table 类型数据,那么文件内容的初始化效率就会低下,容易出错, |
可操作性差 | 配置文件基本只能由开发者来进行修改并且发布,产品、运营的常规业务素材变更工作就不得不卷入其中,由开发执行,对业务的流程效率有严重的影响。 |
正确性、安全性难以保障 | 正因为配置文件的易于实现,很多团队疏忽了运营系统的建设,研发人员随意修改、恶意修改配置文件的情况无法杜绝,细粒度的权限管理、操作的审核、审计就无从谈起。 |
发布效率低下 | 配置文件是单机部署的,在集群规模较大的情况下,配置文件的任意变更都需要经过漫长的灰度发布过程发布到全网,如果配置文件是静态加载的,还需要重启二进制,需要消耗研发、运维人员较多的精力。 |
文件一致性难以保障 | 在发布配置变更的过程中,如果集群中出现宕机情况,会导致不同机器间的配置出现差异,而且没有自动校正的能力,需依赖于人员或者运维系统的支持,进而导致业务进入未定义的行为。 |
如果说易用性、可操作性、正确性、安全性可以通过搭建运营系统来进行改进是优点,而发布效率低下、文件一致性难以保障则是单机配置文件的致命缺点,究其本质,是因为单机配置文件系统是被动的、离散的可以随意接受外界的变更,而没有主动的能力。
3.2 集中式配置文件中心
由此,出现了集中式的配置文件系统,针对性地解决了上述的问题,开发人员将配置文件存储到独立的第三方服务(典型的由 ZooKeeper 进行管理,也有部分团队自行实现微服务管理),然后由 agent 周期性地将配置拉取到本地进行缓存(拉),或者通过事件的订阅通知能力来将变更发布到相应集群(推)。
集中式配置文件系统针对性地解决了发布变更效率问题以及配置文件一致性保障问题。然而在一些所知的应用案例中,仍然存在如下的问题亟需解决:
- 一致性粒度粗
集中式配置文件只能确保分布式集群达到最终一致(时间取决于拉、推的频率及速率),却无法保证任一时刻,对任一配置,所有进程、线程、协程能看到相同的数据,这通常会导致出现无法预期的业务失败;
- 无法保证请求单调性
在一次业务请求中,一般希望用户看到的配置内容是静态的,如果中间发生变更,可能带来业务失败,严重的会导致用户数据状态错乱;基于集中式配置文件系统的配置通常是动态加载的,配置的变更可能随时的反映到实时系统中,导致一次业务请求先后看到不同的数据状态;
- 安全性仍无法彻底保障
虽然集中式配置文件可以修改控制权限,但是在消费者端,技术人员仍然可以手动的修改本地配置文件 cache 来影响程序的运行行为;
- 无法支持灰度能力
配置文件变更的下发是全量的,如果要支持灰度发布的能力,就需要卷入业务方自行实现;
配置文件系统,无论是单机配置文件,还是集中式配置文件,存在的问题,归根结底,是由配置文件这个载体以及集中式配置文件系统的管道定位决定的,所以进行精细化管理的成本高;
- 配置文件的可视、可读能力
对生产者而言是很重要的,但对消费者却是无关紧要的,因此全链路都由配置文件作为载体反而会导致加载效率低下(比如应对千万级黑白名单,或者业务方实时请求链路动态加载);
- 配置文件难以安全、便利管理元信息
为了实现一致性、单调性、安全性,配置需要一些元数据信息管理(下文展开详述),但是配置文件系统没有这种能力,除非业务方使用高成本自行实现。
- 配置文件的数目与配置的数量息息相关
随着时间的发展,配置文件数目膨胀,会带来新的运营问题;
- 集中式配置文件系统通常只把自己定位成管道
既不理解也不维护配置文件的内容,agent 功能单一,业务消费方不与系统直接交互,而是只能看到配置文件,虽然松耦合,可以提高可用性,但也让业务方仍需投入不少的开发成本来处理配置文件。
配置文件只是配置的物理载体,上述缺点并非无法克服,只是在基于配置文件的配置系统下,克服上述缺点的成本高,需要更多的使用约束,以及外围配套。
3.3 数据库配置存储
对结构复杂、类型较多的配置系统,业务研发者通常不会直接使用配置文件来承载,而是使用数据库(关系型或非关系型)表来存储配置,然后再使用编写工具进行数据的导入。这种存储方案克服了配置文件的部分问题,对配置有更精细化的管理。但是仍存在明显的不足,即高度的定制化,不可复用,重复开发成本高。
因此,需要对此进行完善,将配置的存储、读、写、管理等过程提炼共性,通用化、平台化。
04、 方案思考
4.1 物理模型
既然配置文件难以精细化管理,且具备易侵入的物理实体特性(本地文件),所以就需要新的数据结构来承载配置。前文讨论过,配置有两种数据模型,分别是 key=object以及 key=table。对使用者而言,配置必须是可视、可读、易管理的。为了达成这目的,只需在内部运营人员与配置系统核心之间搭一套设计良好的运营系统即可。那么在后端呢?对消费者而言,最注重传输、计算的效率,同时也要与微服务框架的对齐,protobuf message 无疑是最佳的形式。
然而 protobuf 无法自主解释,在没有 message 定义的情况下,既没办法将文本性的配置转换成 pb 二进制流,也没办法反序列化。因此我们必须将业务的 message 定义上提到运营系统,然而 protobuf 却对可视化编辑不太友好。一个可行的思路是基于 JSON 数据进行配置的定义、可视化操作、传输及存储,只有到达业务侧才进行数据类型的转换。
4.2 安全管理
搭建一套配置运营系统,让其成为运营人员管理配置的唯一入口,轻松就可以得到很高的回报。而且还可以基于运营系统进行各种配置以及安全加固,如配置的变更必须具备相应的权限,只有通过审核才能应用到系统,所有的操作都要有审计的能力、配置的历史版本快速可查等。同时灰度、回退等能力也需要基于运营系统进行操作。
4.3 配置系统SDK
上文提及集中式配置文件系统的管道定位,agent 只负责定期的拉取配置然后缓存到本地的文件系统。业务系统与配置系统松耦合。一般认为配置文件仍然具有较高的开发成本,对业务方而言,最佳的开发形式应当是:
int GetConfig<Message>(const std::string& key, ::google::protobuf::Message& msg);
而不需要再去理解文件内容、形式。
并且有必要为业务方提供一套配置系统的 SDK,将配置系统的细节、数据结构等信息都屏蔽起来,让业务方只看到配置的 Protobuf Message 对象。
在 SDK 的基础上,消费者只需轻度介入(业务插件,见下),就可以完成协议转换、配置缓存、进程,线程,协程的快速最终一致、请求单调、灰度发布的能力。
配置系统 SDK 是精细化管理的基础,可以通过维护配置本身内容之外的配置元数据信息来完成上述能力。
4.3 异步化
异步化是配置 SDK 的关键。很多本地缓存的更新是周期性由实时链路请求负责,虽易于实现,但效率上仍存在问题,尤其考虑到还需要对配置进行配置业务逻辑的处理。因此,最佳方案应当是通过异步过程来进行配置的加载、初始化及其他逻辑处理。
异步带来的问题是异步过程与实时请求的并发问题,即异步过程在进行配置变更中,应如何处理实时链路的读请求,这是一个工程问题,会另文讨论,一个可行的思路是多版本及引用计数技术。
4.4 业务插件
异步提供的另外一个好处是,业务可以在配置生效的时候进行一些初始化动作,例如进行配置正确性校验,以及搭建业务适合的数据结构。例如业务白名单在 pb 中只是一个数组,如果业务进行命中查找,代价比较高。业务最期望的方式还是使用 map 来存储。因此配置 SDK 异步化,就为业务插件能力提供了基础。
4.5 推与拉
一般更倾向于配置 SDK 主动拉取配置的更新。推与拉的辩证在于效率和可用性。推比较高效,不存在无用的网络消耗。但是推又引入了新的系统依赖(即事件中心)。“如无必要,勿增实体”,基于这样的思想,所以一般由 SDK 周期性主动拉取。至于效率,完全可以通过各种工程的手段加以优化,达到可以接受的程度。
当然这也取决于系统规模,如果是公司机的配置系统,而不是部分中心级,那么也要认真地思考“推”或者“推拉”结合的模式。
4.7 快速最终一致
无论是单机配置文件系统,还是集中配置文件系统,都存在严重的不一致问题。对一次配置变更,基本上都需要很长的时间才能达到最终一致(即所有并发看到相同的数据状态)。
一个可行的思路是使多版本以及定时生效。配置只有在未来的某个时间(该时间内SDK 已经拉到了最新数据)才对外可见。至于如何确保所有 SDK 都拉到了数据,这涉及到可用性的问题,后续会另文讨论。
4.8 请求单调
定时生效没办法解决请求单调性的问题。请求单调性是指实时服务处理一次请求,在请求调用栈的过程中,读到的配置内容必须是静态、没有变动的,即使中间有待生效数据变成了生效数据。一个思路是可以通过线程私有变量(协程私有变量)缓存配置版本。
4.9 灰度发布
在配置 SDK 多版本能力的基础上,实现灰度发布的能力是轻而易举的。灰度发布的能力,不过就是选择生效配置版本的能力,如果本机、本角色、本请求业务 Key(如用户、商户、订单)等命中灰度范围,则使用新版本,否则使用原版本。
4.10 效率提升
效率提升包括降低网络传输数据量、降低配置存储服务的压力,这些都是具体的工程手段,不在本理论篇内讨论。
4.11 可用性提升
分布式系统的可用性提升是老生常谈的话题,为了聚焦于配置系统独特的能力,本篇不专门进行讨论。
However,尽量减少系统中的单点,是一个重要的原则。在前节”推与拉“中也有涉及。同时为了业务的可用性,第三方配置系统的运营能力、故障主动发现能力、故障通知能力、再现及定位能力也非常重要。这是重复造轮子的一个不得已的重要原因,很多团队软件可能做得不错,但服务能力(主要指运营能力)却有点不尽人意。
05境外支付配置系统
本篇的技术方案目前已服役两年的时间,有上百种业务配置,包括功能开关,但更多的是运营素材配置。
举一个例子,在开发配置系统之前,为了导入一次运营素材配置,需要研发投入2天的时间,数据的审核都是在线下进行(质量可想而知)。目前基于配置系统,小助手10分钟之内完成批量导入、程序自动校验(依赖于业务插件)、审核审批,研发全程无需介入。
同时也为业务方提供了易于使用的配置 SDK,支持多种配置类型,允许业务自定义数据结构,支付定时生效、灰度发布的能力,并且满足请求单调性。
以上是本次分享全部内容,欢迎大家在评论区分享交流。如果觉得内容有用,欢迎转发~
-End-
原创作者|杨平安
技术责编|杨平安
「分布式系统」近年来受到较大关注。有些人认为分布式的优势在于可靠性,就算是一个非常强大的单机系统,在可靠性上也几乎无法与之匹敌。而有部分开发者认为分布式系统虽集成了「集体」的力量、提供了更强大的数据处理能力,但在人多力量大的同时也引入了更多问题,如数据一致性困扰等等。
你怎么看待分布式系统?它有什么优点和缺陷?在什么情境下最适用?欢迎在腾讯云开发者公众号评论区分享你的看法。我们将选取1则最有创意的分享,送出腾讯云开发者-马克杯1个(见下图)。6月25日中午12点开奖。