Meta 在其广泛使用的键值存储 ZippyDB 前部署了 ZGateway,用统一入口承接原本分散的访问流量。代理增加了一次网络跳转,却也提供了实施准入控制、负载均衡、跨地域容灾和统一运维的关键位置。
这类改造的价值不只是“隐藏后端地址”。真正重要的是把客户端难以一致实现的流量治理能力,从众多业务进程集中到一个可独立演进的数据面。
为什么键值存储需要统一入口
客户端直连存储节点看起来路径最短,但随着调用方和集群数量增长,客户端通常会逐渐承担服务发现、分片路由、故障重试、跨地域切换等职责。不同语言 SDK 的能力和发布节奏不一致,最终容易形成多套行为。
在存储前增加代理,可以建立一份统一契约:
- 客户端只需要知道代理端点,不直接感知节点扩缩容和故障迁移。
- 准入控制在请求进入后端队列前执行,避免过载扩散到存储节点。
- 负载均衡、健康检查和节点摘除由统一组件处理。
- 跨地域策略可以集中配置,不必等待所有客户端升级。
- 指标、日志和流量调试集中在一个位置,运维视角更加完整。
代价同样明确。代理进入所有请求的关键路径,会增加延迟、计算成本和新的故障域。它必须按基础设施标准设计,而不能被当成普通无状态 Web 服务。
准入控制比无限排队更有效
存储过载时,最危险的行为往往不是立即拒绝,而是继续接收请求并不断排队。队列拉长会消耗请求的超时预算;客户端随后发起重试,又进一步放大负载。
代理适合在后端资源耗尽前实施准入控制。可以这样实践:
- 按租户、操作类型或优先级分别设置并发额度。
- 请求已经超过截止时间时,不再转发到后端。
- 队列达到上限后快速返回可识别的过载错误。
- 为控制面或恢复任务保留少量容量,防止关键操作被普通流量淹没。
限流不能只依赖每秒请求数。键值存储中的一次小对象读取和一次大范围扫描,成本可能相差很大。更稳妥的模型会同时考虑并发数、响应大小、操作类别和后端实时压力。
负载均衡与跨地域切换的边界
代理能够依据节点健康状态分配流量,但“节点存活”并不等于“节点适合接收更多请求”。连接成功率、尾延迟、队列长度和错误类型都可能影响选择。对有分片或副本语义的存储,代理还必须尊重数据归属,不能把请求随意发送到任意节点。
跨地域容灾也不是简单地修改目标地址。工程上至少需要回答这些问题:
- 备用地域的数据新鲜度是否满足业务要求?
- 写请求切换后,如何防止双写冲突或旧主继续接受流量?
- 客户端剩余的超时预算是否足够完成跨地域访问?
- 哪些错误允许重试,哪些错误必须直接返回?
尤其要谨慎处理写操作。代理若在响应丢失后自动重试,可能造成重复写入。只有当操作具备幂等性,或协议提供请求 ID、去重记录等机制时,写重试才相对安全。
一个可改造的 TCP 代理配置
下面是一个通用 Envoy 示例,用于演示两个键值存储节点前的 TCP 代理、主动健康检查和熔断配置。它不是 ZGateway 的实际配置,也不了解 ZippyDB 的私有协议;使用前需要把地址、端口和容量参数替换为自己的环境。
将以下内容保存为 envoy.yaml:
static_resources:
listeners:
- name: kv_listener
address:
socket_address:
address: 0.0.0.0
port_value: 7000
filter_chains:
- filters:
- name: envoy.filters.network.tcp_proxy
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
stat_prefix: kv_proxy
cluster: kv_backends
idle_timeout: 60s
clusters:
- name: kv_backends
type: STATIC
connect_timeout: 250ms
lb_policy: LEAST_REQUEST
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 1000
max_pending_requests: 200
health_checks:
- timeout: 200ms
interval: 2s
unhealthy_threshold: 3
healthy_threshold: 2
tcp_health_check: {}
load_assignment:
cluster_name: kv_backends
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 7001
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 7002
admin:
address:
socket_address:
address: 127.0.0.1
port_value: 9901
安装 Envoy 后可以直接启动:
envoy --mode validate -c envoy.yaml
envoy -c envoy.yaml
curl -s http://127.0.0.1:9901/stats | grep kv_proxy
这个例子只工作在 TCP 层,因此无法识别租户、键、读写类型或请求截止时间。生产级存储代理通常需要理解应用协议,才能进行按操作限流、分片路由、幂等重试和更精确的可观测性。L4 配置适合验证统一入口和连接级负载均衡,不应被误认为完整的存储网关。
上线时关注失败模式,而不只是吞吐量
统一代理应当渐进接管流量。先镜像指标或迁移少量低风险调用方,再逐步扩大比例,并保留能够快速回退的客户端路径。容量规划需要覆盖正常峰值和故障场景,因为一个地域或一组代理下线后,剩余实例会立即承受更高负载。
上线检查可以围绕以下项目展开:
- 代理额外增加的 P50、P99 和 P999 延迟是否可接受。
- 限流是否按租户和优先级隔离,过载时是否快速失败。
- 重试是否受次数、截止时间和幂等条件约束。
- 健康检查能否识别“进程存活但服务退化”的节点。
- 跨地域演练是否验证了数据一致性,而不只是网络连通性。
- 代理配置错误、证书轮换和版本回滚是否有自动化保护。
- 指标是否能够从入口请求追踪到具体后端和错误类别。
ZGateway 的启示在于,代理可以成为键值存储的流量控制面,但集中能力也集中风险。只有把快速失败、容量隔离、协议语义、可观测性和容灾演练一起纳入设计,额外的一跳才会真正换来更稳定的系统。