皮皮网
皮皮网

【风车4.17源码】【go源码怎么编译】【pdms自动标注源码】cockroachdb源码分析

时间:2024-12-27 14:14:38 来源:创建 dll c源码 下载

1.cockroachDB分布式事务优化:事务管道与并行提交
2.CockroachDB: 弹性、码分地理分布式SQL 数据库
3.15 位新兴开源项目背后的码分明星创始人
4.GitLab在CockroachDB和YugabyteDB上的兼容性对比(二)-读写场景测试
5.CockroachDB 源码闲逛 - II (insert a row)

cockroachdb源码分析

cockroachDB分布式事务优化:事务管道与并行提交

       分布式事务在CockroachDB中的重要性不言而喻,其目标是码分让数据操作简便,而如何使得事务尽可能快速执行是码分CockroachDB关注的重点。CockroachDB设计时特别考虑了全球分布式部署的码分环境,投入大量精力优化事务协议,码分风车4.17源码以适应高节点间延迟的码分集群。接下来,码分我们将深入探讨CockroachDB在分布式事务优化中的码分两大核心策略:事务管道和并行提交。

       在CockroachDB中,码分分布式事务允许操作跨越集群、码分地域和机器进行,码分同时提供SERIALIZABLE级别的码分隔离性和非过时读取的一致性保证。其事务模型受到Google Percolator的码分启发,采用三阶段模式进行处理:prepare阶段从SQL发出“Begin”指令直到事务提交之前,码分对于事务中的写操作,CockroachDB执行了两步操作:commit阶段,当SQL发出“commit”指令时,系统检查事务记录,若无“abort”状态,则将其标记为“commit”;cleanup阶段,根据事务记录,系统异步清理已完成事务的write intent。

       在深入探讨优化策略之前,我们需要了解事务执行“慢”的原因。简化模型中,不考虑从存储读写的延迟时间,延迟模型计算了客户端从输入SQL到收到回应的总延迟:L_txn = L_prep + L_commit,其中L_commit的代价为提交阶段的RPC网络开销,而L_prep则由读和写请求的数量决定。由于CockroachDB的读操作直接从leaseholder(可视为raft leader)本地读取,因此L_r接近于零,简化后的延迟模型为L_txn = R * L_r + W * L_w。由于分布式共识是同步操作,L_prep与写操作的数量成线性关系。

       为优化这一过程,CockroachDB引入了事务管道(Transactional Pipelining)机制,这是一种异步共识的实现方式。在没有事务管道之前,一个put的kv操作需要先在range的leaseholder上获取锁存器,然后进行raft共识写入write intent,待共识成功后返回。事务管道机制则允许put发送给raft状态机后直接返回,通过异步方式完成共识过程。此机制仅适用于不关注返回结果的语句,确保在commit之前所有异步共识都成功即可。

       事务管道的一大挑战是高效证明写意图已成功写入。CockroachDB在put操作返回后,后台启动gorouting异步验证写意图。理想情况下,验证过程应在commit之前结束,否则commit请求将同步验证,确保在commit之前所有写操作都成功复制。然而,实现这一目标并非易事,若在每个写操作返回后立即启动验证协程,可能导致资源耗尽;若只有一个验证协程,则速度会减慢。此外,如果commit请求在写操作后立即发出,go源码怎么编译会导致同步验证已完成的写操作,造成无效且冗余的操作。目前,CockroachDB官方尚未提供完整解决方案,持续关注此问题的进展。

       另一优化策略是“Read your write”原则,该原则指事务中的读依赖之前的写操作,管道机制无法保证之前写操作已成功共识。因此,在事务协调者阶段需记录所有需要确认的写操作,称为inflight-write,当存在依赖读时需要等待write intent成功写入。尽管这导致了管道之前的同步等待,但这是必要的。

       并行提交是CockroachDB在commit阶段的优化策略,旨在减少RPC延迟时间。在引入事务管道优化的基础上,我们分析了传统的两阶段提交(2PC)延迟模型,并提出并行提交策略。2PC至少需要两轮分布式共识后返回客户端提交成功,而并行提交策略则将commit阶段的功能前置至prepare阶段与写请求并行执行,将两轮共识减少为一轮。

       并行提交引入了staging状态,表示prepare和committed/abort之间的中间状态。当事务协调者发现有commit请求时,会并行发送EndTxn请求,将事务记录置为staging状态,同时包含所有未确认的inflight-write,等待所有inflight-write成功写入后,EndTxn成功返回,客户端接收到提交成功信息。随后,后台进程异步将staging状态改为committed。

       staging状态的设计巧妙之处在于记录了所有未确认的inflight-write(由于事务管道异步确认write intent,此集合不会过大),如果事务记录处于staging状态,并且观察者可以证明其事务记录中列出的所有写入已成功达成共识,则该事务被视为已提交。因此,并行提交不需要等待committed请求发出即可返回客户端提交成功。

       然而,staging状态较为“脆弱”,需要采取措施确保一致性。当staging状态的事务被另一冲突事务发现并被标记为废弃时,虽然可以写入write intent,但会将key上的时间戳推大,以阻止未来事务恢复后继续写入。这确保了staging状态不会破坏一致性。

       总结起来,事务管道和并行提交是CockroachDB在分布式事务优化中采用的两种策略,分别在prepare阶段和commit阶段对延迟进行了优化。两者结合将事务延迟降低至L_txn = w * L_w + L_c,将时间复杂度从O(n)优化至O(1),显著提高了事务处理速度。CockroachDB在工程层面实施了大量工作,对于深入理解这些优化的同学,建议直接阅读源码。

       

参考资料:

       1. cockroachlabs.com/blog/...

       2. cockroachlabs.com/blog/...

       3. cockroachDB paper

CockroachDB: 弹性、地理分布式SQL 数据库

       现代 OLTP 负载正迅速地跨越地域分布,pdms自动标注源码这使得跨国公司必须构建可扩展的应用系统并根据法律法规细粒度地控制数据存放位置。在这种背景下,CockroachDB(CRDB)应运而生,它是一个可扩展的 SQL 数据库管理系统,旨在支持全球性的 OLTP 负载的同时,保持高可用性和强一致性。

       CRDB 从头构建,支持在普通商用硬件上实现跨地域的分布式事务,并且能够像蟑螂一样抵御灾难。其创新的事务模型、容错机制和高性能特性使其成为跨国公司理想的选择。此外,CRDB 还提供了 SQL 接口和自动根据数据库集群规模进行伸缩的能力,以满足数据存储和管理的需求。

       为了满足跨国公司的需求,CRDB 重点关注以下几个特性:合规性、容错性和高性能。它具有前沿的查询优化器和分布式 SQL 执行引擎,支持在线模式更改、备份和恢复、快速导入、JSON 支持以及与外部分析系统的集成等功能。此外,CRDB 的源码已入驻 GitHub,且从 BSL 许可转为 Apache 开源 2.0 协议,用户无需依赖第三方 SQL 扩展专利或受制于云供应商宕机风险,避免了供应商锁定问题。

       本文将详细介绍 CRDB 的各个组成部分,包括架构、复制和数据分布机制、事务模型、时间戳排序、SQL 数据模型、执行和模式变化、性能评估和案例学习、经验总结、相关著作以及结论与展望。接下来,我们将从系统架构角度深入剖析 CRDB 的设计与实现。

       系统架构概述

       CRDB 使用无共享架构(share-nothing),所有的节点都同时提供存储和计算能力,集群可以包含任意数量的节点,这些节点可以在同一数据中心或分布于全球。客户端可以连接集群中的任何一个节点。

       CRDB 的架构可以分为以下几层:

       SQL 层

       最顶层是 SQL 层,它是所有用户与数据库交互的接口。它包括解析器、优化器和 SQL 执行引擎,该引擎将高级 SQL 语句转换为底层 key-value (KV) 存储的低级读写请求。

       通常,SQL 层并不了解数据是如何分区或分布的,因为下面的层抽象了一个单体的 KV 存储。然而,在第 5 节中,我们将详细介绍某些查询如何打破这种抽象,以实现更高效的分布式 SQL 计算。

       事务 KV 层

       来自 SQL 层的请求被传递到事务 KV 层,该层确保跨越多个 KV 对的原子性更改。它在很大程度上对 CRDB 的智能网络ea源码隔离保障负有责任。这些原子性和隔离保证将在第 3 节和第 4 节中详细描述。

       数据分布层

       这一层抽象了按 key 排序的单体逻辑键空间。在这个键空间中,所有数据都是可寻址的,无论是系统数据(用于内部数据结构和元数据)还是用户数据(SQL 表和索引)。

       CRDB 对 key 进行范围分区,将数据分成连续有序的,大小约为 MB 的块,我们把这些块叫做“Ranges”。这些 Ranges 之间的排序由一个两层索引结构维护,保存在一系列系统级别 Rranges 里面,并被预缓存以支持快速的按 key 查询。本层负责确定查询的某个子集应该由哪个 Range 处理,并合理路由。

       MB 的 Range 足够小,可以允许快速迁移,又足够大,足以保存一块连续的经常一起被访问的数据。Ranges 的初始状态为空,随着尺寸变化,经历分割、合并。Ranges 分割还可以根据负载进行,以减少 CPU 热点与资源不平衡。

       数据复制层

       默认情况下,每个 Range 保存 3 个副本,每个副本存储在不同的节点上。在第 2.2 节中,我们将描述复制层如何使用基于共识的复制确保修改的持久性。

       存储层

       这是最底层,代表一个本地磁盘支持的 KV 存储。它提供了高效的写和范围扫描,以支持高性能的 SQL 执行。在撰写本文时,我们依赖的是 RocksDB,它在其他地方有详细的记录,本论文中将其作为黑盒处理。

       容错和高可用性

       使用RAFT复制

       一个 Range 的所有副本组成一个 Raft group,其中一个副本是持久的 leader,协调所有发给这个 Raft group 的写操作,其他副本是 follower。复制的单元是命令,代表要存储层处理的一个编辑序列。Raft 在每个 Range 的所有副本范围内,维护一个一致的、排序的更新日志,每个副本各自按顺序在其本地存储引擎里应用那些已经声明被提交的日志。

       CRDB 使用 Range 层面上的租约,其中一个副本(通常是 Raft group leader)承担 leaseholder 角色,因此是唯一允许提供权威最新读取或提交写请求给 Raft group leader 的副本。所有写操作都经过了 leaseholder,因此所有的读都可以在不牺牲一致性的情况下绕过 Raft 所需的网络往返成本。

       用户级 Ranges 的租约和 leaseholder 所在节点的存活性绑定,存活性通知通过节点每 4.5 秒发送一个特殊心跳到系统级 Range 实现。系统级 Range 转而使用基于到期的租约,必须每 9 秒更新一次。如果某个节点探测到 leaseholder 不存活了,它就尝试自己获取租约。

       为了确保每个时间点只有一个副本拥有租约,网站源码自己改租约获取在现有的 Raft 框架内完成,提交一个特殊的获取租约日志记录。每个租约获取请求包含一个它在请求时认为合法的租约数据,两个副本的请求内的租约不重叠就可以达成这个保证。在第 4 节中,我们还会讨论租约不重叠是 CRDB 隔离机制的前提。

       成员变化与自动负载(再)平衡

       集群运行中,节点可能加入或离开该集群,也可能暂时或永久失败。CRDB 使用相同的方法应对这些场景:在最新的存活节点中间重新分配负载。

       节点短暂失败,而多数节点仍然可用的情况下,CRDB 可以持续运行。如果失败的是 Raft group 的 leader,Raft 保证新 leader 的选举。失败节点上先后可以重新加入原来的 group,同伴们帮它追赶错失的更新操作。方法包括:1)发送全量 Range 数据快照给它 2)发送错失的 Raft log 记录集合给它。具体选择根据该副本节点不可用期间错失的操作量作出。

       节点长时间失败,CRDB 自动根据存活的副本为复制等级不够的 Ranges 创建出新的足够的副本。其存放位置由下一节描述选择。决策依赖的相关数据比如,存活节点信息、集群监测指标使用点对点的 Gossip 协议分发。

       副本存放

       支持手动和自动选择。

       手动选择需要用户为每个节点配置属性,比如节点特性(特殊硬件、RAM、硬盘类型...)、节点位置(国家、地区、可用 zone...)。还可以在表模式里指定限制、偏好,比如指定 region 列,可以用来帮助分区,和把分区映射到特定地理区域。

       自动选择根据用户制定的规则和不同的启发式算法自动跨失败域分布副本,容错不同程度的失败(硬盘级、机架级、数据中心级、区域级别)。

       数据存放策略

       CRDB 的副本存放和 leaseholder 存放机制支持广泛的数据存放策略,用户可以借此做到数据合规,并在性能和容错间合理取舍。以下是一些多区域模式。

       本文篇幅较长,将分为三篇发布。

位新兴开源项目背后的明星创始人

       InfoWorld整理了一份“ star founders of high-flying open source projects”名单,旨在揭示当今最重要和最具创新性的开源项目背后的驱动力。随着开源的演变,它已获得企业界的接纳,改变了企业和开源社区的格局。现今,最具影响力的开源项目多源于大公司的合作,但仍对整个社区产生积极影响。创造者在提高知名度的同时,也从他们的作品中获益。

       A new generation

       早期的开源运动由Linus Torvalds引领,他以学生身份创建Linux,最终成为Linux内核社区的主导人物。然而,开源环境已发生变化,开源项目如今更常在大公司内部合作中诞生。尽管如此,它们依然保持开源特性,为整个社区提供支持,创造者也从中受益。

       以下是一些关键开源项目背后的创新者:

       Apache Kafka:Jay Kreps、Neha Narkhede、Jun Rao

       Kafka数据流解决方案的诞生,是为了解决公司处理和传输数据的挑战,LinkedIn是最早遭遇此问题的公司之一。Jay Kreps、Neha Narkhede和Jun Rao三位工程师在LinkedIn工作时,参与了一个消息系统项目,最终发展为Kafka。Kafka的开放源代码发布后,迅速成为多个公司和项目的重要组成部分,并催生了Confluent公司。

       Redis:Salvatore Sanfilippo

       Redis高性能key-value数据库的出现,弥补了memcached等数据存储的不足,成为关系数据库的有力补充。Salfilippo推出Redis后,将其开源,项目迅速发展。随后,他被VMware聘请,继续参与Redis项目。

       Kubernetes:Joe Beda、Brendan Burns、Craig McLuckie

       Kubernetes作为容器编排系统,已成为大小公司的关键基础设施。它起源于谷歌内部,由Joe Beda、Brendan Burns和Craig McLuckie共同创建,基于谷歌早期的Borg集群管理器概念。Kubernetes最初被称为“Project 7”,以《星际迷航:航海家》中的Borg角色七号命名。

       Apache Spark:Matei Zaharia

       Apache Spark,数据处理平台,取代了MapReduce,成为大数据领域的佼佼者。Spark诞生于学术界,由Matei Zaharia在加州大学伯克利分校获得博士学位时奠定基础。几乎从诞生之初,Spark就是开源的。

       Ansible:Michael DeHaan

       Ansible自动化平台的关键部分,也是基础设施即代码运动的重要工具。基于YAML的Ansible Playbook定义服务器配置,通过SSH自动部署至目标系统。Michael DeHaan是Ansible的创造者,其职业生涯展示了开源项目和创造者可能遵循的路径。他后来围绕Ansible建立公司,并被其老东家红帽收购。

       Node.js和Deno:Ryan Dahl

       Node.js和Dahl遵循了开源轨迹,项目质量达到临界点后,项目和创造者转移到可以孵化其的营利性公司。几年后,Dahl离开,Node.js被转移到基金会。随后,Dahl提出Deno,一个更安全、更高效的JavaScript运行时,旨在改进Node.js。Deno发展迅速,Dahl成立新公司作为其总部。

       CockroachDB:Spencer Kimball、Peter Mattis、Ben Darnell

       Spencer Kimball和Peter Mattis在加州大学伯克利分校共同开发GNU Image Manipulation Program和GTK,这两个基础软件促进了开源的可行性。在硅谷企业中度过一段时间后,他们与Ben Darnell联手推出CockroachDB,一个基于分布式架构的SQL数据库。CockroachDB借鉴了Kimball、Mattis和Darnell早期的谷歌经验,与Google Cloud Spanner竞争,作为开源项目,它对任何人免费提供。

       Django:Simon Willison、Adrian Holovaty

       Simon Willison和Adrian Holovaty在为Lawrence Journal-World工作时,设计了基于Python的网络应用程序框架Django,以快速创建复杂数据库驱动的网络应用。Django在Python Web开发领域大受欢迎,至今仍具有重要地位。虽然Willison和Holovaty的名声不如名单上的其他人物,但他们拥有有趣且富有成效的职业生涯。Holovaty为华盛顿邮报等出版物的科技新闻项目工作,而Willison与妻子合作开发活动平台,被Eventbrite收购。

       “如名单上的其他人一样,许多创造者从他们的开源工作获益,这无疑使他们感到满足。”

GitLab在CockroachDB和YugabyteDB上的兼容性对比(二)-读写场景测试

       在本篇文章中,作者何傲,作为神州数码集团的高级开发工程师,分享了对GitLab在CockroachDB和YugabyteDB上的兼容性对比测试。从测试背景来看,GitLab作为一个流行的源代码管理工具,为了应对大型企业中分布式仓库管理的需求,人们开始考虑将其部署在分布式数据库上,如CockroachDB和YugabyteDB,这两者都支持PG协议。

       在上一篇文章中,我们发现CockroachDB由于不支持GitLab的自动数据库schema创建,导致GitLab无法启动,而YugabyteDB则可以顺利启动。本次测试,作者将标准GitLab库和数据导入到两个数据库,然后测试了一系列关键业务场景,包括项目列表、项目视图等。

       迁移和测试过程中,CockroachDB在项目创建、GitLab导入和部分业务操作(如Merge Request)中遇到问题,如索引不兼容、未定义的列错误等。而YugabyteDB在大部分场景中表现良好,尽管在项目创建和GitLab导入上存在持续加载和权限问题,但整体兼容性较好。

       总结来说,YugabyteDB在本次测试中的兼容性优于CockroachDB,但在Merge Request相关的操作上仍可能存在待解决的问题。dbaplus社群是一个专注于Database、BigData、AIOps的专业社群,提供技术分享和资源,如果你对数据库技术感兴趣,可以关注公众号dbaplus社群获取更多内容。

CockroachDB 源码闲逛 - II (insert a row)

       本文将深入探讨 CockroachDB 的启动过程以及处理一条简单 SQL(如插入一行数据)的具体流程。CockroachDB 使用 Go 语言中流行的 Cobra 库来构建其命令行界面(CLI),在使用 `start` 命令启动服务端后,代码从特定位置开始执行。

       启动初期,CockroachDB 会准备好各种日志和 pprof 功能。pprof 功能允许通过开关控制定期导出 CPU 和内存(通过 go/jemalloc)的性能分析报告,并定期清除旧的 pprof 数据,这有助于在排查问题时找到事故现场的堆栈或性能数据。

       之后,服务端使用一个端口同时处理 PostgreSQL、HTTP 和 gRPC 协议,代码进入 `Server.start()` 方法。这个方法包含复杂的逻辑用于节点发现和 bootstrap。主要关注点在于 SQL 处理,尤其是 PostgreSQL 协议下的客户端连接。

       当客户端通过 PG 协议连接到服务端时,代码进入 `pgwire.Server#ServeConn` 方法。通过校验版本等步骤后,进入 `conn.serveImpl` 方法,这是处理请求的主要逻辑。在这里,每个客户端连接由两个 goroutine 分别处理读取协议解析和命令执行。这种设计允许在执行过程中同时接收客户端连接事件,例如在执行大规模 SQL 过程时,通过关闭其中一个 goroutine 可以在 SQL 执行的同时响应客户端的 `FIN` 指令。

       在客户端连接的两个 goroutine 准备好后,发送的 SQL 语句开始在 `coordinator-side` 进行处理。首先,`read goroutine` 解析网络包,并根据不同的 PG cmd 分发到相应的方法进行处理。对于简单的文本执行查询,`handleSimpleQuery` 方法相对简单。为了区分不同批量的命令,当一组命令推送到 `stmtBuf` 后,会插入一个哨兵 `Sync` 来标记当前批次结束以及后续命令属于下一个批次。

       随后,`process goroutine` 从 `stmtBuf` 中获取命令,根据不同的命令类型分发到相应的 `exec*` 方法。例如,简单查询产生的 `ExecStmt` 会进入 `execStmt` 方法,在此之前会创建 `stmtRes` 来封装后续返回客户端响应的缓冲区刷新逻辑。

       在处理 SQL 语句时,CockroachDB 会维护一个状态机(StateMachine),用于管理当前连接的事务状态。状态机的定义和行为主要与事务相关,包括 `noTxn`、`open`、`abort`、`implicit` 等状态。在处理插入一行数据的简单语句(如 `INSERT INTO t (a) VALUES (1);`)时,流程如下:

       首先,客户端与服务端建立连接,启动两个 goroutine。当插入语句发送到服务端后,`read goroutine` 开始解析并放置到 `stmtBuf`。

       随后,`process goroutine` 从 `stmtBuf` 拿出命令,识别为 `ExecStmt`。由于执行此语句前未开始事务,当前连接的状态机处于 `stateNoTxn`。因此,执行 `execStmtInNoTxnState` 方法,因为没有事务,仅执行 `execStmtInNoTxnState` 的默认分支,返回 `eventTxnStart` 事件和 `eventTxnStartPayload`。此时,状态机应用 `noTxnToOpen` 进程,为隐式事务的启动做准备。服务端通过 `client.NewTxn` 创建事务,获取时间戳并准备 `sender` 和 `coordinator` 等工作。接着,设置 `advanceInfo` 为 `advanceOne`、`noRewind`(无需回移 `stmtBuf`,通常重试时需要回移)和 `txnState` 为 `txnStart`。事务状态为 `open` 后,`execCmd` 会从 `stmtBuf` 中继续取出插入语句并执行。

       当当前事务状态为 `open` 且为 `implicit` 时,`execStmtInOpenState` 方法继续执行。由于当前 SQL 不是 `BEGIN`、`COMMIT` 等操作,挂载了 `handleAutoCommit` 的 `defer` 函数,并处理 `AS OF` 时间逻辑后,进入 `dispatchToExecutionEngine` 方法。

       在 `makeExecPlan` 方法中,创建逻辑计划。接下来,评估是否能够分布执行逻辑计划(对于插入操作,CockroachDB 当前不支持分布式计划)。然后,为逻辑计划准备上下文,调用 `execWithDistSQLEngine`。对于不可分布执行的情况,创建简化版的 `planCtx`,用于生成物理计划。在此步骤中,生成物理计划(如 `row count` 算子)并最终生成执行流程。

       在准备和生成流程后,服务端启动在本地节点的执行流程。通过 `local execution` 的 `setup` 和 `run` 方法,执行生成的处理器(如 `planNodeToRowSource`)。在 `run` 方法中,执行 `rowCountNode` 算子,进而触发 `insertNode` 的 `BatchNext`,以火山模式(一次过一个批处理的多个行)执行插入操作。

       插入操作中,`BatchNext` 分批处理,根据 `maxInsertBatchSize`(默认为 )进行分批。对于非最后一批次,会通过 `txn.Run` 发送至存储节点,将数据分批存储。在 `checkHelper` 函数中,检查表约束,分为 `eval` 和 `input` 模式,前者是老逻辑,后者在插入前检查约束结果,作为插入算子的输入,有利于优化插入操作。

       添加批处理时,调用 `initResult` 准备每个 `CPut` 的结果。如果批处理中某个命令失败(如序列化失败),会在 `initResult` 中保存序列化失败信息。

       之后,将准备好的批处理发送至 `replica-side`。在 `finalize` 中,将 `EndTransactionRequest` 添加到批处理的末尾,通过 `txn.Run` 发起。此时,批处理中包含一个条件 `put` 和一个结束事务请求,服务端通过 `DistSender.Send` 将批处理发送至 `replica-side`。批处理中的 `result` 包含 `err` 信息,用于验证批处理序列化无误。

       在 `replica-side`,请求到达节点的存储层,找到相关范围的副本对象并处理等待逻辑。对于写入操作,使用 Raft 进行 `Replica.executeWriteBatch`。在此方法中,使用 `Latch` 机制来优化对交叠和非交叠批处理的处理,同时执行批处理命令的 `evaluateWriteBatch` 方法将所有命令应用到数据中,生成 `engine.Batch` 并构建 `ProposalData`。最终,通过 Raft 提出修改,实现数据的最终一致性。

       最后,执行成功或失败后,结果会沿原路径返回至客户端。

       总结,本文详细阐述了 CockroachDB 从启动到处理简单 SQL(如插入操作)的全过程。通过深入分析,读者能够更好地理解 CockroachDB 的内部工作机制,为后续阅读代码提供基础。未来计划将关注点扩展到重试处理逻辑,进一步探索 `stmtBuf` 和状态机在 CockroachDB 中的使用。

更多内容请点击【热点】专栏