热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

微信研发体系下的分布式配置系统设计概要

作者:ypaapyyang,腾讯WXG后台开发工程师,个人公众号:码农课代表。本文旨在分析分布式配置系统的必要性、可行性,及其关键约束,并介绍一款基于该系列分析,在微信

作者:ypaapyyang,腾讯 WXG 后台开发工程师,个人公众号:码农课代表。







本文旨在分析分布式配置系统的必要性、可行性,及其关键约束,并介绍一款基于该系列分析,在微信研发体系下的实践尝试。


前言

对很多的业务开发同学而言,对运营素材的处理不是一件轻松的事,通常需要定制化的进行数据的清理、格式的转换、工具的开发。笔者就曾过这样一段不愉快的回忆,为了导入一次性的近十种类型的配置数据,就耗去了两天的时间。如果说这段经历有何价值的话,那就是促使我思考分布式配置系统,并且在工作中实践,使自己避免再次陷入如此槽糕的过程中。

本文正是旨在分析分布式配置系统的必要性、可行性,及其关键约束,并介绍一款基于该系列分析,在微信研发体系下的实践尝试。

配置的定义

我们清楚软件建模的本质是对现实世界(人、事、物及规则)的映射,映射的产出物即包括编程系统和配置。配置为我们提供了动态修改程序运行时行为的能力,即常说的“系统运行时飞行姿态的动态调整”,究其根源则是“我们人类无法掌控和预知一切,映射到软件领域上,我们总是需要对系统的某些功能特性预留出一些控制的线头,以便我们在未来需要的时候,可以人为的拨弄这些线头从而控制系统的行为特征。”

因此,本文所指的配置特指内部运营人员产生的数据(广义的系统运营人员,包括产品、运营、研发等),并且作为输入参数而作用于编程系统(包括实时系统、批跑程序以及数据任务等)。

归纳而言,配置通常包含如下三种:

a. 环境配置,定义了应用程序运行的环境相关参数,如 IP、Port 等;

b. 应用配置,定义了应用程序自身相关的参数或者信息安全控制等,如初始内存分配大小、数据库连接池大小、日志级别、账号密码等;(密码、证书这类东西肯定不要放在配置系统中,而应当走统一加解密服务)

c. 业务配置,定义了应用程序所执行的业务行为数据,比如最常见的功能开关,参与活动的商户名单等。

系统约束


数据模型

配置最基本的数据单元是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类型的数据。

访问模型

相别于产品用户产生的数据,配置系统的数据流是单向的,离线系统与实时系统结合而读写分离(异步写、实时读)的。最终我们要搭建的分布式配置系统,它的系统设计,也必然是建立在这类访问模型上的。


系统约束

显然,内部运营人员作为生产者,所有的配置肯定都是文本类型的(Readable),并且数据量少(相对于用户、系统等生产数据而言),对存储空间需求少,更新频次低。可以这么理解,在整个配置系统架构中,输入方就如同键盘相对于 CPU 而言是超慢速设备,他们对系统的易用性、易操作性、安全性要求更高。


我们思考下用户画像系统,它部分满足配置系统的访问模型,即数据流是单向的,离线系统负责写入画像数据,而实时系统读数据。但是首先它的数据生产者通常是离线任务,而非运营人员;再次,它涉及到的数据量是巨大的,通常需要定制的存储引擎。配置系统与之相比,不可同日而语。

相较而言,配置系统的消费者则是高频的读访问,对系统的吞吐量延时网络流量可用性、一致性、请求单调性都有更高的要求。后续我们逐一展开深入的思考。

配置系统的设计应当充分考虑到上述的数据模型、访问模型以及系统约束。(比较奇怪的是,笔者在查阅相关配置系统实现时,鲜少看到有针对一致性、请求单调性的讨论。这也是促使笔者撰写本文的原因)

安全约束

正因为配置可以轻易的调整系统运行期行为,因此配置的安全性至关重要。实现安全的必要条件是:让正确的人,以正确的方式,在正确的时机,发布正确的配置。因此,配置系统不但要支持灰度发布的基本能力,还要在权限管理、权限粒度管理、配置变更审核、审计、历史版本等方面都要加强建设。

系统的演进


单机配置文件

在单机系统时代,我们基本上都是使用配置文件来存储配置数据(比如 ini 文件、xml 文件等)。配置文件易于理解、便于实现、可用性高,因此进入分布式集群时代,仍在广泛使用。

然而配置文件存在诸多的缺点,包括:


  • 易用性差,主要体现为表达的数据类型单一,比如 ini 只能管理配置项,即 key=value 类型数据;而如果使用 xml 文件来管理 key=table 类型数据,那么文件内容的初始化效率低下,容易出错,难以维护;


  • 可操作性差,配置文件基本只能由开发来进行修改并且发布,产品、运营的常规业务素材变更工作就不得不卷入开发执行,对业务的流程效率有严重的影响;


  • 正确性、安全性难以保障,正因为配置文件的易于实现,导致很多团队疏忽了运营系统的建设,研发人员随意修改、恶意修改配置文件的情况无法杜绝,细粒度的权限管理、操作的审核、审计无从谈起;


  • 发布效率低下,配置文件是单机部署的,在集群规模较大的情况下,配置文件的任意变更都需要经过漫长的灰度发布过程发布到全网,如果配置文件是静态加载的,还需要重启二进制,需要消耗研发、运维人员较多的精力;


  • 文件一致性难以保障,在发布配置变更的过程中,如果集群中出现宕机情况,会导致不同机器间的配置出现差异,而没有自动校正的能力,依赖于人员或者运维系统的支持,从而导致业务进入未定义的行为。



如果说易用性、可操作性、正确性、安全性可以通过搭建运营系统来进行改进,而发布效率低下、文件一致性难以保障则是单机配置文件的致命弱点,究其本质,是因为单机配置文件系统是被动的、离散的接受外界的变更,而没有主动的能力。

集中式配置文件中心

由此,出现了集中式的配置文件系统,针对性的解决了上述的问题,开发人员将配置文件存储到独立的第三方服务(典型的由 ZooKeeper 进行管理,也有部分团队自行实现微服务管理),然后由 agent 周期性的将配置拉取到本地进行缓存(),或者通过事件的订阅通知能力来将变更发布到相应集群()。

集中式配置文件系统针对性的解决了发布变更效率问题以及配置文件一致性保障问题。然而在笔者所知的应用案例中,仍然存在如下的问题亟需解决:


  • 一致性粒度粗,集中式配置文件只能确保分布式集群达到最终一致(时间取决于拉、推的频率及速率),却无法保证任一时刻,对任一配置,所有进程、线程、协程看到相同的数据,而这通常会导致出现不预期的业务失败;


  • 无法保证请求单调性,在一次业务请求中,我们希望用户看到的配置内容是静态的,如果中间发生变更,可能带来业务失败,严重的导致用户数据状态错乱;而基于集中式配置文件系统的配置通常是动态加载的,配置的变更可能随时的反应到实时系统中,导致一次业务请求先后看到不同的数据状态;


  • 安全性仍无法彻底保障,虽然集中式配置文件的修改可以控制权限,但是在消费者机器上,开发者仍然可以手动的修改本地配置文件 cache 来影响程序的运行行为;


  • 无法支持灰度能力,配置文件变更的下发是全量的,如果要支持灰度发布的能力,就需要卷入业务方自行实现;


配置文件系统,无论是单机配置文件,还是集中式配置文件,存在的问题,归根结底,是由于配置文件这个载体以及集中式配置文件系统的管道定位决定的,从而导致进行精细化管理的成本高:


  • 配置文件的可视可读能力对生产者而言是重要的,但对消费者却是无关紧要的,因此全链路都由配置文件作为载体反而可能导致加载效率低下(比如应对千万级黑白名单,或者业务方实时请求链路动态加载);


  • 配置文件难以安全、便利管理元信息,为了实现一致性、单调性、安全性,配置需要一些元数据信息管理(下文展开详述),但是配置文件系统没有这种能力,除非业务方使用高成本自行实现;


  • 配置文件的数目与配置的数量息息相关,随着时间的发展,配置文件数目膨胀,带来新的运营问题;


  • 集中式配置文件系统通常只把自己定位成管道(据笔者所知),即不理解也不维护配置文件的内容,agent 功能单一,业务消费方不与系统直接交互,而是只看到配置文件,虽然松耦合可以提高可用性,但也让业务方仍然投入不少的开发成本来处理配置文件。


配置文件只是配置的物理载体,上述缺点并非无法克服,只是在基于配置文件的配置系统下,实现上述能力的成本高,需要更多的使用约束,以及外围配套。

数据库配置存储

对结构复杂、类型较多的配置,业务研发同学通常也不会直接使用配置文件来承载,而是使用数据库(关系型或非关系型)库表来存储配置,然后再编写工具进行数据的导入。这种存储方案克服了配置文件的部分问题,对配置有更精细化的管理。但是也存在明显的不足,即高度的定制化,不可复用,重复开发高。因此,我们需要对此进行完善,将配置的存储、读、写、管理等过程提炼共性,通用化、平台化。

方案思考


物理模型

既然配置文件难以精细化管理,且具备易侵入的物理实体(本地文件),我们需要新的数据结构来承载配置。前文我们讨论过,配置有两种数据模型,分别是key=object以及key=table。对使用者而言,配置必须是可视、可读、易管理的。为了达成这目的,我们只需在内部运营人员与配置系统核心之间搭一套设计良好的运营系统即可。那么在后端呢?对消费者而言,最注重传输、计算的效率,同时为了与微服务框架的对齐,protobuf message无疑是最佳的形式。

然而 protobuf 无法自解释,在没有 message 定义的情况下,我们即没办法将文本性的配置转换成 pb 二进制流,也没办法反序列化。因此必须将业务的 message 定义上提到运营系统,然而 protobuf 却对可视化编辑不太友好。因此一个可行的思路是基于JSON 数据进行配置的定义、可视化操作、传输及存储。只有到达业务侧才进行数据类型的转换。


安全管理

搭建一套配置运营系统,让之成为运营人员管理配置的唯一入口,轻松就可以得到很高的回报。我们可以基于运营系统进行各种配置安全加固,如配置的变更必须具备相应的权限,而且只有通过审核才能应用到系统,所有的操作都要有审计的能力,配置的历史版本快速可查等。

同时灰度、回退等能力也需要基于运营系统进行操作。


配置系统 SDK

上文提及,集中式配置文件系统的管道定位,agent 只负责定期的拉取配置然后缓存到本地的文件系统。业务系统与配置系统松耦合。我们认为配置文件仍然具有较高的开发成本,对业务方而言,最佳的开发形式应当是:

int GetConfig(const std::string& key, ::google::protobuf::Message& msg);

而不需要再去理解文件内容、形式。那么我们就有必要为业务方提供一套配置系统的 SDK,将配置系统的细节、数据结构等信息都屏蔽起来,让业务只看到配置的 Protobuf Message 对象。

在 SDK 的基础上,消费者只需轻度介入(业务插件,见下),我们就可以完成协议转换、配置缓存、进程,线程,协程快速最终一致、请求单调、灰度发布的能力。

配置系统 SDK 是精细化管理的基础,我们可以通过维护配置本身内容之外的配置元数据信息来完成上述能力。


异步化

异步化是配置 SDK 的关键。很多本地缓存的更新是周期性的由实时链路请求负责,易于实现,但效率上存在问题,尤其考虑到我们还需要对配置进行配置业务逻辑的处理。因此,最佳方案应当是通过异步过程来进行配置的加载、初始化及其它逻辑处理。

异步带来的问题是异步过程与实时请求的并发问题,即异步过程在进行配置变更过程中,应如何处理实时链路的读请求,这是一个工程问题,我们会另文讨论,一个可行的思路是多版本及引用计数技术


业务插件

异步为我们提供的另外一个好处是,业务可以在配置生效的时候进行一些初始化动作,比如进行进行配置正确性校验,以及搭建业务适合的数据结构。比如业务白名单在 pb 中只是一个数组,如果业务进行命中查找,代价比较高。业务最期望的方式肯定还是使用 map 来存储。因此配置 SDK 异步化,就为业务插件能力提供了基础。


推与拉

我们更倾向于配置 SDK 主动拉取配置的更新。推与拉的辩证在于效率和可用性。推比较高效,不存在无用的网络消耗。但是推又引入了新的系统依赖(即事件中心)。如无必要,勿增实体,基于这样的思想,我们倾向于由SDK 周期性主动拉取。至于效率,完全可以通过各种工程的手段加以优化,达到可以接受的程度。

当然这也取决于系统规模,如果我们要讨论的是公司机的配置系统,而不是部分中心级,那么我们也会认真的思考推或者推拉结合的模式。

快速最终一致

无论是单机配置文件系统,还是集中配置文件系统,都存在严重的不一致问题。对一次配置变更,基本上都需要很长的时间才能达到最终一致(即所有并发看到相同的数据状态)。

一个可行的思路是多版本以及定时生效。配置只有在未来的某个时间(该时间内 SDK 已经拉到了最新数据)才对外可见。至于如何确保所有 SDK 都拉到了数据,这涉及到可用性的问题,我们另文讨论。


请求单调

定时生效没办法解决请求单调性的问题。请求单调性是指,实时服务处理一次请求,在请求的调用栈过程中,读到的配置内容必须是静态、没有变动的,即使中间有待生效数据变成了生效数据。一个思路是我们可以通过线程私有变量(协程私有变量)缓存配置版本即可。

灰度发布

在配置 SDK 多版本能力的基础上,实现灰度发布的能力也是轻而易举的。灰度发布的能力,不过就是选择生效配置版本的能力,如果本机、本角色、本请求业务 key(如用户、商户、订单)等命中灰度范围,则使用新版本,否则使用原版本。


效率提升

效率提升包括降低网络传输数据量、降低配置存储服务的压力,这些都是具体的工程手段,我们不在本理论篇内讨论。

可用性提升

分布式系统的可用性提升是老生常谈的话题,为了聚焦于配置系统独特的能力,我们本篇不专门进行讨论。

(However,尽量减少系统中的单点,是一个重要的原则。在前节”推与拉“中也有涉及。同时为了业务的可用性,第三方配置系统的运营能力、故障主动发现能力、故障通知能力、再现及定位能力也非常重要。也这是重复造轮子的一个不得已的重要原因,很多团队软件可能作的不错,但服务能力(主要指运营能力)却有点差强人意。)

加入我们

境外支付团队在不断追求卓越的路上寻找同路人,岗位需求:

28605-微信支付境外支付前端开发工程师(深圳)

10月24日,约你来腾讯滨海大厦



10.16 世界粮食日


我们用AI技术助力了一场光盘行动

‍欢迎关注视频号 腾讯程序员

为你分享有趣、实用的腾讯产品&技术‍



推荐阅读
  • 深入解析Hadoop的核心组件与工作原理
    本文详细介绍了Hadoop的三大核心组件:分布式文件系统HDFS、资源管理器YARN和分布式计算框架MapReduce。通过分析这些组件的工作机制,帮助读者更好地理解Hadoop的架构及其在大数据处理中的应用。 ... [详细]
  • Java项目分层架构设计与实践
    本文探讨了Java项目中应用分层的最佳实践,不仅介绍了常见的三层架构(Controller、Service、DAO),还深入分析了各层的职责划分及优化建议。通过合理的分层设计,可以提高代码的可维护性、扩展性和团队协作效率。 ... [详细]
  • 嵌入式开发环境搭建与文件传输指南
    本文详细介绍了如何为嵌入式应用开发搭建必要的软硬件环境,并提供了通过串口和网线两种方式将文件传输到开发板的具体步骤。适合Linux开发初学者参考。 ... [详细]
  • 深入解析Serverless架构模式
    本文将详细介绍Serverless架构模式的核心概念、工作原理及其优势。通过对比传统架构,探讨Serverless如何简化应用开发与运维流程,并介绍当前主流的Serverless平台。 ... [详细]
  • Python + Pytest 接口自动化测试中 Token 关联登录的实现方法
    本文将深入探讨 Python 和 Pytest 在接口自动化测试中如何实现 Token 关联登录,内容详尽、逻辑清晰,旨在帮助读者掌握这一关键技能。 ... [详细]
  • 深入解析Spring Cloud微服务架构与分布式系统实战
    本文详细介绍了Spring Cloud在微服务架构和分布式系统中的应用,结合实际案例和最新技术,帮助读者全面掌握微服务的实现与优化。 ... [详细]
  • 本文探讨了现代分布式架构的多样性,包括高并发、多活数据中心、容器化、微服务、高可用性和弹性架构等,并介绍了与这些架构相关的重要管理技术,如DevOps、应用监控和自动化运维。文章还深入分析了分布式系统的核心概念、主要用途及类型,同时对比了单体应用与分布式服务化的优缺点。 ... [详细]
  • 本文详细介绍了Java的安装、配置、运行流程以及有效的学习方法,旨在帮助初学者快速上手Java编程。 ... [详细]
  • Spring Cloud学习指南:深入理解微服务架构
    本文介绍了微服务架构的基本概念及其在Spring Cloud中的实现。讨论了微服务架构的主要优势,如简化开发和维护、快速启动、灵活的技术栈选择以及按需扩展的能力。同时,也探讨了微服务架构面临的挑战,包括较高的运维要求、分布式系统的复杂性、接口调整的成本等问题。最后,文章提出了实施微服务时应遵循的设计原则。 ... [详细]
  • 解决TensorFlow CPU版本安装中的依赖问题
    本文记录了在安装CPU版本的TensorFlow过程中遇到的依赖问题及解决方案,特别是numpy版本不匹配和动态链接库(DLL)错误。通过详细的步骤说明和专业建议,帮助读者顺利安装并使用TensorFlow。 ... [详细]
  • 简化报表生成:EasyReport工具的全面解析
    本文详细介绍了EasyReport,一个易于使用的开源Web报表工具。该工具支持Hadoop、HBase及多种关系型数据库,能够将SQL查询结果转换为HTML表格,并提供Excel导出、图表显示和表头冻结等功能。 ... [详细]
  • 鼠标悬停出现提示信息怎么做
    概述–提示:指启示,提起注意或给予提醒和解释。在excel中会经常用到给某个格子增加提醒信息,比如金额提示输入数值或最大长度值等等。设置方式也有多种,简单的,仅为单元格插入批注就可 ... [详细]
  • 深入解析Spring启动过程
    本文详细介绍了Spring框架的启动流程,帮助开发者理解其内部机制。通过具体示例和代码片段,解释了Bean定义、工厂类、读取器以及条件评估等关键概念,使读者能够更全面地掌握Spring的初始化过程。 ... [详细]
  • 深入解析动态代理模式:23种设计模式之三
    在设计模式中,动态代理模式是应用最为广泛的一种代理模式。它允许我们在运行时动态创建代理对象,并在调用方法时进行增强处理。本文将详细介绍动态代理的实现机制及其应用场景。 ... [详细]
  • 本文介绍了解决在Windows操作系统或SQL Server Management Studio (SSMS) 中遇到的“microsoft.ACE.oledb.12.0”提供程序未注册问题的方法,特别针对Access Database Engine组件的安装。 ... [详细]
author-avatar
sdfqwerwfds
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有