CockroachDB 是一款基于分布式共识协议的分布式 SQL 数据库,它把逻辑上的表数据切分成一个个 range,每个 range 默认有三个副本,其中只有一个副本持有 range lease,负责处理该 range 上的全部读写请求。正常情况下 lease 是相对稳定的,但在实际运维中经常遇到 lease 频繁转移的情况,表现出来就是延迟毛刺增多、节点之间流量来回摆动。这篇文章围绕 range lease 的转移频率问题,重点分析 locality 相关的配置如何影响 lease 的归属,以及应该如何调整配置让 lease 稳定下来。

range lease 的基本工作机制
每个 range 的多个副本分布在不同的节点上,通过 Raft 协议保证数据一致性。Raft 协议本身解决的是日志复制顺序问题,但读写请求需要一个明确的协调者,这个协调者就是 lease holder。lease holder 通过定期续约来维持自己的身份,默认的 lease 有效期大约是 9 秒,续约周期约为 3 秒。只要 lease holder 节点健康并且能够正常与多数派副本通信,lease 就会一直留在它手里。
lease 的转移通常发生在几种场景:一是节点故障或重启,lease 必须被其他副本接管;二是副本迁移,比如节点下线、负载均衡触发的 rebalance 操作,lease 会跟着副本移动;三是配置约束变化,当用户修改了 zone 配置中的约束条件后,系统会重新评估 lease 应该归属哪个副本;四是明确设置了 lease preference,当首选位置的副本状态变化时,lease 会主动搬到符合偏好的副本上。
理解这些触发条件非常关键,因为 lease 频繁转移往往不是系统 bug,而是配置与集群拓扑之间发生了某种冲突。比如把 lease preference 设置在了一个不稳定的位置,或者副本约束导致副本不断被重新放置,lease 自然也会跟着来回跳。
locality 与 lease 归属的关系
启动 CockroachDB 节点时可以通过 --locality 参数描述节点的物理位置,例如 --locality=region=cn-east,zone=az1,rack=r3。locality 本身是层级化的键值对,层级顺序从大到小排列。locality 主要影响两件事:副本放置(通过 zone 配置的 constraints)和 lease 放置(通过 lease preferences)。
一个常见的误区是认为只要副本分布对了,lease 就会自动跟着 locality 走。实际上,如果没有显式配置 lease preference,lease 通常会倾向于留在离多数派副本最近的位置,但在节点负载不均或发生 rebalance 时它可能出现在任何副本上。对于跨地域部署的集群,如果 lease 落在远离用户的地域,每个请求都要承担一次跨地域往返延迟,这就是很多用户感觉读写时快时慢的根本原因。
下面的示例展示了一个跨三地域集群的典型配置方式,先给数据库设置约束让副本按 3-2-2 分布,再把 lease 固定到用户所在地域:
-- 为特定数据库设置副本约束
ALTER DATABASE appdb CONFIGURE ZONE USING
num_replicas = 7,
constraints = '{"+region=cn-east": 3, "+region=cn-north": 2, "+region=cn-south": 2}';
-- 将 lease 偏好设置在主地域,备选地域次之
ALTER DATABASE appdb CONFIGURE ZONE USING
lease_preferences = '[[+region=cn-east], [+region=cn-north]]';
需要注意 lease_preferences 的语法是嵌套数组的形式,内层数组表示同一优先级内的多个可选项,外层表示优先级从高到低。上面的写法表示优先选择 cn-east 地域的副本,如果 cn-east 没有可用副本则退到 cn-north。这种写法在主备地域架构中非常实用,既能保证正常情况下 lease 稳定在主地域,又能在主地域整体故障时自动降级。
定位 lease 频繁转移的原因
当怀疑 lease 转移过于频繁时,第一步是看数据。CockroachDB 的 Web 控制台有专门的 Replication 面板,其中 Lease Transfers 图表展示了单位时间内 lease 转移的次数。除此之外,也可以直接查询系统表来统计当前每个 range 的 lease 位置:
-- 查看当前 lease 分布在哪些节点上 SELECT lease_holder, count(*) AS range_count FROM crdb_internal.ranges GROUP BY lease_holder ORDER BY range_count DESC; -- 查看热点 range 的 lease 归属 SELECT range_id, database_name, table_name, lease_holder FROM crdb_internal.ranges WHERE table_name = 'orders' LIMIT 50;
拿到数据之后,重点观察转移的模式。如果转移集中在某个节点,多半是这个节点磁盘慢、CPU 过载或者网络抖动,导致它续约失败。如果转移是全局性的、周期性的,那大概率是配置层面的问题,比如有人频繁修改 zone 配置,或者 lease preference 指向的副本集合本身在不停变化。还有一种情况是节点存储即将耗尽触发了持续的 rebalance,副本在搬迁,lease 自然也跟着动。
排查时可以结合 crdb_internal.kv_flow_control_handles 和节点详细页面的网络与磁盘指标一起看。如果 lease 转移和磁盘写入延迟的毛刺在时间上对得上,基本可以断定是存储层的问题,应该先处理慢盘而不是改配置。
优化 locality 配置的实践建议
第一,对于跨地域集群,优先使用 geo-partitioning 而不是全局统一的 zone 配置。geo-partitioning 允许按行级别指定数据归属的地域,配合 regional 表或者分区级别的 lease preference,可以让数据和 lease 一起靠近用户。这样每个地域的用户请求基本都在本地闭环,跨地域流量只剩下 Raft 日志复制。
-- 创建区域化的分区表,每个分区独立配置副本与 lease
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
region STRING NOT NULL,
amount DECIMAL,
created_at TIMESTAMPTZ DEFAULT now()
) PARTITION BY LIST (region);
ALTER TABLE orders PARTITION BY LIST (region) (
PARTITION east VALUES IN ('cn-east'),
PARTITION north VALUES IN ('cn-north')
);
ALTER PARTITION east OF TABLE orders
CONFIGURE ZONE USING
constraints = '{"+region=cn-east": 3}',
lease_preferences = '[[+region=cn-east]]';
第二,locality 层级要设计得合理。不要在层级中混入会变化的属性,比如把实例编号或者临时标签写进 locality,这样会让约束匹配变得混乱。层级应该是 region、az、rack 这种稳定的物理结构,最多再加上 cloud 或 provider 描述混合云场景。层级写反(比如 rack 在前 region 在后)虽然不会报错,但会让后续基于前缀的约束表达非常别扭。
第三,避免对 lease preference 设置过多优先级层级。每一层偏好都是一个潜在的转移触发点,层级越多,系统在不同状态下重新评估 lease 位置的频率就越高。实践中两层偏好(主选加一个备选)通常是稳定性和容灾能力的最佳平衡。同时务必保证每个偏好层级下存在健康的副本,如果 preference 指向的地域一个副本都没有,系统会反复尝试匹配并产生大量无效的转移日志。
验证与监控配置效果
调整配置之后,建议用如下方式验证效果:先对比调整前后 Web 控制台中 Lease Transfers 曲线,理想状态下日常稳态的转移次数应该趋近于零,只有在节点重启或人为运维操作时出现尖峰。其次观察 P99 延迟的变化,lease 靠近用户后,跨地域往返的开销消失,读延迟的改善通常非常明显。
还可以在系统表中确认 lease 是否真正落到了预期的 locality 上,写一个定时任务把 crdb_internal.ranges 中 lease holder 节点的 locality 与期望值做比对,一旦出现偏移就告警。这种主动监控比事后排查要省心得多,特别是对于规模较大、range 数量上百万的集群,靠人工翻面板几乎不可能及时发现局部的 lease 漂移。
总结一下,range lease 频繁转移的根源大多是配置与拓扑不匹配:副本约束、lease preference、节点 locality 三者要形成一致的规划。把数据副本、lease 和用户流量放在同一个 locality 组里,是让 CockroachDB 跨地域集群稳定低延迟运行的核心原则。
CockroachDBrange leaselocality修改时间:2026-09-09 07:24:40