订阅以接收新文章的通知:

重新设计 Workers KV,以提高可用性和更快的性能

2025-08-08

13 分钟阅读时间
这篇博文也有 English日本語版本。

此内容已使用自动机器翻译服务进行翻译,仅供您参考及阅读便利。其中可能包含错误、遗漏,或与原始英文版本存在理解方面的细微差别。如有疑问,请参考原始英文版本。

2025 年 6 月 12 日,Cloudflare 遭遇了一次严重的服务中断,影响了我们的大量关键服务。正如我们在有关该事件的博客文章中所解释的,原因是我们的 Workers KV 服务使用的底层存储基础设施出现故障。Workers KV 不仅为许多客户所依赖,而且还作为许多其他 Cloudflare 产品的关键基础设施,处理受影响服务的配置、身份验证和资产交付。这一基础设施的一部分由第三方云提供商提供支持,该提供商在 6 月 12 日发生了一次中断,直接影响了我们 KV 服务的可用性。

今天,我们提供对 Workers KV 改进的更新,这些改进旨在确保类似的中断不会再次发生。我们现在将所有数据存储在自己的基础设施上。除了用于冗余的任何第三方云供应商外,我们也从我们自己的基础设施满足所有请求,确保高可用性并消除单点故障。最后,这项工作显着提高了性能,并为消除对第三方提供商作为冗余备份的依赖指明了清晰的道路。

背景:原始架构

Workers KV 是一种全球键值存储,支持低延迟高读取量。在幕后,该服务将数据存储在区域存储中,并在 Cloudflare 的网络中缓存数据,以提供卓越的读取性能,适用于需要在全球范围内立即可用的配置数据、静态资产和用户首选项。

Workers KV 最初于 2018 年 9 月推出,其出现早于 Durable Objects 和 R2 等 Cloudflare 原生存储服务。因此,Workers KV 的原始设计利用了多个第三方云服务提供商的对象存储产品,通过提供商冗余来最大化可用性。系统以主动-主动配置运行,即使在其中一个提供商不可用、遇到错误或运行缓慢的情况下,也能成功服务请求。

对 Workers KV 的请求由 Storage Gateway Worker (SGW)处理,这是一项在 Cloudflare Workers 上运行的服务。当收到写请求时,SGW 会将键值对同时写入到两个不同的第三方对象存储提供商,确保数据始终可从多个独立的来源获得数据。对删除的处理方式类似,通过写入一个特殊的逻辑删除值来代替对象,以将键标记为已删除,这些逻辑删除随后被作为垃圾收集。

从 Workers KV 的读取通常可以从 Cloudflare 的缓存中提供,提供可靠的低延迟。对于不在缓存中读取的数据,系统会将请求与两个提供商竞速,并返回最先到达的响应,这通常来自地理位置较近的提供商。这种竞赛方法始终获得最快的响应,优化了读取延迟,同时提供针对提供商问题的恢复能力。

鉴于使两个独立存储提供商保持同步本身就有困难,该架构包含了复杂的机制来处理后端之间的数据一致性问题。尽管有这个机制,但由于上游对象存储系统本质上不完美的可用性,以及在独立提供商之间保持完美同步面临的挑战,一致性边缘情况仍然比消费者的要求更加频繁。

多年来,系统的实现发生了显着变化,包括我们去年讨论过的各种性能改进,但基本的双提供商架构保持不变。这为 Workers KV 使用的大规模增长提供了可靠的基础,同时保持了其对全球应用程序有价值的性能特征。

扩展挑战和架构权衡

随着 Workers KV 使用量的激增,访问模式变得更加多样化,双提供商架构面临着越来越多的运营挑战。提供商具有截然不同的限制、故障模式、API 和操作程序,需要不断调整。

扩展问题不仅仅局限于提供商的可靠性。随着 KV 流量的增加,IOPS 总数超过了我们写入本地缓存基础设施的能力,迫使我们在从源存储获取数据时依赖于传统的缓存方法。这种转变暴露了在较小的规模上不明显的其他一致性边缘情况,因为缓存行为变得更不可预测,并且更加依赖于上游提供商的性能特征。

最终,一致性问题、提供程序可靠性差异和运营开销的组合导致我们在今年早些时候通过转移到单一对象存储提供商来降低复杂性的战略决策。做出这个决定是因为意识到了风险状况,但我们认为运营利益超过了风险,并将这视为我们开发自己的存储基础设施的一个临时的中间状态。

不幸的是,在 2025 年 6 月 12 日,这种风险变成了现实,我们剩余的第三方云提供商经历了全球中断,导致高比例的 Workers KV 请求失败,持续时间超过两个小时。这对客户和其他 Cloudflare 服务产生了严重的影响:所有基于身份的登录均失败,Gateway 代理不可用,WARP 客户端无法连接,其他数十项服务经历了严重中断。

设计解决方案

事件发生后的直接目标很明确:使至少另一个完全冗余的提供商在线,以便另一个单一提供商中断不会导致 KV 关闭。新的提供商需要在几个方面处理大规模的问题:数千亿个键值对,存储的 PB 级数据,每秒数百万次的 GET 请求,每秒数万次的稳定状态 PUT/DELETE 请求,以及数千万次的高达每秒 100 亿的吞吐量,而且全部具有高可用性和低单位数毫秒内部延迟。

一个明显的选择是重新启用我们今年早些时候禁用的提供商。然而,我们不能就这样将开关拨回原来的位置。先前第三方存储提供商的双后端配置中运行的基础设施现已不复存在,代码出现了一些位腐烂,使得快速恢复到先前的双提供商设置变得不可行。

此外,另一个提供商经常成为其自身运营问题的根源,错误率相对较高,请求吞吐量限制也低得令人担忧,这使我们犹豫是否再次依赖它。最终,我们决定,我们的第二个提供商完全由 Cloudflare 所有并由 Cloudflare 运营。

下一个选项是直接在 Cloudflare R2 上构建。我们已经在 R2 上运行了一个 Workers KV 的内测版本,但这次体验帮助我们更好地了解 Workers KV 的独特存储需求。Workers KV 流量模式的特点是包含数千亿个小对象,大小中值仅 288 字节,与假定文件大小较大的典型对象存储工作负载大不相同。

对于以这种规模的低于 1KB 的对象为主的工作负载,数据库存储比传统的对象存储显着提高效率和成本效益。当您需要以最小的每个值开销存储数十亿个非常小的值时,数据库在架构上是最适合的。我们正在努力针对 R2 进行优化,例如使用元数据内联小对象,以消除额外的检索跃点,从而提高小对象的性能,但对于我们的直接需求,由数据库支持的解决方案提供了最有希望的前进路径。

在对各种可能方案进行全面评估后,我们决定使用 Cloudflare 生产中已经投入使用的一个分布式数据库。R2 和 Durable Objects 在幕后使用同一个数据库,这给了我们几项关键优势:我们拥有深厚的内部专业知识和现有的部署和运营自动化,而且我们知道可以大规模依赖其可靠性和性能特征.

我们将数据分布到多个数据库集群,每个集群都配置三向复制,以提高持久性和可用性。这种方法使我们能够水平扩展容量,同时在每个分片内保持强大的一致性保证。我们选择运行多个集群而不是一个庞大的系统,以避免在任何集群变得不健康时产生更小的影响范围,并避免在 Workers KV 继续增长时单集群可扩展性的实际极限。

实施解决方案

在实施这个系统时,我们遇到的一个直接挑战是连接性。SGW 需要与我们的核心数据中心中运行的数据库集群进行通信,但数据库通常通过持久的 TCP 连接使用二进制协议,而不是在我们的全球网络中高效工作、基于 HTTP 的通信模式。

我们构建了 KV 存储代理(KVSP)来弥补这一不足。KVSP 服务提供了一个 HTTP 接口,我们的 SGW 可以使用这个接口在幕后管理复杂的数据库连接、身份验证和分片路由。KVSP 使用一致的哈希值在多个集群之间条带化命名空间,防止形成热点(即流行的命名空间可能压垮单个集群),消除嘈杂的邻居问题,并确保容量限制是分布式的,而不是集中的。

使用分布式数据库来存储 Workers KV 的最大缺点是,虽然它擅长处理在 KV 流量中占主导地位的小对象,但对于偶尔存储高达 25 MiB 的大值(有些用户有高达 25 MiB)的情况而言,这并不是最佳选择。我们对任一用例都不妥协,而是扩展了 KVSP,使其自动将较大的对象路由到 Cloudflare R2,从而创建一种混合存储架构,根据对象特征优化后端选择。从 SGW 的角度来看,这种复杂性是完全透明的——相同的 HTTP API 适用于所有对象,无论大小如何。

我们还恢复了 KV 先前架构中的存储提供商之间的双提供商功能,并对它们进行了调整,使其能够很好地与 KV 降级为单一提供商以来对 KV 实施进行的更改协同工作。修改后的系统现在通过同时向两个后端进行竞时写入来运行,但一旦第一个后端确认写入,就会将成功返回给客户端。

这一改进可以最大限度地减少延迟,同时确保在两个系统上的持久性。当一个后端成功而另一个后端失败时——由于临时网络问题、速率限制或服务降级——失败的写入将排队等待后台协调,这是我们同步机制的一部分,下文将详细介绍。

部署解决方案

实施混合架构后,我们开始了一个谨慎的推出流程,旨在验证新系统的同时维持服务可用性。

第一步是从 SGW 向新的 Cloudflare 后端引入后台写入。这使我们能够验证实际生产负载下的写入性能和错误率,而不影响读取流量或用户体验。这也是将所有数据复制到新后端的必要步骤。

接下来,我们将现有数据从第三方提供商复制到在 Cloudflare 基础设施上运行的新后端,并通过 KVSP 路由数据。这为我们带来了一个关键的里程碑:现在,如果发生另一个提供商中断的情况,我们可以在几分钟内手动将所有操作故障转移到新的后端。导致 6 月事件的单点故障已经消除。

出于对故障转移能力的信心,我们开始以主动-主动模式启用我们的首批命名空间,从内部 Cloudflare 服务开始,我们对那里的工作负载进行了复杂的监控和深入了解。我们谨慎地比较了后端之间的结果,非常缓慢地增加流量。事实上,SGW 可以在向用户返回响应后,能够异步地查看来自两个后端的响应,让我们能够执行详细的比较并捕捉任何差异,而不影响面向用户的延迟。

在测试过程中,与单一提供商的设置相比,我们发现了一个重要的一致性倒退,这导致我们短暂地回滚了将命名空间置于主动-主动模式的更改。虽然 Workers KV 在设计上是最终一致的,随着缓存版本超时,更改需要最多 60 秒时间传播到全球,但对于通过相同 Cloudflare 入网点路由的请求,我们无意中降低了自写(RYOW)一致性。 .

在之前的双提供商主动-主动设置中,我们在每个 PoP 中提供 RYOW,因为我们将 PUT 操作直接写入本地缓存,而不是依赖于上游存储之前的传统缓存系统。然而,KV 吞吐量超过了缓存基础设施可以支持的 IOPS 数量,因此我们不能再依赖这种方法了。这并非 Workers KV 的文档属性,但某些客户在其应用程序中开始依赖这种行为。

为了了解这个问题的范围,我们创建了一个对抗性测试框架,通过从世界各地的少数几个位置快速分散对一小组键的读写,来最大化达到一致性边缘情况的可能性。这个框架让我们能够衡量观察到 RYOW 一致性违规的读取百分比:来自同一入网点的写入操作之后紧接着的读取操作,将返回过期数据,而不是刚刚写入的值。这允许我们设计并验证一种新的方法来 KV 如何填充和使缓存中的数据失效,它恢复了客户期望的 RYOW 行为,同时保持了使 Workers KV 有效处理高读工作负载的性能特征。

KV 如何保持跨多个后端的一致性

由于写入竞相传输到两个后端,以及读取可能返回不同结果,在独立存储提供商之间维护数据一致性需要一种复杂的多层方法。虽然细节上随着时间的推移而不断演变,但 KV 始终采用相同的基本方法,即三种互补的机制相互配合,以减少出现不一致的可能性,并最小化数据发散的窗口。

第一道防线在写操作期间发生。当 SGW 同时向两个后端发送写入时,一旦任何一个提供商确认持久性,我们就会认为该写入成功。然而,如果一个写入在一个提供商上成功,但在另一个上失败——由于网络问题、速率限制或暂时的服务降级——失败的写入被捕获并将发送到后台协调系统。该系统会删除失败密钥的重复数据,并启动同步过程以解决不一致问题。

第二种机制在读操作期间激活。当 SGW 与两个提供商进行读取竞赛并获得不同的结果时,它会触发相同的后台同步过程。这有助于确保使变得不一致的键在首次访问时恢复对齐,而不是无限期地保持分歧。

第三层由后台爬虫组成,它们持续扫描两家提供商的数据,识别并修复被前面的机制遗漏的任何不一致性。这些爬虫还提供了关于一致性偏移率的宝贵数据,帮助我们了解密钥有多频繁地通过反应机制并解决任何根本问题。

同步过程本身依赖于我们附加到每个键值对的版本元数据。每次写入都会自动生成一个新版本,由一个高精度时间戳和一个随机数组成,并与实际数据一起存储。在比较提供商之间的值时,我们可以根据这些时间戳确定哪个版本更高。然后,将较新的值复制到具有旧版本的提供商。

在时间戳之间的偏差在毫秒以内的极少数情况下,时钟偏差在理论上可能会导致错误的排序,但考虑到我们通过 Cloudflare 时间服务对时钟维护的严格范围,以及典型的写入延迟,此类冲突只有在几乎同时进行重叠写入操作时才会发生。

为了防止同步期间的数据丢失,我们使用条件写入,在写入之前验证最后的时间戳是否较旧,而不是盲目地覆盖值。如果距离很近的请求成功发送到不同的后端,并且同步过程将旧值复制到较新的值,这样我们就可以避免引入新的不一致问题。

同样,我们不能在用户请求时就删除数据,因为如果删除只在一个后端成功,同步过程会将此视为丢失数据,并从另一个后端复制。相反,我们使用具有较新时间戳的逻辑删除而非实际数据来覆盖该值。只有在双方都获得了逻辑删除之后,我们才会继续实际从存储中移除密钥。

这种分层一致性架构并不能保证强一致性,但在实践中,它确实消除了后端之间的大多数不匹配情况,同时保持了一个性能特征,这使 Workers KV 对延迟敏感的高读取工作负载具有吸引力,同时还提供了任何后端不匹配时的高可用性。后端错误。用分布式系统的术语来说,KV 选择了CAP 定理中的可用性 (AP) 而不是一致性 (CP),更有趣的是,在没有分区的情况下,它在PACELC 定理下选择延迟而不是一致性,这意味着它是 PA/EL 。通过反应机制,大多数不一致问题都能在几秒钟内解决,而后台爬虫确保即使是边缘情况通常也会随着时间的推移得到纠正。

上述描述适用于我们历史的双提供商设置和今天的实施,但当前架构中的两项关键改进显着改善了一致性。首先,与我们之前的第三方提供商相比,KVSP 保持了低得多的稳定状态错误率,从而减少了造成不一致的写入失败的频率。其次,我们现在对两个后端进行所有读取竞赛,而之前的系统通过在初始学习期后优先将读取操作路由到单个提供商来优化成本和延迟。

在原始双提供商架构中,每个 SGW 实例最初将与两个提供商进行读取竞赛,以确定基准性能特征。一旦实例确定一个提供商在其地理区域内始终优于另一个提供商,它就会将后续的读取专门路由到更快的提供商,仅在主服务器发生故障或异常延迟时回退到较慢的提供商。虽然这种方法有效控制了第三方提供商的成本并优化了读取性能,但它在我们的一致性检测机制中造成了一个重大盲点——如果始终仅从一个后端提供读取服务,则提供商之间的不一致性可能会无限期地存在。

结果:性能和可用性提升

凭借这些一致性机制,以及通过内部服务验证的谨慎部署策略,我们继续将主动-主动操作扩展到跨内部和外部工作负载的更多命名空间,所看到的效果让我们感到非常兴奋。新架构不仅为 Workers KV 提供了所需的更高可用性,还带来了显着的性能提升。

作为我们新存储后端的所在地,这些性能提升在欧洲尤其明显,但其好处远远超出了仅地理位置所能解释的范围。与我们并行写入的第三方对象存储相比,内部延迟改善非常显着。

例如,KVSP 读取的 p99 内部延迟低于 5 毫秒。相比之下,在对传输时间进行标准化以进行同类比较后,从我们最近的位置对第三方对象存储进行的非缓存读取通常约为 80 毫秒,第 50 名和第 99 位为 200 毫秒。

下图显示了我们能得到的最接近的同类比较:我们观察到的对 KVSP 的请求的内部延迟,与对缓存未命中并最终从 DNS 转发到外部服务提供商的请求的观察到的延迟。的延迟,其中包括额外的 5-10 毫秒请求传输时间。

这些性能改进直接转化为许多依赖 Workers KV 的内部 Cloudflare 服务的响应时间更快,为我们的平台创造了连锁反应。事实证明,经过数据库优化的存储对于在 Workers KV 流量中占主导地位的小对象访问模式特别有效。

在看到这些积极的结果后,我们继续扩大推广,为内部和外部客户复制数据并启用命名空间组。可用性增强与性能优化的结合验证了我们的架构方法,并展示了在我们自有平台上构建关键基础设施的价值。

接下来?

我们近期的计划重点是扩展这种混合架构,为 Workers KV 提供更强大的韧性和性能。我们正在将 KVSP 解决方案推广到更多地点,创建一个真正的全球分布式后端,它可以完全从我们自己的基础设施服务流量,同时努力进一步加快我们在提供商之间以及在写入后在缓存中达到一致性的速度。

我们的最终目标是完全消除我们现存的第三方存储依赖,实现 Workers KV 完全独立于基础设施。这将消除导致 6 月事件的外部单点故障,同时让我们完全控制存储层的性能和可靠性特征。

除了 Workers KV 之外,该项目还展示了混合架构的强大功能,这种架构结合了不同存储技术的最佳方面。我们开发的模式(使用 KVSP 作为转换层,根据大小特征自动路由对象,以及利用我们现有的数据库专业知识)可以被其他需要平衡全球规模与强一致性要求的服务利用。从单一提供商设置到在 Cloudflare 基础设施上运行的弹性混合架构的历程,表明深思熟虑的工程团队能够将运营挑战转化为竞争优势。凭借显着提高的性能和主动冗余,Workers KV 已做好准备为越来越多依赖它的客户提供一个更可靠的基础。

我们保护整个企业网络,帮助客户高效构建互联网规模的应用程序,加速任何网站或互联网应用程序抵御 DDoS 攻击,防止黑客入侵,并能协助您实现 Zero Trust 的过程

从任何设备访问 1.1.1.1,以开始使用我们的免费应用程序,帮助您更快、更安全地访问互联网。要进一步了解我们帮助构建更美好互联网的使命,请从这里开始。如果您正在寻找新的职业方向,请查看我们的空缺职位
Cloudflare Workers KVPost Mortem

在 X 上关注

Alex Robinson|@alexwritescode
Cloudflare|@cloudflare

相关帖子