原文发布于 quant67.com,转载请保留出处。
前 15 篇把 TiKV 的 Region、Multi-Raft、PD/TSO、Percolator 事务和 TiFlash Learner 拆完了。读者这时候很容易冒出一个问题:CockroachDB 不也是"分片 + Raft + KV"吗,是不是换了个名字的 TiKV? 答案是否定的------两者共享同一套分布式共识底座(Raft),但在角色划分 、事务提交协议 、部署形态 上做出了不同的工程选择。本文只对照这三点,不重讲 Raft 协议本身(见 raft-explained),也不对 CockroachDB 的 SQL 优化器做展开。
版本锚定 :CockroachDB 文档引用其官方 Architecture 系列文档(stable 版本,A 级)与 Taft et al., CockroachDB: The Resilient Geo-Distributed SQL Database, SIGMOD 2020(A 级论文)。TiKV 侧对照见本系列第 3--11 篇。
一、Range 与 Region:命名不同,切分逻辑相近
CockroachDB 把全局有序 key 空间切成连续的 Range ,每个 Range 对应一个独立的 Raft 组,副本默认 3 份------这与 TiKV 的 Region 是同一类设计:都是"按 key range 分片 + 每片一个 Raft 组"的 Multi-Raft 思路(第 3、4 篇已经拆过 TiKV 侧的 range/epoch/peer 模型)。两者的分片大小、Split/Merge 触发条件在实现细节上有差异,但这属于参数级别的不同,不改变架构范式。
真正值得对照的是副本角色的划分方式。CockroachDB 官方文档明确区分了两个概念:
- Raft leader:按 Raft 协议选出、负责日志复制的角色;
- Leaseholder(租约持有者):持有该 Range 的"range lease",是唯一能对外提供一致性读、并把写请求转成 Raft 命令的角色。
CockroachDB 通过 Leader leases 机制让这两个角色绝大多数时刻绑定在同一个副本上(官方文档:"The range lease is always colocated with Raft leadership via the Leader leases mechanism, except briefly during lease transfers"),但在 lease 转移的短暂窗口内二者可能短暂不一致。
TiKV 没有把这两个角色显式拆成两个可能分离的概念------Region 的 Raft Leader 直接承担对外读写的职责,不存在一个独立命名、可以临时与 Raft leader 分离的"租约角色"。这不是说 TiKV 完全没有"安全本地读"的优化(Follower Read 依赖 ReadIndex/lease read 类机制),而是 CockroachDB 把这个概念做成了用户可观察、文档明确定义的架构角色,TiKV 把等价的优化留在了 raftstore 内部实现里,不作为独立对外概念暴露。这是"文档化的显式分层"与"内部化的隐式实现"之间的工程选择差异,不代表哪一方缺少对应的机制。
二、事务协议:Percolator 2PC vs Parallel Commits
TiKV 的分布式事务落地 Percolator 模型(第 10 篇):Prewrite 阶段给每个 key 写 Lock(记录 Primary 位置),Commit 阶段先决 Primary、再异步决 Secondary,事务的提交状态由 Primary 的 Write 记录唯一决定。
CockroachDB 的事务模型走了不同路径。它的核心结构是写意图(Write Intent) ------本质上是带事务记录指针的 MVCC 版本,兼具"临时值"和"排他锁"两种语义,思路上与 Percolator 的 Lock 类似;但状态判定不是靠"Primary key 是否已经从 Lock 变成 Write",而是靠一个独立的事务记录(Transaction Record) ,记录状态在 PENDING/STAGING/COMMITTED/ABORTED 之间转移。
CockroachDB 在 2018 年之后引入的 Parallel Commits (SIGMOD 2020 论文、CockroachDB RFC 20180324,A/B 级)把提交延迟从两轮共识压缩到一轮:客户端发起 COMMIT 时,协调者把事务记录直接置为 STAGING,同时列出所有正在写入的 key;只要能证明这些 key 的写入都已经达成共识,事务就被判定为"隐式已提交"(implicitly committed),协调者可以立即向客户端返回成功,再异步把状态落实成 COMMITTED 并清理写意图。
| 维度 | TiKV Percolator | CockroachDB Parallel Commits |
|---|---|---|
| 学术锚点 | Peng & Wei, OSDI 2010(A 级,奠基论文) | Taft et al., SIGMOD 2020(A 级,工程论文,非同一篇奠基工作的直接延伸) |
| 提交判定 | Primary key 的 Lock→Write 转换 | 事务记录进入 STAGING + 所有写意图达成共识可证明 |
| 时间戳来源 | commit_ts 由 PD TSO 分配 | 时间戳由 CockroachDB 内部 HLC(Hybrid Logical Clock)生成,不依赖外部单点授时服务 |
| 提交延迟目标 | 两轮 Raft 复制(Prewrite + Commit) | 通过 STAGING 与写意图并行,把提交路径压缩到接近一轮 |
需要谨慎的是:"Parallel Commits 比 Percolator 快一个量级"这类结论本站不下------两套协议面向不同的时钟模型(PD 集中式 TSO vs HLC)与不同的部署形态,没有同一口径、同一硬件的实测对比,公开资料也没有给出这样的直接数字。能确认的只是两者解决的问题相同(降低跨 key 提交的共识轮数),走的路径不同。
三、部署形态:分离式组件 vs 单进程一体化
TiKV 生态是分离式的:TiDB(SQL 层,无状态)、TiKV(存储与事务)、PD(元数据/调度/TSO)是三个独立进程,各自独立扩缩容------第 1 篇会讲到的四件套分工在这里再次出现。
CockroachDB 是单进程一体化 :SQL 层、分布式层(DistSender/range 路由)、复制层(Raft)、存储层全部编译进同一个 cockroach 二进制,每个节点既能是 SQL 网关,也能是某些 Range 的副本所在地。CockroachDB 官方架构文档把这个分层描述为 SQL → Transactional → Distribution → Replication → Storage 五层,全部跑在同一个进程空间里,节点之间通过 DistSender/BatchRequest 通信。
这个差异带来的实际后果:
- 运维单元不同:TiDB 集群需要分别规划 TiDB/TiKV/PD 三类节点的数量与资源;CockroachDB 只需要一种节点类型,扩容即加节点;
- 计算与存储的耦合度不同:TiDB 的 SQL 计算与 TiKV 存储物理分离,SQL 层可以独立水平扩展,不占用存储节点资源;CockroachDB 的 SQL 执行发生在接收请求的节点上,与该节点是否持有相关 Range 副本无关,但计算资源和存储资源仍共享同一批进程;
- HTAP 路径不同 :TiDB 靠 TiFlash 这个独立列存 Learner 副本承接分析负载(第 14 篇);CockroachDB 的主线定位是分布式 OLTP,没有与 TiFlash 对等的官方列存副本方案,分析型负载通常依赖外部 ETL 或第三方列存系统。
四、时钟模型:集中式 TSO vs 混合逻辑时钟
事务提交顺序需要一个全局可比较的时间戳,这一点两个系统都认,但拿时间戳的方式不同:
- TiKV :所有
start_ts/commit_ts都由 PD 的 TSO 服务集中分配(第 9 篇),保证任意两个时间戳可以直接比较大小,代价是每次事务开始和提交都要一次到 PD 的网络往返; - CockroachDB :用 HLC(Hybrid Logical Clock) ------每个节点的本地物理时钟加一个逻辑计数器,节点间通过请求/响应携带的时间戳互相"对表"、只在必要时把本地时钟推进到不落后于对方------不需要一个中心化的服务来分配时间戳,但代价是 HLC 的时钟偏移量必须被限制在一个已知的最大误差(
max-offset,默认通常在数百毫秒量级)之内,否则跨节点的时间戳比较会失去意义。
这个差异是"集中式授时换取严格顺序、但引入单点往返"与"去中心化换取更低延迟、但需要限定并持续校验时钟偏移"之间的经典权衡,与 Spanner 用 TrueTime 限定时钟不确定性区间的思路同源,但 CockroachDB 的 HLC 不依赖专用硬件(如 GPS/原子钟),精度保证也相应弱一些------这一点 CockroachDB 官方文档本身也承认,其严格可串行化保证在极端的时钟漂移场景下依赖运维层面保证 max-offset 不被打破,而不是像 TrueTime 那样有硬件级别的不确定性区间保障。
五、隔离级别:Snapshot Isolation 与 Serializable 的真实代价
前四节讲的是复制、提交协议、部署形态、时钟模型的差异,但有一个更容易被忽略、后果却更直接的差异:两个系统默认给用户的隔离级别不一样。
TiKV/TiDB 的 Percolator 事务模型本质上提供的是 Snapshot Isolation(SI) ------TiDB 开发指南明确写的是"TiKV 是支持基于 MVCC 的 Snapshot Isolation 的多版本存储引擎"。SI 保证每个事务看到的是一个自身开始时刻的一致性快照,写写冲突会被检测(第 10 篇的 Lock 机制),但 SI 不保证可串行化 ------两个并发事务各自读一部分数据、各自更新另一部分数据,只要它们没有直接的写写冲突,SI 允许这两个事务的合并效果是任何单独串行执行都不会产生的结果。这类异常有个经典名字:写偏斜(Write Skew)。
CockroachDB 默认给用户的是 SERIALIZABLE ------官方文档明确写"默认情况下,CockroachDB 保证事务隔离级别是 SERIALIZABLE,不允许任何并发异常"。它能做到这一点,靠的是 Serializable Snapshot Isolation(SSI) :Cahill, M., Röhm, U. & Fekete, A. 在 Serializable Isolation for Snapshot Databases (SIGMOD 2008,A 级)里给出的算法------在标准 SI 的基础上,运行时动态检测"读写依赖环"(rw-antidependency cycle),一旦检测到可能导致不可串行化结果的依赖模式,就主动中止其中一个事务,而不需要像悲观锁那样阻塞读写。CockroachDB 的设计文档直接引用了 Cahill 的论文(docs/design.md,A 级)作为其 SSI 实现的依据。
一个标准的写偏斜例子能说明两者的实际差异:
text
初始状态:账户 A 余额 100,账户 B 余额 100,业务规则「A+B 不能同时低于阈值 50 才能各自扣款」
事务 T1(在快照下检查 A+B=200 >= 100,扣 A 的 80)
事务 T2(在快照下检查 A+B=200 >= 100,扣 B 的 80)
T1、T2 并发执行,彼此看不到对方的更新(各自读到的是 A=100, B=100 的快照)
两者都提交成功:最终 A=20, B=20,A+B=40 < 100 ------ 业务规则被违反,但纯 SI 系统里两个事务各自的写没有冲突
| 维度 | TiKV / TiDB(Percolator,SI) | CockroachDB(默认 SSI) |
|---|---|---|
| 学术锚点 | Percolator 论文本身只要求 SI(OSDI 2010),未承诺可串行化 | Cahill et al., SIGMOD 2008(SSI 算法) |
| 写偏斜 | 可能发生 ,需要应用层显式加锁(如 SELECT ... FOR UPDATE,第 11 篇)规避 |
默认阻止,代价是可能触发事务重试(SQLSTATE 40001) |
| 用户可选项 | 只有 SI(TiDB 内部称为 Repeatable Read,语义等价 SI,而非 ANSI 标准的 RR) | SERIALIZABLE(默认)与 READ COMMITTED 两档可选 |
| 性能权衡 | 无冲突检测开销,但需要开发者理解 SI 的异常边界 | 低竞争下开销接近 SI;高竞争下中止率上升,需要客户端做重试处理 |
这不是"哪个更先进"的问题,是两条不同的默认安全边界 :选择 TiKV/TiDB 的团队,需要清楚自己的业务逻辑是否隐含"读一部分、判断后写另一部分"的模式(典型如余额检查、库存扣减),这类模式在 SI 下必须显式加锁;选择 CockroachDB 的团队默认不必操心这一点,但要设计好事务重试逻辑去应对 SSI 中止。这也是本系列第 11 篇强调"TiKV 无锁"是误解的原因之一:SI 的正确使用要求开发者在写偏斜风险场景主动加锁,锁不是可选项,而是弥补 SI 语义边界的必要手段。
六、陈旧读的两种实现:safe-ts vs Closed Timestamps
第 15 篇定义过 TiFlash 的 safe-ts :一个 Region 级别的时间戳下界,查询时间戳不超过它就能保证读到快照一致的结果;TiDB 的 Stale Read 靠 Resolved TS 让查询主动放宽新鲜度要求换取更低延迟。CockroachDB 有一套目标几乎完全对应的机制,叫 Closed Timestamps :Leaseholder 持续向所有副本(不止是分析副本)广播一个"承诺"------任何时间戳不超过这个值的写入都不会再被接受,所以任何副本只要 apply 到了对应的 Raft 日志位置,就能安全地服务这个时间戳以下的读请求(CockroachDB Docs, Transaction Layer §Closed Timestamps;CockroachDB Blog, An Epic Read on Follower Reads,A/B 级)。
两套机制的目标相同(让非 Leaseholder/非 Leader 的副本也能安全提供一致性读),但覆盖范围和实现细节有明显差异:
| 维度 | TiFlash safe-ts | CockroachDB Closed Timestamps |
|---|---|---|
| 适用副本类型 | 仅 Learner(TiFlash),面向 HTAP 分析读 | 所有 Voter 副本,用于任意 Follower Read 场景(不限于分析负载) |
| 推进依据 | apply 进度 + 未决锁 resolve 结果(第 15 篇 §一) | Leaseholder 承诺的时间戳下界,随 Raft 日志一起下发(piggyback 在 Raft 命令上) |
| 触发读取的语法 | AS OF TIMESTAMP / TIDB_BOUNDED_STALENESS(TiDB SQL 层) |
AS OF SYSTEM TIME(CockroachDB SQL 层) |
| 与租约的关系 | 与 TiKV 的 Region Leader 无强绑定语义(Learner 独立判断 apply 进度) | 显式与 Leaseholder 的 lease 生命周期绑定:新 Leaseholder 不会推翻旧 Leaseholder 已经关闭的时间戳承诺 |
| 语义 | 快照一致(Snapshot Isolation 意义上的一致),非可串行化 | 在 SERIALIZABLE 默认隔离级别下,历史读同样遵循串行化保证 |
关键差异在"覆盖范围" :TiFlash 的 safe-ts 只解决"分析副本能不能安全读",因为 TiKV 的标准 Follower 走的是第 5 篇提到的 ReadIndex/Lease Read 机制,不需要 safe-ts 这套时间戳下界;CockroachDB 则把"任意副本能否安全提供历史读"做成了统一机制,不区分副本是不是列存/分析角色------这与第三节讲过的"单进程一体化,没有独立的分析副本类型"是同一个架构选择的延伸:CockroachDB 没有 TiFlash 这类专门角色,所以它必须让 Closed Timestamps 覆盖所有副本,才能达到"从任意副本读到不太旧的一致数据"的效果。
七、OceanBase 边界(一句话)
OceanBase 走的是第三条路:它用 shared-nothing + Multi-Paxos 做副本复制,但把 SQL 引擎、存储引擎、事务引擎全部集成在同一个 OBServer 进程里,并且默认让 OLTP 与轻量 OLAP 都直接由主副本的 LSM-tree 存储引擎服务(基线数据 + 增量数据两层),只有在需要更强分析能力时才引入行列混合存储或列存备份副本------这与 TiKV+TiFlash"行列两套独立副本"、CockroachDB"单一行存引擎"都不同,本系列不做展开,仅作边界标注。
八、与 distributed/39 的分工
《分布式 KV 存储对比:etcd、TiKV 与 FoundationDB》已经把 TiKV、etcd、FoundationDB 三者的共识协议、存储引擎、事务模型、性能特征做过系统对照,本篇不重复那张对照表。本篇新增的是 distributed/39 没覆盖的 CockroachDB,重点讲清楚"表面都是 range/Region + Raft,但角色划分与提交协议差在哪";完整的多系统选型决策留给第 18 篇统一收束。
九、常见误解
误解一:CockroachDB 和 TiKV 是同一套设计换了个名字。 共识层都基于 Raft、分片模型都是"range + 每片一个 Raft 组",但 CockroachDB 显式定义了 Leaseholder 这个独立角色、用 Parallel Commits 重新设计了提交路径、把 SQL 引擎直接编译进存储节点------这些都是不同的工程决策,不是同一套实现的改名版本。
误解二:Parallel Commits 让 CockroachDB 的事务比 Percolator 快一个数量级。 没有可信的、同口径的公开实测支撑这个结论。能确认的只是两者用不同机制压缩了跨 key 提交的共识轮数,具体延迟差异依赖硬件、网络拓扑与负载特征,本站不下无证据的性能排名。
十、开放问题
- 单进程一体化 vs 分离式部署,长期运维成本谁更低:这是工程哲学层面的争论,缺乏公开的、控制变量的对比研究,通常只能看到各自厂商基于自身案例的说法,本站不采信单方营销结论。
- Leaseholder 显式分层是否该被 TiKV 借鉴:CockroachDB 把"谁能读写"和"谁是 Raft leader"做成可能短暂分离的两个概念,这在处理跨地域部署、lease 转移时提供了额外的灵活度;TiKV 是否需要引入等价的显式分层,还是维持当前的隐式实现,取决于生产场景对灵活度与实现复杂度的权衡,目前没有公开的路线图结论。
- 默认隔离级别的选择是否该由存储层还是 SQL 层决定:TiKV 把 SI 作为存储层的固定语义,可串行化的保证(如果需要)交给应用层加锁;CockroachDB 把 SSI 做成 SQL 层默认策略,可按需降级到 SI 或 READ COMMITTED。哪种分层更适合大多数业务,缺乏跨系统、控制变量的量化研究,本站不下结论,只标注两者选择了隔离语义栈里不同的层。
十一、小结
三句话小结:
- CockroachDB 和 TiKV 都基于 Raft 做 range/Region 级 Multi-Raft 分片,但 CockroachDB 显式定义了可能短暂偏离 Raft leader 的 Leaseholder 角色,TiKV 把等价优化隐藏在 raftstore 内部。
- 两者事务模型都源自"给每个 key 打临时标记 + 集中记录最终状态"的思路,但 TiKV 走 Percolator 的 Primary/Secondary 两阶段提交,CockroachDB 走 Parallel Commits 把提交路径压缩到接近一轮共识;默认隔离级别也不同------TiKV 是 Snapshot Isolation,CockroachDB 默认 Serializable(基于 Cahill et al. 2008 的 SSI 算法)。
- TiKV 生态是 TiDB/TiKV/PD 三组件分离部署,CockroachDB 是单进程一体化;OceanBase 走的是第三条路,本系列只做边界标注不做深拆。
参考资料
- CockroachDB Docs, Architecture Overview / Replication Layer / Transaction Layer(cockroachlabs.com/docs/stable/architecture,A 级)。
- Taft R., et al. CockroachDB: The Resilient Geo-Distributed SQL Database . SIGMOD 2020(A 级)。doi.org/10.1145/331...
- CockroachDB Blog / RFC, Parallel Commits(cockroachlabs.com/blog、cockroachdb/cockroach RFC 20180324,A/B 级,机制设计文档)。
- Peng D., Dabek F. Large-scale Incremental Processing Using Distributed Transactions and Notifications. OSDI 2010(Percolator,A 级)。
- Cahill, M. J., Röhm, U. & Fekete, A. Serializable Isolation for Snapshot Databases . SIGMOD 2008(A 级,SSI 算法,rw-antidependency 环检测)。doi.org/10.1145/137...
- CockroachDB, docs/design.md (cockroachdb/cockroach,A 级,SI/SSI 语义与 Cahill 论文引用出处);CockroachDB Docs, Transactions(A 级,默认 SERIALIZABLE、READ COMMITTED 可选级别)。
- TiDB Development Guide, MVCC garbage collection(A 级,TiKV 基于 MVCC 的 Snapshot Isolation 定义)。
- CockroachDB Docs, Transaction Layer §Closed Timestamps;CockroachDB Blog, An Epic Read on Follower Reads(A/B 级,Closed Timestamps 与 Follower Reads 机制)。
- OceanBase Developer Guide, Architecture(oceanbase.github.io,A 级,一句话边界依据)。
- distributed/39 分布式 KV 存储对比(etcd/TiKV/FoundationDB 完整对照,不在本篇重复)。
- 本系列 第 3--4 篇 Region 模型与 Multi-Raft、第 10--11 篇 Percolator 乐观事务与 ResolveLock、第 15 篇 新鲜度与一致性。
上一篇 :新鲜度与一致性
下一篇 :生产排障:热点、锁、TSO、apply
返回 系列目录