- 我们介绍 ZGateway:通过这一代理,统一访问 Meta 使用最广泛的键值存储 ZippyDB 的流量。
- 它还带来准入控制、负载均衡、跨区域韧性,以及更丰富的操作能力。
ZippyDB 是 Meta 使用最广泛的键值存储,支撑产品元数据、计数器和配置,在全球分布的集群上每秒服务数十亿次操作。
此前的文章介绍过 ZippyDB 的工作方式。本篇讨论它前面的一层:我们正借助 ZGateway 统一 ZippyDB 客户端流量。
ZGateway 最初为管理不断扩张的客户端集群而生,最终证明其价值体现在架构层面。一个客户端可能属于超过一百万台主机之一,归数百个团队管理,无法快速修改。代理则处在截然不同的位置:同时位于许多客户端的请求路径上,这种共享视角让它具备单个客户端无法实现的能力。
下面每项能力放入统一管理的一层,都更容易、更安全,甚至只有这样才可能实现;逐一分散到一百万个客户端二进制中则困难得多。我们通过连接管理与请求批处理两个最清楚的案例展开。
为什么 ZippyDB 需要代理层
当庞大且多样的客户端群体访问共享后端时,代理随处可见:连接池服务、服务网格 sidecar、CDN 边缘、API 网关。在大量调用者与共享资源之间插入一层,带来三方面收益:
- 约束问题规模:后端不再直接面对所有客户端,而是面对由自身运营团队控制的集群。
- 集中共享工作:连接池、重试、路由、缓存和准入控制,由最了解后端的团队解决一次,替代每个客户端分别实现。
- 建立控制点:在同一位置观察全部负载,将负载归因给产生者,并在几分钟内改变行为,无需等待整个客户端集群完成部署。
代价是多一次网络跳转,以及多一层要运营的系统。当客户端庞大、多样且无法由你修改时,这种取舍才有收益;ZippyDB 是极端案例。
直接访问模式中,每个客户端连接它需要的每台数据库主机。稳定时间窗口内,单个客户端可能触达数万个不同分片,而分片分散在数十万台数据库主机上。结果是密集的多对多 TLS 连接网:典型客户端持有数万个出站连接,典型数据库主机接收数万个入站连接。

这种网既浪费又脆弱。每个连接在两端占用内存、CPU 和文件描述符,且多数时间空闲。入站连接随客户端群体增长,每加入一批客户端,每台数据库主机的处境都会恶化。
每个客户端又各自管理连接池与故障转移,因此连接复用突然下降——例如一批客户端重启或部署滚动——会给集群带来新建连接风暴。我们曾将因文件描述符耗尽及内存不足导致的主机崩溃,追溯到这种情况。
客户端侧难以修复,因为两套系统同时变化:客户端不断改变连接池策略并扩大规模,数据库集群则按自己的节奏整合。直接耦合两者,如同在两辆行驶中的车之间跳跃。代理解耦双方,把连接网变成两段有界的跳转,改善效率、性能、扩展性,尤其是可靠性。
最后一项最重要。直接访问下,重连风暴会成为灾难。一次路由缺陷让每个客户端为每个分片打开连接,主机突破文件描述符限制,集群陷入重启循环。ZGateway 位于路径中后,风暴被限制在代理层:这是我们能够集中控制、观察和加固的集群。连接管理问题没有消失,而是移到了我们能统一解决的位置。
直接访问是 ZippyDB 早期阶段的正确设计。扇入随客户端数量增加,资源开销随采用程度上升:几千个客户端时只是低效,规模扩大后便成为可靠性限制。实现共享层的条件也随后成熟,包括 ServiceRouter 功能改进、Thrift 过载保护、轻量客户端等。如果更早构建 ZGateway,就必须先逐一构建这些能力。
ZGateway 是什么
ZGateway 是 ZippyDB 客户端与数据库主机(ZServer)集群之间的无状态代理层。它能处理每秒超过十亿次操作,承载约 40% 的 ZippyDB 流量,预计增长至 60% 以上;对于平均用例,只增加约 6% 的计算开销。
它通过 Meta 超大规模服务网格方案 ServiceRouter 发现区域服务层,让客户端尽量靠近网关。纯代理和读穿透缓存两种形式共享同一流水线。其引擎是我们的功能完整 C++ 客户端,每个用例对应一个内部客户端。ZGateway 相当于以托管服务方式运行 ZippyDB 客户端,因此把能力迁移到这里很自然。

客户端沿粘性连接将请求发送至区域 ZGateway 主机。网关终止 TLS,依据用例 ACL 授权,并执行租户级准入控制、校验与整形。随后解析分片;缓存层检查本地缓存,将未命中请求和写入与同分片的其他在途请求一起批处理和合并,再发送给正确副本。
响应按调用者拆分返回,途中记录各用例的指标、追踪及配额使用量。
关键在于连接数量的不对称性:每个客户端只需保持通往区域 ZGateway 主机的粘性连接池;每台 ZServer 只看到来自我们控制规模的 ZGateway 集群的连接。有些职责有意保留在原处:TLS 位于 Thrift/ServiceRouter 栈中,键到分片的映射由分片定位器完成,副本选择和对冲请求由嵌入客户端处理。ZGateway 负责流量管理,而不是重写数据库客户端。
降低扇入与扇出
计算方式如下:将集群建模为“球投入箱子”。把 B 个球(一台主机触达的分片)投入 H 个箱子(主机),计算命中的不同箱子数量。
原文给出的期望数量公式:
E(H,B) = H(1 - e^(-B/h))
特定箱子被命中的概率:
p(B) = 1 - e^(-B/H)
扇出是命中的不同箱子数量;扇入则是该概率乘以调用者数量。使用便于理解的模拟数值——20 个区域、500,000 台数据库主机、30,000 台代理主机、1,000,000 个客户端、每个客户端 50,000 个分片——模型得到下图。

连接没有凭空消失,而是移到为承载它们而构建的层。端到端的持久连接总量仍约下降 19 倍,因为每个后端连接复用许多客户端。
但再显著的一次性减少也不是重点,重点是扩展行为发生变化。在直接访问模式中,数据库主机扇入是 H_client · p,与客户端群体线性相关,每加入一批客户端都使数据库主机负担更重。引入 ZGateway 后,客户端数量完全退出表达式:扇入约为 R · S_host,即区域数乘以每台主机的分片密度,不依赖双方集群规模。剩下的控制因素只有分片密度,而它由我们掌控。
由其他所有人驱动的无界数量,变成了我们控制的有界数量。
压缩请求流:批处理与合并
ZGateway 位于许多客户端路径上,因此能完成单个客户端库做不到的事:合并互不相关调用者的工作。每台主机上的共享批处理器按用例和物理分片分组,将发往同一目的地的请求合成一次后端 RPC。
它还合并重复读取:多个调用者同时请求同一个键,网关只获取一次,再将结果分发。客户端侧批处理器只能合并自身进程的请求,网关则跨客户端压缩请求。
无论请求大小,每次 RPC 都有固定开销:Thrift 序列化、分片查找、授权与系统调用。多个操作合成一次能分摊这些开销,减少后端请求数量、增大单次请求、降低 QPS 和 CPU;等待窗口平滑微小突发,使负载更稳定。用例按发送的 QPS 计费,因此批处理延长其限流预算,减少节流,无需用例侧做任何工作。
另有两项收益并非效率。其一是热点键:数千个并发调用者压缩成一次后端读取,让热点键不至于形成针对单个副本的惊群。
其二是能删除旧组件。多年来,需要批处理的客户使用客户端库,它们脆弱、消耗 CPU、分别调优,并不断引发事故,因为复杂性存在于一百万个无法由我们控制的二进制中。共享批处理器可跨客户端合并,这是那些库做不到的,并让我们淘汰旧库。
请求暂存在内存批次中,当等待窗口结束、载荷超过大小限制,或请求数量达到上限时刷新,因此新增延迟有界。过大或刚迁移的批次回退为单独发送。内存中保留请求有 OOM 风险,所以批处理带有两道保护。
空闲淘汰处理缓慢增长:批次映射中空闲超过 TTL 的条目,在下一次刷新时删除。在途上限处理急性过载:后端变慢时,运行已刷新批次的协程堆积速度超过排空速度,超过限制便拒绝新执行。持续清理加上急性安全阀,使批处理默认安全。

ZGateway 如何演进
一旦流量流经同一层,它自然成为各客户端原本需重复实现的能力的归属地。批处理是最清楚的例子,以下是其他能力。
流量路由与安全迁移路径
迁移至代理风险很高,必须渐进、可逆且限定范围。客户端配置标志按服务及分片前缀控制 ZGateway 路由:百分比旋钮逐渐增加符合条件的流量,区域过滤器限定影响范围,全局停用开关即时回退。纯配置,无需改客户端代码,因此能实时控制部署。
租户隔离与准入控制
共享层服务数百个用例,一个行为异常的租户不能耗尽其他租户的资源。防线是 Discriminant Load Shedding(DLS,差别化负载卸载)。每个请求按用例及优先级进入租户桶,各桶轮流排空。租户淹没服务层时,自己的桶满并丢弃超额请求,其他桶继续排空。隔离来自结构,而非运气。
DLS 前的 CPU 并发控制器使用 AIMD 循环,调节共享令牌桶准入速率;内存处理器以类似方式防止 OOM。
卸载仍保持差别化。在 CPU 超过90%、约1,350个活跃租户桶的受控过载实验中,只有真正产生干扰的6个租户发生卸载;其余约1,344个租户完成99.9%的请求,零拒绝,有效吞吐率维持约97%—98%,机制本身约占8%的CPU。

带实时失效机制的读取缓存
缓存层通过进程内缓存响应热点读取。未命中时取得键级填充锁,把同一键的惊群压缩成一次后端获取。写入及检查点事件组成的变更数据捕获流,失效或重填受影响条目,在明确的有界陈旧契约内保证新鲜度;各主机通过一致性哈希拥有键空间的一部分。
这样以更低延迟显著卸载存储读取,同时维持正确性。
服务层中的负载均衡
ZGateway 无状态,区域层的任意主机都能响应任意请求,因此可以引导流量平衡负载。硬件并不一致,主机约从26核到126核不等,大规模任务替换可在几分钟内重新分配容量。对不同主机一视同仁会产生过热异常主机,继而引发错误率激增和 ServiceRouter 节流。ServiceRouter 采用加权一致性哈希,因此控制点是每台主机的权重。
控制平面的均衡器定期读取各主机近期 CPU 利用率,将层平均值归一化为1.0,逆着负载方向微调权重。护栏维持稳定:调整被阻尼和限幅,分布以目标中位数重新居中,防止权重趋零;变更限速让每轮只调整最不均衡的主机,减少分片重排。缓存层重排昂贵,因为改变权重意味着移动键。
新主机初始权重按硬件容量缩放。
经验是:固定策略无法同时适用于平静与受冲击的服务层。因此均衡器正在变得自适应,将状态分类为稳定漂移、任务变动、初始均匀权重、双峰负载、热点异常主机、区域偏斜等,再应用对应策略。
跨区域韧性
ZGateway 过去大部分时间严格限定于区域内,故障转移也在区域内部。这有利于延迟,但整个区域受压时,请求在本地排队并超时,邻近区域的健康容量却空闲。基于 ServiceRouter,我们可以通过三种机制受控地跨区域路由:
- 全局路由:建立跨区域路由表,使饱和本地层转移至健康区域。
- 大区域:将地理相近区域合并为一个局部性单元,溢出就近承接,保留大部分延迟优势。
- 环:明确声明哪些区域相互备援及其比例。
每项机制都按服务层及区域,在百分比开关后启用。故障转移信号与路由同样重要:简单区域 CPU 平均值会平滑掉恰需识别的热点状态,因此使用更敏锐的指标,调优至区域进入过载之前触发,而非之后。
事务与更丰富的操作
事务需要保存客户端账务信息:读集、扫描范围、待处理写入。历史上它们位于功能完整客户端中;客户迁至轻量客户端后,状态必须迁入网关。首个版本留下两套并行实现:ZGateway 专用存储及引擎已有的内存路径,在最影响正确性的部分出现两种实现。
我们在标志开关后整合为一套,分九个阶段推进至流量最大的区域,覆盖100%事务流量,没有可靠性回退。与引擎共享路径,让 ZGateway 与服务端事务演进同步:在层内部演进一次能力,全部客户端继承。
生产环境中的 ZGateway 运营
ZGateway 在数十个区域的大量服务器上运行,按负载划分为少量服务层:服务长尾用例的大型通用层、最大客户的专用层,以及独立的高吞吐代理层。占用与规模差异超过一个数量级;单个层内部也不均匀,主要因为任务堆叠。多任务以不同密度放在同一台机器上,同时存在完整规格的专用主机。
所有这些层都提供丰富的用例级可观测性,正是这种可见性让准入控制和负载均衡能在共享基础设施上安全运行。
ZGateway 的下一步
近期方向不变:通过 ZGateway 统一全部 ZippyDB 流量。更有趣的是,普遍采用的网关能带来什么。三个方向尤为突出,都有同一主题:ZGateway 看到最多,也决定最多。
由代理运营的启发式策略
几乎每项能力都有控制循环与人工调节参数:卸载桶大小及CPU阈值、均衡器参数、故障转移触发条件、批次刷新窗口、缓存陈旧界限。今天由人调节、定时任务推动;前述自适应均衡器实际上已是代理,只是没有这个名称。
下一步将这种方式明确化,把启发式规则与内部状态暴露为结构化控制面,让 AI 代理观察相同遥测,诊断层状态、把事故归因于产生干扰的租户,并在护栏内实施补救,比值班人员更快。
共置
ZGateway 是独立层,代价是额外网络跳转与几个百分点的开销。对延迟或效率关键的负载,可以把部分网关能力下沉到 ZServer 主机旁,让网关到服务器的链路成为本地调用,控制平面仍集中。难点是避免重新耦合此前有意解耦的集群。
连接管理及准入控制前端仍保留为共享区域层,只下沉能受益于数据局部性的部分。
多进程网关
一个进程承担许多职责,一个租户的内存膨胀就可能威胁主机上的全部工作。拆成协作进程——连接/TLS 前端、请求工作进程、独立缓存及事务组件——能提供硬故障隔离与独立生命周期。
这还与代理运营策略和共置相互补充:代理可以管理主机上的进程集群,而数据平面已成为独立可放置的进程时,共置也更清晰。
共同作用下,ZGateway 从智能服务层走向可编程服务层:控制决策由代理执行,系统占用向工作所在位置移动,故障域通过结构隔离。











暂无评论内容