内容简介:动态配置能够在不重新启动应用程序的情况下更改正在运行的系统的行为和功能。理想的动态配置系统使服务开发人员和管理员能够方便地查看和更新配置,并高效可靠地向应用程序提供配置更新。它使组织能够快速、大胆地迭代新特性,并提供工具,减少与更改现有系统相关的风险。在 Twitter 的早期,应用程序管理并分发自己的配置,通常存储在 ZooKeeper 中。但是,我们以前本文将介绍 Twitter 的动态配置系统 ConfigBus。ConfigBus 包括存储配置的数据库、将配置分发到 Twitter 数据中心中的机
动态配置能够在不重新启动应用程序的情况下更改正在运行的系统的行为和功能。理想的动态配置系统使服务开发人员和管理员能够方便地查看和更新配置,并高效可靠地向应用程序提供配置更新。它使组织能够快速、大胆地迭代新特性,并提供工具,减少与更改现有系统相关的风险。
在 Twitter 的早期,应用程序管理并分发自己的配置,通常存储在 ZooKeeper 中。但是,我们以前 使用 ZooKeeper 的经验 表明,它在用作通用键值存储时不能伸缩。其他团队转而使用 Git 进行存储,并结合自定义的 工具 来更新、分发和重新加载配置。随着 Twitter 的发展,很明显需要一个标准的解决方案来提供可伸缩的基础设施、可重用的库和有效的监控。
本文将介绍 Twitter 的动态配置系统 ConfigBus。ConfigBus 包括存储配置的数据库、将配置分发到 Twitter 数据中心中的机器的管道、读取和更新配置的 API 和工具。
架构
在一个较高的层次上,你可以将 ConfigBus 看作一个 Git 存储库,它的内容被推送到 Twitter 数据中心的所有机器上。配置更改经过一系列步骤到达目的地:
- 开发人员向 Git 存储库提交更改。经过身份验证的应用程序也可以通过 ConfigBus 服务提交到存储库。
- 预接收钩子验证更改。如果验证通过,则在服务器上接受并提交推送。(我们将在“特点”部分更详细地讨论这种验证。)
- 一旦服务器上的提交过程完成,Git 接收后钩子就会使用提交信息(SHA、时间戳等)更新 ZooKeeper 中的特定节点。
- 接下来会触发 ZooKeeper watches ,引发下游过程的动作:
a. 在作为配置准备区的“ConfigStore”机器上,watch 事件会调用一个回调,从 Git 服务器获取最新提交。它还用当前的 SHA 更新 ZooKeeper 中的条目。
b. 在目标机器上,watch 事件触发镜像任务,该任务轮询 ZooKeeper,找出与最新提交具有相同 SHA 的 ConfigStore 机器。一旦找到源机器,镜像任务将运行 rsync 把更改同步到本地机器上。 - 最后,使用这些配置文件的应用程序将看到文件系统上的更改(通过 ConfigBus 客户端库),并启动进程内重新加载。
最后,系统停止工作,以便把第 1 步中更改的文件同步到所有目标机器,并由依赖这些机器的所有客户端应用程序重新加载。
特点
配置即代码
使用 Git 允许开发人员重用源存储库提供的许多相同的命令和工作流。Git 及其周边的生态系统主要提供了以下功能:
- 具有更改日志的源代码控制 :能够检查过去的配置更改以查看更改了什么(以及由谁更改、何时更改或为什么更改)是非常有价值的。Git 自然允许这样做。开发人员完全有信心,当前和过去的版本在版本控制中是安全的。
- 自动部署 :ConfigBus 中的配置文件会自动复制到所有目标机器。目前,配置传播的平均延迟为 80-100 秒,而 p99 延迟大约为 300 秒。
- 分析验证 :ConfigBus 使用 预接收钩子 来运行验证程序,它们可以检查配置文件中的语法错误,进行模式验证,以及执行任何类型的自定义验证。它针对 JSON 等流行配置格式为 Twitter 开发人员提供了开箱即用的语法验证。它还提供了指定模式和验证兼容性的能力。用户还可以编写自定义验证,并在 Git 中添加和更新配置时执行这些验证。
- 代码审核 :让配置更改通过代码评审有助于减少错误,并在将其投入生产环境之前发现问题。
- ACL :我们在 Git 存储库中强制执行配置所有权,以确保配置文件只被应该管理它们的团队和应用程序修改。当推送一个更改时,一个预接收钩子会验证是否允许执行该推送的用户对这些文件进行更改。
- 编程访问 :ConfigBus 服务支持对 ConfigBus 的编程访问。该服务实现了 Git“智能”HTTP 协议 ,并充当配置存储库的前端。它通过 HTTP 和 Thrift API 提供读取和“compare-and-set”写入功能。这使得编写多用户应用程序向单个文件推送更改变得简单了,不需要它们具有存储库的本地克隆。该服务还内置了乐观并发控制,当推送由于对存储库的并发更新而失败时会自动重试。
大规模持续交付
一旦配置被安全存储,我们需要一种方法把它们提供给运行在 Twitter 基础设施 上的软件,包括运行在 Mesos 云以及直接运行在裸机上的服务。这是通过 rsync 将文件推送到所有的机器上来实现的。需要访问配置的应用程序只需从本地文件系统读取。这样做的好处是:
- 简单 :使用文件系统作为 API 使得任何语言编写的应用程序都可以使用 ConfigBus。可以在本地文件系统读取配置还有助于减少服务启动时间,特别是在云环境中,应用程序实例可以在一个节点上宕掉,然后在另一个节点上运行。
- 可容错 :将配置数据推送到每台机器上的本地文件系统中,这样,即使 ConfigBus 管道的某些部分失败,应用程序也可以继续运行。例如,如果 Git 服务器宕机,团队将无法进行新的配置更改,但是运行中的应用程序不会受到太大影响。同样,如果 ZooKeeper 出现故障,分发管道也会受到影响,但机器上现有的配置仍然可用。相反,如果配置获取服务宕掉,需要按需获取配置数据的系统将会失败。
- 可扩展 :ConfigBus 的多层架构允许系统根据需求的增加进行伸缩。ConfigStore 层将 Git 服务器与直接流量隔离开来。在这一层增加容量以适应 Twitter 不断增长的机器数量所带来的需求增加,这在操作上是非常简单的。
“热”重载
动态配置系统的主要优点之一是能够独立于使用它的软件部署重新加载配置更改。此外,一个完全动态的配置系统应该能够在不重新启动应用程序进程的情况下重新加载更改,从而最小化对整个应用程序的影响。ConfigBus 提供了一些库,允许客户端订阅特定的文件,并在这些文件更改时调用回调。虽然应用程序也可以直接从文件系统读取,但是使用经过良好测试和封装的客户端库具有以下优点:
- 避免检测配置更改并触发重新加载的代码重复;
- 允许嵌入发布配置新鲜度指标用于问题检测的代码。
监控
ConfigBus 是一个复杂的分布式系统,有许多活动部件。我们在各个层级对系统进行监控,以便收集统计信息,并在发现异常行为时发出警报。
- 独立部件 :我们监控各个子系统(如 Git 和 ZooKeeper 服务器)的健康状况。例如,我们收集与 Git 存储库的功能和性能相关的统计信息(包大小、提交延迟、钩子延迟等等),并发出警报。
- 版本跟踪 :Git 服务器将期望的配置版本发布到 ZooKeeper。下游消费者使用此版本监控配置数据的新鲜度。
- 端到端监控 :运行在客户端机器上的监控应用程序每隔几分钟就会更新配置存储库中的特定文件,并等待更新传播到其本地机器。这有助于度量 ConfigBus 管道的关键特征,例如提交成功率、提交延迟和配置同步延迟。
用例
流量路由:在 Twitter,ConfigBus 用于存储服务路由参数。这可以用于控制请求路由逻辑(例如,如果开发人员希望将 1% 的服务请求路由到运行软件自定义版本的一组实例)。
元服务发现:在 Twitter,服务是通过一个服务发现服务发现彼此。但是,它们必须首先发现服务发现服务本身。这是通过 ConfigBus 实现的。使用 ConfigBus 与类似于 ZooKeeper 这样的东西,其优点是,在每台机器的本地文件系统上都有可用的信息,这使得系统更能抵御故障(也就是说,如果 ConfigBus 或 ZooKeeper 出现故障,服务发现仍然可以运行)。
Decider:在 Twitter,Decider 是服务使用的特性标识系统,用于在运行时动态启用和禁用单个特性。该系统位于 ConfigBus 之上。Decider 是面向 键 - 值 的(“cool_new_feature 的值是多少?”),而 ConfigBus 是面向 文件 的(“文件 application/config.json 的内容是什么?”)。单个特性的标识称为“决策器(decider)”。一旦嵌入到代码中,决策器就可以更改正在运行的应用程序的行为,而不需要更改代码或重新部署。除此之外,决策器可以用来:
- 有选择性地启用代码 :可以在代码中使用决策键来选择性地启用代码块。Decider 提供的主要方法是“isAvailable”,它接受一个决策键参数,如果针对当前的调用打开特性,则返回“true”;如果针对当前的调用关闭特性,则返回“false” 。“isAvailable”方法使得开发人员可以像下面这样切换代码路径:
- 切换后台存储系统 :有些应用程序使用决策器选择写入或读取的后台存储系统。例如,应用程序从旧数据库迁移到新数据库,它可能会临时将数据写入两个系统,并在迁移完成后动态关闭旧数据库。或者,如果新系统有问题,应用程序可能会选择关闭它。决策器使得这些更改安全而快速。
- 作为止血带断开过载系统 :在 Twitter,监控系统有时会在检测到负载过重时更新决策器,以便禁用某些代码路径。这可以防止系统过载,并允许它们进行恢复。
- 区域间故障转移 :Decider 用于存储某些路由参数,这些参数控制着 Twitter 服务跨区域的流量分布。其中许多是通过监控软件自动更新的,后者可以观察每个区域的故障率。
“特性切换(Feature Switches)”:在 Twitter,特性切换为控制应用程序的行为提供了一个复杂而强大的基于规则的系统。特性切换控制特性在初步开发、团队测试、内部测试、Alpha 测试、Beta 测试、发布以及最终淘汰的过程中的可用性。与 Decider 的配置一样,特性切换配置存储在 ConfigBus 中。不过,在移动设备上,最终配置会有一个关键区别。在发布的最后一个阶段,移动应用程序会通过运行在 Twitter 数据中心的服务定期 拉取 这些配置更新。与 Decider 相比,特性切换还提供更细粒度的控制。典型的 Decider 配置很简单,例如,“允许数据中心 X 中 70% 的请求写入新数据库”。特性切换配置更高级,也更复杂,例如,“为 X 团队中的所有人以及这个平台上的这些特定用户启用这个新特性。”
“库切换(Library toggles)”:特性切换和 Decider 是为了帮助应用程序开发人员安全发布特性而设计的。库开发人员在推出更改时有时需要类似的开关机制。 Finagle 是 Twitter 面向 JVM 的开源 RPC 框架,它提供了一种 切换 机制, 库开发人员 可以使用该机制安全地发布更改,同时也为服务所有者提供了某种程度的控制。Twitter 在内部实现这个 API 时使用 ConfigBus 来动态控制这些切换。
执行 A/B 测试:要有效地运行产品试验需要快速迭代和易于调整的功能。Twitter 的试验框架使用了 ConfigBus,允许应用程序开发人员轻松地设置和扩展试验,并在需要时快速关闭它们。
一般应用程序配置:ConfigBus 最典型的用法是存储一般应用程序配置文件,并在更改提交时动态地重新加载它们。
经验教训
我们在生产环境中运行 ConfigBus 已经将近四年了。以下是我们学到的一些东西。
近实时分发
尽管 ConfigBus 的目标是近实时分发,但是,签入存储库的糟糕配置更改会迅速传播到任何地方。为了最小化这种更改的影响,ConfigBus 最近添加了一个可选特性,提供了“分阶段(staged )”滚动功能,从而可以增量地滚动推出更改。这是通过同时推送新旧版本的配置以及一些关于推出阶段的元数据来实现的。然后,各个应用程序实例使用阶段元数据来动态地加载合适版本的配置。
Git 存储库规模
随着 Git 存储库年龄的增长,它的规模也在增大。较大的存储库会减慢诸如“git clone”和“git add”之类的操作。存储库大小不仅受检入的大文件影响,而且还受到重大更改的影响。下面是我们用来解决这个问题的一些策略。
- “浅推送(Shallow pushes)” :我们升级到一个比较新的 Git 版本,该版本允许开发人员从存储库的浅克隆版本推送更改。这意味着最初的克隆操作要快得多,因为它只传输 HEAD 提交以及提交和推送更改所需的最小元数据。
- 归档 :在极少数情况下,我们会对 Git 存储库进行归档,将所有历史记录移动到一个归档中,然后重新开始。这使得我们可以删除旧的、潜在的大文件,减少存储库的大小。我们会避免经常这样做,因为这会迫使开发人员重新克隆存储库。
- 延长 delta 链 :我们目前正在研究,是否要积极地对 Git 对象进行重新打包以延长 delta 链,从而帮助减少存储库的大小,同时又保持更改提交性能不变。
分区
我们不允许 Git 存储库上的“非快进(non-fast-forward)”推送,以保护主分支中的提交不会被强制推送覆盖。这项设置的效果是要求对存储库的任何推送都必须使用存储库的最新副本。如果两个提交者在将数据推送到存储库时产生竞争,那么其中一个将胜出,另一个将不得不拉取最新的更改并重试。这会增加配置更新操作的延迟。对于频繁提交者来说,延迟的增加带来了一个巨大的问题。我们是通过在后台将频繁更新的名称空间划分到单独的专用存储库来解决这个问题。对于使用 API 进行配置更新的客户端来说,这没有任何差异。
文件级线性化
实际上,不允许非快进推送意味着 ConfigBus 在存储库级上是线性化的。如果两个开发人员因同时推送更改而发生竞争,则其中一个将“获胜”,另一个必须拉取最新的更改并重试。即使这两个开发人员正在更新完全不同的文件,情况也是如此。对于不断更新的存储库,这会给客户端带来不必要的负担。因此,我们把 ConfigBus 服务设计成自动拉取更新,并在失败时重试推送。这提供了表面上的文件级线性化,确保客户端只在文件级更新冲突时才看到失败。
git fetch
与 git pull
“git pull”实际上是“git fetch”+“git merge”。如果 ConfigStore 机器上的克隆站点损坏或与远程服务器不同步,合并步骤可能会失败。以自动方式从服务器获取更新最安全、最简洁的方法是运行“git fetch”+“git reset——hard FETCH_HEAD”,以便覆盖克隆站点上存在的任何本地状态。
rsync 导致的缓慢
我们选择让少量的 ConfigStore 机器从 Git 上获取信息,并将它们作为其他机器通过 rsync 进行同步的源。我们使用 -c 选项运行 rsync,迫使它忽略时间戳,并为大小相同的文件计算校验和。这是 CPU 密集型的,因此限制了每台 ConfigStore 机器可以提供的并发 rsync 操作的数量。反过来,这又增加了整个端到端传播的延迟。将名称空间分区成单独的存储库可以减少 rsync 在每次提交时需要比较的文件数量。另一种可能的选择是在每台 ConfigStore 机器上运行一个 Git 服务器,并让所有的目标机器运行“Git fetch”,这只需要下载最新的“HEAD”,而不需要任何比较开销(因为 Git 服务器确切地知道发生了什么变化)。
非原子同步
ConfigBus 使用 rsync,这意味着文件可以单个地同步到目标机器上。因此,如果提交碰巧更改了多个文件,那么目标机器上的文件系统可能会暂时包含新旧文件的混合。一个可能的解决方案是同步到一个临时位置,然后使用原子重命名操作来完成更改。但是,由于需要以向后兼容的方式支持分区名称空间,部署位置上存在符号链接,这使得问题变得复杂。一个更可行的解决方案是继续像现在这样分发主 Git 存储库,但是把将来的分区存储库切换到原子部署。
未来展望
在 Twitter,我们构建 ConfigBus 是为了提供一个健壮的动态配置平台。随着现有用例的发展和新用例的出现,ConfigBus 必须进行更改以适应它们。以下是我们特别关注的几个领域。
Git
对于终端用户而言,Git 有很多优点,但是,它代表了一个持续的操作挑战。我们对它今后是否仍然是正确的解决办法持开放态度。替代方法包括键 - 值存储,如 Consul,但我们必须解决历史太少这个 相反的问题 。
重新设计分发
使用 rsync 从一个很小的 ConfigStore 机器池进行分发限制了分发管道的速度。探索点对点分发模型将是一件很有趣的事情,在这种模型中,每台机器在拥有部分或全部数据后都充当进一步传输的源。
支持大对象
目前,我们不鼓励使用 ConfigBus 处理大型 Blob,这主要是因为 Git,也因为在每台机器上存储大型 Blob 的效率很低。一个潜在的解决方案是将 Blob 存储在一个常规的 Blob 存储中,并将活动版本存储在 ConfigBus 中,然后根据需要下载它们。
查看英文原文: Dynamic configuration at Twitter
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。