酒泉市网站建设_网站建设公司_Spring_seo优化
2025/12/17 9:31:04 网站建设 项目流程

一、读写——仅讨论raft与rocksdb层面无mvcc与transaction

一、写入流程

涉及组件TIDB Server、PD、TIKV
各组件所做工作:

1. TiDB Server

  • 接收用户写请求,解析为 Key-Value 修改指令
  • 向 PD 要两个关键信息:① 该 Key 所属的 Region 及 Leader 节点地址;② 一个起始时间戳(start_ts,用于标记操作顺序)
  • 直接把修改指令发给 TiKV 的 Leader 节点

2. PD

  • 维护集群导航信息:谁是哪个 Region 的 Leader、数据存在哪里
  • 给 TiDB Server 分配 start_ts + 返回数据的存储位置(Region + Leader 地址)

3. TiKV Leader 节点(按顺序执行)

  1. ① 提议(Proposal):Raftstore Pool接收写请求后,把修改指令包装成 Raft 日志,写入 WAL(预写日志文件)
  2. ② 持久化(append):Raftstore Pool将该 Raft 日志写入 RocksDB Raft 实例(专门存储 Raft 日志的 RocksDB,非用户数据)完成持久化
  3. ③ 复制(Replicate):Raftstore Pool把持久化后的 Raft 日志同步给该 Region 的其他 Follower 节点
  4. ④ 提交(Committed):Raftstore Pool收到多数节点(超过一半)的日志复制确认后,标记该日志 “已提交”,并更新集群commitIndex(已提交日志的最大索引)
  5. ⑤ 应用(Apply):Raftstore Pool将已提交的日志推送给Apply PoolApply Pool解析日志中的 Key-Value 操作,写入存储用户数据的RocksDB,同时更新applyIndex(已应用到用户数据的最大日志索引)

随:Raftstore PoolApply Pool都是线程池,本质都是设定好的执行容器,当然其中的配置信息,你可以设置,需要注意的是这两个都是每个node中都有的

二、读取流程(两种方式,读 “已提交的完整数据”)

方法 1:ReadIndex(从 Leader 读,保证数据最新)

  1. 用户发起读取请求 → 与 TiDB Server 交互 → TiDB Server 向 PD 请求目标 Key 所属 Region 的 Leader 节点路由信息,PD 返回位置信息
  2. TiDB Server 携带路由信息,向目标 Leader 节点发起读请求
  3. Leader 节点确认:
    • 基础方案:向集群其他节点发送心跳确认自己仍是当前 Region 的 Leader(会引入网络延迟)
    • 优化方案(Lease Read 本地读):检查当前时间是否在租约有效期[当前时间, 当前时间+election timeout]内,若在则直接确认 Leader 身份,无需心跳
  4. 核心步骤:确定 ReadIndex 保证线性一致性
    • 原理:Region 内的所有写请求会生成按序排列的 Raft 日志,日志索引单调递增(先提交的日志索引小,后提交的索引大)。读请求需要确保读取到所有在它之前提交的写操作结果,因此需要一个 “最小安全索引” 作为 ReadIndex
    • 实现:Leader 直接取当前的commitIndex(集群已提交的最大 Raft 日志索引)作为 ReadIndex—— 这等价于 “挑一个比所有已提交写请求 ID 都大的标尺”,无需额外查找
  5. Leader 节点等待本地的applyIndex(已应用到 RocksDB 的日志索引)追上 ReadIndex(即applyIndex >= ReadIndex),确保所有已提交的写日志都已被 Apply Pool 解析并写入 RocksDB
  6. 待条件满足后,Leader 节点直接从本地 RocksDB 中查找目标 Key 值并返回结果

方法 2:Follower Read(从 Follower 读,减轻 Leader 压力)

  1. Follower 先从 Leader 同步最新的 “已提交日志索引”(commitIndex)
  2. 等自己把所有已提交的日志都应用到 RocksDB 后,再返回数据

注:哪怕 Follower 处理得比 Leader 快,也会等 Leader 的最新提交信息,保证读的数据和 Leader 一致

问题聚合

问题1

问题:从 TiDB Server 获取 Leader 节点路由信息,到实际去该节点读取数据的这段时间内,如何保证这个节点仍然是路由所指的leader呢,即合法性?(毕竟集群可能会因热点负载均衡或手动操作触发 Leader 切换)

解决方法
  1. 基础方案:心跳确认 Leader 有效性,读取请求到达目标节点后,该节点会先向集群内其他节点发送心跳,确认自己还是当前 Region 的 Leader。这种方式能保证 Leader 身份准确,但会引入额外的网络延迟,影响读取性能。

  2. 优化方案:Lease Read(本地读),消除心跳延迟Leader 节点会记录两个关键时间:

    • 当前时间
    • Raft 协议的election timeout(选举超时时间)Leader 会划定一个租约有效期[当前时间, 当前时间 + election timeout]。在这个时间段内,集群不会触发新的 Leader 选举,因此该节点可以直接确认自己的 Leader 身份,无需发送心跳。

问题2

当用户 A 修改数据的写请求执行到 “Committed(提交)” 阶段但未完全落地时,若用户 B 此时读取该数据,如何避免读到旧数据?是否必须等待用户 A 的写请求完全提交落地后,用户 B 的读请求才能执行?

核心解决思路:通过 ReadIndex 机制保证读取的线性一致性

线性一致性:后发起的读请求,必须能读到先发起的已提交写请求的结果(即用户 B 读数据,必定拿到用户 A 修改后的最新数据)。

具体实现逻辑:

  1. 写请求的有序性基础:Region 内所有写请求会生成<Region号_ID, 写入请求ID>的唯一标识,且按 ID 从小到大严格排序 ——ID 越小,写请求越先被集群提交(Committed)。
  2. ReadIndex 的选取规则:为读请求选取一个 “最小安全索引(ReadIndex)”,这个索引是比当前所有已提交写请求 ID 更大的数值(TiDB 中直接取 Leader 节点当前的commitIndex,即集群已提交的最大写日志索引),并将该 ReadIndex 记录在 Raftstore Pool 中。
  3. 读请求的执行条件:读请求不会立即执行,必须等待本地 Apply Pool 维护的applyIndex(已落地到 RocksDB 的最大日志索引)追上 ReadIndex(applyIndex ≥ ReadIndex)。
    • 这意味着:所有 ID 小于 ReadIndex 的写请求(包括用户 A 的修改请求),都已完成集群提交且落地到 RocksDB 后,读请求才会执行。
    • 最终效果:读请求不会被 “堵塞” 在网络层面,而是通过索引等待机制,确保读取到的是前序所有已提交写请求修改后的最新数据,既保证线性一致性,又避免无意义的等待。

问题3

问题:在方法2中,可能会读取到「未被集群确认提交,但 Follower 本地提前落地」的数据,那如何保持数据一致呢?

可能会读取未确认数据核心产生原因:Leader 落地数据慢,Follower 反而快
1. Leader Apply 慢:写请求压力 + 多 Region 资源分摊

Leader 是 Region 的唯一写入口,需同时承担两类核心任务,导致 Apply 速度慢

简单说:Leader 既要 “存储与写请求”,在高并发场景下,让多线程分摊多个 Region 的 Apply 任务,Region 的 Apply 资源被稀释,热点 Region日志会持续生成并提交,单个Region对应的 Apply 线程来不及处理热点导致applyIndex追不上commitIndex,出现日志堆积,表现为 Apply 速度慢

2. Follower Apply 快:无写压力 + 多线程专注处理

Follower 不接收外部写请求,核心工作仅为:

  • 从 Leader 同步 Raft 日志,写入本地 WAL 持久化
  • 待 Leader 确认日志 “已提交” 后,更新commitindex,Apply Pool 的多线程专注处理同步过来的日志

由于无写请求干扰,Follower 的多线程可集中资源处理各 Region 的 Apply 任务,不会出现日志堆积,因此applyIndex能快速跟上已提交日志进度,甚至比 Leader 更快

总结:Follower 节点把 Raft 日志解析并写入 RocksDB 的速度,比 Leader 节点更快,导致 Follower 本地的applyIndex数值,会比 Leader 节点的applyIndex数值更大。

核心解决方法
前提:
前提 1:单个region的落地操作必须按顺序来

TiKV 里的每个 Region,把日志解析后写入 RocksDB后,必须按日志提交的顺序一步步执行

前提 2:多线程是为了让不同分区并行干活

不管是 Leader 还是 Follower 节点,都能通过raftstore.apply-pool-size配置多个 Apply 线程。这些线程的作用是同时处理不同分区的落地任务,如线程 1 处理regionA、线程 2 处理region B,提升整个节点的工作效率,而不是让一个region的任务被多个线程同时处理即单个region串行化操作,这样能避免同时写入导致数据顺序乱掉,保证前提交前落地。

一、关键保障:Follower Apply 快但不破坏一致性

Follower 即便applyIndex更高(Apply 更快,即本地落地的日志索引比 Leader 大),也不会导致读请求获取不一致数据,核心靠两层机制保障:

1. Follower 读请求以 Leader 的 commitIndex 为 “安全标尺”

Follower Read 的核心规则:

  • Follower 收到读请求后,不会直接使用自身applyIndex判断,而是先向 Leader 同步最新的commitIndex(记为leader_commitIndex);
  • 即便 Follower 自身applyIndex已超过leader_commitIndex(比如 Leader commitIndex=100,Follower applyIndex=105),也仅等待applyIndex ≥ leader_commitIndex,且只读取该标尺及之前的日志对应数据,避免读取本地提前应用但集群未确认的日志。
2. 集群一致性的核心:Leader 的 commitIndex 是全局唯一标准
  • 集群 “已提交数据” 的唯一判定依据是 Leader 确认的commitIndex(需多数节点确认日志提交),非单个节点的applyIndex
  • Follower 提前 Apply 的日志(如索引 101-105),本质是 “未被集群认可提交的日志”,仅为本地提前解析落地,读请求会严格过滤这类数据,确保只读取 Leader 确认的、集群一致的已提交数据

二、Coprocessor协同处理器

  • 问题:如果所有数据都拉到 TiDB Server 再计算(当TIDB server接收用户的sql语句,调用node节点中的数据,由于数据分散,就会造成数据的聚合,以及需要统计信息),那么网络和 Server 负载会很大。
  • 解决:让 TiKV 的 Coprocessor 先做 “初步计算”—— 比如过滤不需要的数据、统计数量(count/sum),再把结果传给 TiDB Server 做最终整合。
  • 核心:把能在数据存储端做的计算,就不在 Server 端做,提升效率。

随:因为TIDB Server在TIKV之上,所以叫做计算下推,这种做法减少着TiDB Server的压力

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询