想象你是一位超大城市的总图书管理员,手底下有上百家分馆,市民不管从哪个分馆借书,看到的都必须是同一套“最新藏书记录”。为了保证这一点,你规定每本书至少在三个分馆各存一份;分馆之间必须有一份“抄本核对协议”,确认哪一本才是当前权威版本;每一次借还、批注都要留下修改历史,让读者在任何时刻都能看到一致的视图;当某个分馆的藏书超过一万册时,还要把书架拆成若干小区域,分给更多分馆共同负担。这些图书馆管理员的日常烦恼,正是 TiKV 这个分布式事务 KV 存储引擎每天都在解决的问题。 TiKV 是 TiDB 的存储底座,负责把海量的键值对数据可靠地分散在上百台机器上。它用 Raft 算法让每个数据分片拥有多个副本,用 MVCC 让读和写互不阻塞,用类 Percolator 的两阶段提交让跨节点的修改拥有原子性,又用 Region 的自动分裂与调度,让系统在扩容、故障时持续保持平衡。这篇文章就像一次馆长培训课,从书架怎么摆、分馆怎么核对,一路讲到整座联盟图书馆如何自动运转。

岗位关联

对 AI 从业者而言,弄懂 TiKV 等于拿到了分布式数据库内核的“操作手册”。无论是做分布式系统架构、性能调优,还是参与 TiDB 开源社区,你都能一眼看穿存储系统在每个时刻正在做什么、为什么这样做。

目录
  1. 可视化演示
  2. 总体架构
  3. Raft一致性
  4. MVCC机制
  5. 事务模型
  6. Region管理
  7. 高可用机制
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

演示TiKV核心机制,包括Raft多副本一致性、MVCC多版本并发控制、Percolator分布式事务模型和Region分裂调度,通过交互式可视化展示分布式存储底层原理。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Raft多副本一致性 Leader接收写请求 日志复制到Follower 多数派确认后提交 三副本保证数据不丢失

总体架构

想象你是那座超大城市联盟图书馆的第一任总馆长。市民不需要知道书放在哪个分馆,他们只面对一个统一的“检索台”。为了让这套系统能无限扩张,你不会把全城书目抄成一本大厚册子,而是把书架按照编号切成许多连续的区块,每块配三位馆员负责。这正是 TiKV 的顶层设计思路:把数据切成一格一格的 Region,每个 Region 就是一段连续键值的“书架”。

从数据模型看,TiKV 是一个 Key-Value 存储:每个数据项都是一对 (key, value),其中 key 是二进制字符串,value 是字节数组。所有 key 按字典序排列,被切分到不同的 Region 中。每个 Region 默认由 3 个副本组成,这 3 个副本分布在不同的物理节点上,构成一个 Raft 复制组,共同对外提供“这一个 Region 不丢数据”的承诺。

整套系统在逻辑上分为四层:最底层用 RocksDB 做单机持久化存储;往上是 Raft 层,负责副本间的日志复制与一致性;再往上是事务层,实现分布式事务;最顶上是 PD(Placement Driver),负责记录每个 Region 的位置、管理分裂与故障迁移。分层设计的妙处在于,每一层都可以独立演进,就像图书馆的采购、编目、馆际互借各有专人负责一样。

回到图书馆的比喻:只有当每格书架都边界清晰、责任到人,整座联盟图书馆才能永远保持“无论书在哪个分馆,市民总能找到它”的承诺。TiKV 的顶层架构,归根结底做的就是这件事。

Region={ (key,  value)∣start_key≤key<end_key }\text{Region} = \{ \, (\text{key},\; \text{value}) \mid \text{start\_key} \le \text{key} < \text{end\_key} \, \}
这个公式定义了 Region 的本质:它是键值对的集合,且只收纳“键”落在某个左闭右开区间内的数据。
  1. 键值对集合Region 是一组 (key, value) 条目的集合,就像一格书架上的所有藏书。
    Region={ (key,  value)∣⋯ }\text{Region} = \{ \, (\text{key},\; \text{value}) \mid \cdots \, \}
  2. 区间边界条件 start\_key ≤ key < end\_key 是左闭右开的范围限制,保证任意 key 恰好属于一个 Region,不会重叠。
    start_key≤key<end_key\text{start\_key} \le \text{key} < \text{end\_key}
  3. 整体覆盖所有 Region 的并集等于全量键空间,就像所有书架并在一起就是整座图书馆的馆藏。
    ⋃iRegioni=全量数据\bigcup_{i} \text{Region}_i = \text{全量数据}

Raft一致性

三个分馆各存了一本《国富论》,但其中一本被读者做了批注,另外两本没有。你作为馆长必须规定:到底哪一本才是“官方版本”?如果每次有读者来问,都临时开三方会议,效率太低。于是你指定:平时由“主馆”负责对外回答,但它改书之前,必须把改动同步给其他分馆,得到多数确认后才能正式生效。这就是 Raft 协议的核心思路。

Raft 是 TiKV 中每个 Region 副本组使用的共识算法。它把一个复制组里的节点分成三种角色:Leader(主副本)、Follower(从副本)和 Candidate(竞选者)。正常情况下,只有 Leader 接收客户端写入,它把写操作追加为一条日志,并行发给所有 Follower,等多数派节点确认后,才把这条日志“提交”并应用到状态机。如果 Leader 宕机,Follower 会通过选举选出新 Leader,选举同样遵循多数派原则。

Raft 的核心价值在于“让多数派说了算”。它解决了三个子问题:Leader 选举、日志复制、成员变更。成员变更允许在不中断服务的情况下增减副本,比如把一个 Region 从 3 副本扩容到 5 副本。整个过程不需要人工指定谁当主,系统自己就能达成一致——就像图书馆联盟的总馆名号不是永久固定的,谁的能力获得多数分馆认可,谁就接班。

分馆之间核对抄本、按多数派盖章生效,听起来繁琐,却换来一个极其宝贵的性质:只要多数派节点还活着,整组服务就能继续。图书馆不会因为一家分馆临时停电就关门,TiKV 也不会因为一台机器宕机就丢数据。

q=⌊n/2⌋+1q = \lfloor n / 2 \rfloor + 1
这是 Raft 的“法定人数”公式:n 个副本的复制组,需要至少 q 个节点确认才能提交决策。
  1. 副本总数 nn 代表当前 Raft 复制组中参与投票的副本数量,例如 3 副本或 5 副本。
    n=副本总数n = \text{副本总数}
  2. 向下取整⌊n/2⌋ 表示对 n 的一半向下取整,例如 3 副本时取 1。
    ⌊n/2⌋=⌊n2⌋\lfloor n / 2 \rfloor = \left\lfloor \frac{n}{2} \right\rfloor
  3. 超过半数加上 1 之后,q 必然大于 n/2,这就是“多数派”的数学含义。
    q=⌊n2⌋+1>n2q = \left\lfloor \frac{n}{2} \right\rfloor + 1 > \frac{n}{2}

MVCC机制

图书馆的借阅卡片上,如果管理员每次都在同一格里涂改,借阅者就无法知道“上次归还时书是什么状态”。于是你规定:每一次借出、归还、续借都追加一行记录,标注时间和操作人。读者查询时,系统只展示“他查询那一刻之前最接近的一条有效记录”——这就是多版本并发控制(MVCC)在图书馆里的样子。

在 TiKV 中,每个 key 可以同时存在多个版本的数据,每个版本都带有一个全局递增的提交时间戳 commit_ts。当事务读取某个 key 时,它会携带一个 read_ts(读取时间戳),存储引擎只返回满足“commit_ts ≤ read_ts”的最新版本。这样,一个长时间运行的事务也能看到一致的快照,而不会被其他并发写入干扰。

MVCC 最大的收益是“读写互不阻塞”:写操作永远追加新版本,不覆盖旧版本;读操作永远在旧版本快照上进行,不需要加锁。这样一来,在线业务最常见的读多写少场景就不会因为锁竞争而卡顿。不过,版本越积越多会浪费空间,因此 TiKV 会定期做垃圾回收,清理那些“任何活跃事务都看不到”的旧版本。

图书馆的读者永远看不到“正在被涂改的那一行”,看到的是他进门那一刻之前最完整的记录。MVCC 的智慧正是如此:用历史版本的叠加代替就地修改,让并发不再是敌人,而是秩序的一部分。

visible(v,ts)=(v.commit_ts≤ts)\text{visible}(v, \text{ts}) = \bigl( v.\text{commit\_ts} \le \text{ts} \bigr)
这个公式描述了 MVCC 的可见性规则:一个版本 v 在读取时间戳 ts 下是否可见,取决于它是否已提交且提交时间不晚于 ts。
  1. 版本 vv 表示某个 key 的一条历史版本记录,包含当时的 value 和 commit_ts。
    v=(value,  commit_ts)v = (\text{value},\; \text{commit\_ts})
  2. 读取时间戳 tsts 是读事务开始时获取的全局时间戳,决定了该事务能看到哪些版本。
    ts=读取时刻的全局逻辑时钟\text{ts} = \text{读取时刻的全局逻辑时钟}
  3. 已提交且不晚于v.commit_ts ≤ ts 是可见性判据,只有“已经提交”且“提交得足够早”的版本才被读到。
    visible(v,ts)=(v.commit_ts≤ts)\text{visible}(v, \text{ts}) = \bigl( v.\text{commit\_ts} \le \text{ts} \bigr)

事务模型

一位市民在分馆 A 还掉旧书,同时想从分馆 B 借走另一本书。这两个操作必须“要么都成功、要么都不成功”——否则就会出现书丢了却没借到的混乱。馆长定下一个两步协议:第一步,先在两个分馆的账本上写下“此书正在处理中”的锁定标记并预登记;第二步,等两边都确认无误,再盖上“正式生效”的章,同时解除锁定。这个协议,就是 Percolator 两阶段提交模型的现实原型。

TiKV 的分布式事务基于 Google 的 Percolator 论文设计,核心目标是在没有全局锁的前提下,让跨多个 Region、多个 key 的写入拥有原子性。事务开启时先获取一个 start_ts;提交时获取一个更大的 commit_ts。流程分为两阶段:预写(Prewrite)阶段,事务向每个涉及的 key 写入一个带 start_ts 的预存值,并尝试加锁;如果任何 key 的锁已经被其他事务占用,则整个事务回滚。提交(Commit)阶段,所有预写成功后,事务用 commit_ts 打上最终时间戳,释放全部锁,数据对外可见。

这种设计最精妙的地方在于:没有任何中央协调者,每个 key 的锁自包含在自己的记录里。读操作遇到锁时,只需根据锁中的 start_ts 判断持有锁的事务是否已经超时,进而决定等待还是清理。由于 commit_ts > start_ts,所有并发事务都能依据时间戳形成唯一的历史顺序,不会出现“先提交却后可见”的错乱。

借书协议里的“先锁定、后盖章”,映射到数据库就是“先预写、后提交”。它让所有分馆的账本在任意时刻都能达成一致——哪怕没有一个中央指挥官,每个馆员也清楚自己该做什么。

CommitValid(T)=(∀k∈T:prewrite(k)=ok)∧(commit_ts>start_ts)\text{CommitValid}(T) = \bigl( \forall k \in T: \text{prewrite}(k) = \text{ok} \bigr) \land \bigl( \text{commit\_ts} > \text{start\_ts} \bigr)
这是事务提交是否有效的判定式:事务涉及的所有 key 都预写成功,且提交时间戳必须晚于开始时间戳,满足这两条才允许提交。
  1. 遍历所有 key∀k ∈ T 表示对事务 T 写入的每个 key 逐一检查预写结果。
    ∀k∈T\forall k \in T
  2. 预写成功prewrite(k) = ok 表示该 key 成功写入预存值且锁未被他人持有。
    prewrite(k)=ok\text{prewrite}(k) = \text{ok}
  3. 时间戳递增commit_ts > start_ts 保证“提交发生在开始之后”,这是建立全局历史顺序的基石。
    commit_ts>start_ts\text{commit\_ts} > \text{start\_ts}
  4. 两者同时成立用逻辑与连接两个条件,任何一边不满足都不能提交。
    CommitValid=条件一∧条件二\text{CommitValid} = \text{条件一} \land \text{条件二}

Region管理

一万本书堆在同一格书架上,不但找书困难,搬运也极慢。馆长定下规矩:每个书架区块的书目数量超过上限,就必须自动拆成两个新块,分别挪到不同分馆。TiKV 的 Region 分裂逻辑与此几乎完全相同。

TiKV 中每个 Region 默认约 96MB 数据。当 Region 的数据量超过这个阈值时,PD 会指示该 Region 的 Leader 执行分裂操作:把键值范围从中间一分为二,生成两个大小相近的新 Region。分裂过程不需要拷贝数据,因为新 Region 只是改变了键空间的边界,底层存储仍然共享同一份 RocksDB 数据。分裂完成后,新的 Region 元数据会注册到 PD,客户端通过 PD 获取最新的路由信息。

Region 分裂的意义在于“控制单点容量上限”。单个 Region 过大会导致三个问题:一是参与复制的数据量增大,网络和 IO 压力上升;二是热点集中在一个副本组,无法发挥分布式优势;三是故障恢复时补副本的时间变长。通过自动分裂加上 PD 的负载均衡调度,TiKV 能把数据均匀地铺到整个集群中。当某个节点存储接近饱和,PD 还会把 Region 调度到空余节点,这就是所谓的“热点调度”——它会监控读写流量的分布,把热 Region 分散到更多机器上。

书架拆块看似小事,却是联盟图书馆能无限扩容的秘密。因为每一块书架都足够小,搬迁、复制、重新平衡的成本都可控;反过来,TiKV 也靠这套“拆块”机制,让上百台机器的集群像一台巨型计算机一样顺畅运转。

split(R)=true  ⟺  size(R)>96MB\text{split}(R) = \text{true} \iff \text{size}(R) > 96\text{MB}
这个公式表示 Region R 的分裂时机:当它的数据量超过 96MB 时自动触发分裂。
  1. 当前数据量size(R) 表示 Region R 当前承载的键值数据大小。
    size(R)=Region 的数据占用\text{size}(R) = \text{Region 的数据占用}
  2. 触发条件当 size(R) 大于 96MB 时,分裂条件成立。
    size(R)>96MB\text{size}(R) > 96\text{MB}
  3. 立即分裂条件为真时系统对 R 执行分裂,把一个 Region 变为两个。
    split(R)=true\text{split}(R) = \text{true}

高可用机制

某个深夜,一家重要的分馆突发水管爆裂,书架全部被淹。作为总馆长,你不会让大家等着修好——而是立刻启动预案:从其他分馆调出这本书的备份副本,在新分馆重建书架,同时对外保持“开门营业”的状态。TiKV 的高可用机制,就是这套预案的工程化实现。

在 TiKV 集群中,PD 通过心跳机制检测每个节点的健康状态。如果某个 store 节点心跳超时,PD 会判定它失联,并为其负责的每个 Region 生成“补副本”任务:选择集群中另一个健康节点,从已有副本同步数据,把新的副本建立起来。这个过程依赖 Raft 的成员变更能力,先添加新副本、再移除故障副本,整个过程对客户端透明。

那么,系统到底能容忍多少个节点同时故障?答案取决于副本数。一个 3 副本的 Region,最多容忍 1 个副本故障;5 副本的 Region,最多容忍 2 个。因为只要多数派存活,Raft 就能继续选主、继续提交日志。故障期间读写可能短暂变慢,但不会停机——这就是“无感切换”的含义。数据恢复则依靠日志的追平机制:新副本加入后,Leader 会把缺失的日志项同步过去,直到它与 Leader 状态一致。

回看图书馆的比喻:分馆可以坏,但书不能丢。TiKV 用“多副本 + 多数派 + 自动补位”三重保险,把故障从“事故”变成“日常调度里的一件小事”。这正是分布式系统高可用的本质:不是不故障,而是故障之后能自行恢复。

fmax⁡=⌊n−12⌋f_{\max} = \left\lfloor \frac{n - 1}{2} \right\rfloor
这个公式给出一个 n 副本 Raft 组最多可同时故障的副本数量:不超过一半减一。
  1. 副本总数 nn 是 Region 的副本个数,通常为 3 或 5。
    n=副本个数n = \text{副本个数}
  2. 最大容忍故障数(n-1)/2 向下取整,得到的 f_max 是能容忍的故障副本上限。
    fmax⁡=⌊n−12⌋f_{\max} = \left\lfloor \frac{n - 1}{2} \right\rfloor
  3. 多数派仍存活只要存活节点数 ≥ q 即 ⌊n/2⌋+1,该 Raft 组仍能正常工作。
    存活数≥q⇒服务继续\text{存活数} \ge q \Rightarrow \text{服务继续}

AI 应用场景

在线支付系统的分布式事务底座

阶段:在线交易数据存储层

解决的问题:传统单机数据库无法承受高并发支付请求,且跨账户转账涉及多个数据分片,必须保证原子性和一致性。TiKV 通过 Raft 多副本保证数据不丢,通过 Percolator 事务模型保证资金流转的原子性,使任意分片的读写都符合事务语义。

具体案例:某支付平台将用户余额、流水记录存入 TiKV,单笔交易同时更新“扣款方余额”和“收款方余额”两个 key。系统在预写阶段给两个 key 加锁,全部成功后再统一提交;若任一 key 被并发事务锁定,则整体回滚,用户端感受到的只是“稍后再试”。

AI特征存储与模型推理的特征服务

阶段:特征工程与在线推理

解决的问题:推荐系统中用户特征和物品特征需要高并发读写,且要求在特征更新时不影响正在进行的推理请求。TiKV 的 MVCC 让“写特征”和“读特征”互不阻塞,读请求总能拿到某一时间戳下的一致快照,非常适合作为特征在线存储。

具体案例:某短视频推荐系统把用户最近 100 个交互行为编码成 key-value 特征写入 TiKV,模型服务在线读取时携带请求到达时间戳,得到该时刻的特征快照。新行为写入不会干扰正在执行的推理批次,特征更新的延迟由 Raft 复制保证在毫秒级。

物联网设备状态海量存储

阶段:数据采集与状态管理

解决的问题:物联网平台每天接入数百万设备的上下线状态,写入量大、单条数据小,并且必须支持按设备 ID 快速查询。TiKV 的 Region 自动分裂与 PD 热点调度,能把这些高频写入均匀分散到集群中,避免单点瓶颈。

具体案例:某车联网平台将所有车辆的 GPS 坐标、电量、告警标志以“车辆ID_属性”为 key 写入 TiKV。每当某区域车辆激增,PD 会自动识别热点 Region 并将之分片后调度到其他节点,保障整个平台的路由更新和状态查询始终稳定。

Python 演示

python61 行
#!/usr/bin/env python3"""TiKV核心机制模拟:极简MVCC存储 + 两阶段提交事务。""" from collections import defaultdict  class MVCCStore:    """用Python模拟TiKV的多版本存储与事务提交语义。"""     def __init__(self):        self.data = defaultdict(list)  # key -> [(commit_ts, value)]        self.locks = {}                # key -> start_ts,模拟prewrite锁        self.ts = 0                    # 全局逻辑时间戳     def next_ts(self):        self.ts += 1        return self.ts     def get(self, key, read_ts):        """读取时间戳下最新可见版本。"""        for commit_ts, value in reversed(self.data[key]):            if commit_ts is not None and commit_ts <= read_ts:                return value        return None     def prewrite(self, key, value, start_ts):        """事务阶段一:加锁并写入预提交版本。"""        if key in self.locks:            raise RuntimeError("write-write conflict")        self.locks[key] = start_ts        self.data[key].append((None, value))  # 未提交版本以None标记     def commit(self, keys, start_ts, commit_ts):        """事务阶段二:生效提交,并释放锁。"""        for key in keys:            self.locks.pop(key, None)            for i, (ts, val) in enumerate(self.data[key]):                if ts is None:                    self.data[key][i] = (commit_ts, val)                    break  def main():    store = MVCCStore()     t1 = store.next_ts()    store.prewrite("user:1:age", "20", t1)    store.commit(["user:1:age"], t1, store.next_ts())    read_older = store.next_ts()    print("第一次读到的age:", store.get("user:1:age", read_older))     t2 = store.next_ts()    store.prewrite("user:1:age", "21", t2)    store.commit(["user:1:age"], t2, store.next_ts())    read_newer = store.next_ts()    print("第二次读到的age:", store.get("user:1:age", read_newer))    print("旧时间戳仍读到旧值:", store.get("user:1:age", read_older))  if __name__ == "__main__":    main()
目的:用不到百行代码复现 TiKV 两个核心机制——MVCC 多版本可见性判断,以及类似 Percolator 的“预写加锁 + 提交生效”两阶段流程。
  1. self.data = defaultdict(list)每个 key 对应一个版本列表,存放 (commit_ts, value) 元组。
  2. def get(self, key, read_ts)从最新往旧遍历版本,返回第一个 commit_ts ≤ read_ts 的已提交值。
  3. if commit_ts is not None过滤掉尚未提交的预写版本,保证读操作看不到“半成品”。
  4. self.locks[key] = start_ts模拟预写阶段的锁,以 start_ts 记录持有者。
  5. self.data[key].append((None, value))用 None 标记未提交版本,对应 TiKV 中的 prewrite 数据。
  6. def commit(self, keys, start_ts, commit_ts)把 None 替换为 commit_ts,版本正式生效。
  7. store.get("user:1:age", read_older)即使新版本已提交,旧读取时间戳依然读到旧值,这就是 MVCC 快照隔离。

常见误区

误区很多初学者把 Raft 理解为“选举大王”——选出 Leader 就完事了,选完主以后每个节点各写各的。

正解选举只是 Raft 三个子问题之一。真正的核心是日志复制:Leader 把所有写操作作为日志项广播给 Follower,只有日志被多数派节点确认并提交后,数据才算是“生效”。选举存在的意义,是为了在异常情况下选出一个拥有完整日志的新 Leader。

为何常见:初学 Raft 时最容易记住“投票选主”这个画面,而日志复制、提交、截断这些更繁琐的机制需要花时间消化。

误区“有了 MVCC,读写不阻塞,那事务之间都可以无锁并发了。”

正解MVCC 解决的是“读写互不阻塞”,它让写事务不必等待读事务完成。但写与写之间仍然可能冲突:两个事务同时修改同一个 key 时,照样要靠锁(TiKV 中表现为 prewrite 锁)来决定谁先谁后。Percolator 模型中的锁依然是两阶段提交的关键一环。

为何常见:把“读不用锁”推广成了“写也不用锁”,忽略了事务隔离中写写冲突这一基本问题。

误区“Region 超过 96MB 后就拷贝一份数据,变成两个 96MB 的 Region。”

正解分裂并不拷贝数据,它只是在逻辑上把键值范围从中间切开,两个新 Region 共享底层 RocksDB 存储文件。分裂后每个 Region 只是各自的键范围变小了,物理数据几乎不需要移动。“重新平衡”时才需要把新 Region 的副本调度到其他节点,这一步会发生跨节点数据传输。

为何常见:“分裂”这个词容易让人联想到细胞分裂式的物理复制,却忽略了 Region 本质上只是一个逻辑上的键范围概念。

误区“两阶段提交就是全局锁,提交期间所有其他事务都得停下来等着。”

正解Percolator 模型的锁是“按 key 粒度”的,不是全局锁。事务只锁它自己涉及的 key,其他 key 上的读写完全不受影响。即使遇到冲突,读操作也可以根据锁上的时间戳判断是否清理超时锁,而不是永久阻塞。

为何常见:经典分布式数据库教材强调两阶段提交的阻塞问题,Percolator 通过选主协议和时间戳优化缓解了这一点,初学者容易把两者混为一谈。

面试题

Q1. Raft 保证多副本数据一致性的核心手段是什么?

单选题 · easy

  • A.随机选举领导节点
  • B.日志复制到达多数派← 正确答案
  • C.所有节点同步写入
  • D.中心节点统一调度

答案:B。Raft 的核心是把“数据一致性”归结为“日志一致”。Leader 将写操作作为日志项复制给 Follower,只有超过半数节点确认后才提交,这正是多数派(quorum)原则。选主只是过程,不是一致性本身;中心调度不是 Raft 的做法。

Q2. 在 TiKV 中,数据的基本分片和复制单元是什么?

单选题 · easy

  • A.数据表
  • B.逻辑分区
  • C.Region← 正确答案
  • D.日志段

答案:C。TiKV 将整个键空间切分成连续的 Region,每个 Region 是一段 key 的范围,也是 Raft 复制组的基本单元。表是关系层概念,逻辑分区和日志段都不参与 TiKV 的存储分片。

Q3. MVCC 在 TiKV 中的主要作用是什么?

单选题 · mid

  • A.提高磁盘写入速度
  • B.避免读写互相阻塞← 正确答案
  • C.压缩历史数据空间
  • D.加速网络传输速度

答案:B。MVCC(多版本并发控制)让每个 key 的写操作追加新版本而非覆盖旧版本,读操作基于时间戳读取快照,从而读写互不阻塞。它并不压缩数据,也不直接加速磁盘或网络。

Q4. Percolator 分布式事务的提交流程是?

单选题 · mid

  • A.先全局锁表再提交
  • B.先预写加锁再提交← 正确答案
  • C.直接覆盖所有数据
  • D.依赖中心事务管理器

答案:B。Percolator 采用两阶段提交,第一阶段对每个涉及的 key 做 prewrite 加锁并写预存值,第二阶段用更大的 commit_ts 提交并释放锁。它没有中心事务管理器,也不直接覆盖旧数据。

Q5. 一个 5 副本的 Raft 复制组,最多能容忍几个副本同时故障?

单选题 · hard

  • A.1 个
  • B.2 个← 正确答案
  • C.3 个
  • D.4 个

答案:B。5 副本的多数派要 ⌊5/2⌋+1 = 3 个节点在线。因此最多可以容忍 5-3 = 2 个副本故障。若故障 3 个,剩余 2 个不足多数派,无法选出新 Leader 或提交日志。

Q6. PD(Placement Driver)在 TiKV 中的核心职责是什么?

单选题 · mid

  • A.存储所有用户数据
  • B.管理 Region 路由与调度← 正确答案
  • C.执行复杂的 SQL 查询
  • D.汇总全集群监控日志

答案:B。PD 是 TiKV 的调度中心,负责维护 Region 的位置信息、触发分裂、执行副本迁移与负载均衡。用户数据由各存储节点保存,SQL 查询由 TiDB 计算层处理,监控日志只是辅助功能。

Q7. TiKV 中 Region 自动分裂的典型触发条件是什么?

单选题 · easy

  • A.节点 CPU 使用率超过 80%
  • B.Region 数据量超过阈值← 正确答案
  • C.客户端连接数超过上限
  • D.磁盘 IO 达到饱和

答案:B。Region 分裂的经典触发条件是数据量超过约 96MB,这是为了避免单 Region 过大造成复制、恢复和热点问题。CPU、连接数、磁盘 IO 都不是 Region 分裂的直接依据。

Q8. 以下哪些属于 Raft 算法需要解决的子问题?

多选题 · mid

  • A.Leader 选举← 正确答案
  • B.日志复制← 正确答案
  • C.成员变更← 正确答案
  • D.哈希索引

答案:ABC。Raft 将共识问题拆解为三个子问题:Leader 选举、日志复制和成员变更。哈希索引是存储引擎的索引结构,与 Raft 的共识机制无关。

Q9. 关于 TiKV 的读写流程,以下说法正确的是?

多选题 · hard

  • A.读请求可由任意副本直接响应
  • B.写请求交给 Leader 处理← 正确答案
  • C.事务提交前需要预写锁← 正确答案
  • D.所有请求必须经过 PD 转发

答案:BC。写请求必须经 Leader 进入 Raft 日志;事务在 Percolator 模型中先预写加锁再提交。为满足线性一致读,读请求通常也在 Leader 上执行或携带时间戳校验,并非所有副本都能直接响应。客户端通过 PD 获取路由信息,但后续读写直接访问对应节点,不经过 PD 转发。

Q10. 在 Percolator 事务模型中,下列哪些是保证事务正确性的关键设计?

多选题 · extreme

  • A.每个 key 预写前先加锁← 正确答案
  • B.commit_ts 必须大于 start_ts← 正确答案
  • C.所有参与者必须共享同一物理时钟
  • D.预写阶段发现锁冲突则回滚← 正确答案

答案:ABD。Percolator 用 prewrite 锁检测写写冲突,任何 key 被锁住就回滚;commit_ts 必须大于 start_ts 才能保证历史顺序正确。它并不要求所有参与者共享同一物理时钟,而是依赖分布式逻辑时间戳(如 TSO)生成全局递增时间。