2026/3/25 15:20:04
网站建设
项目流程
百度网站标题,网站文章怎么做分享qq,佛山网站建设开发团队,网站建设框架模板一、读写——仅讨论raft与rocksdb层面无mvcc与transaction
一、写入流程
涉及组件TIDB Server、PD、TIKV 各组件所做工作#xff1a;
1. TiDB Server
接收用户写请求#xff0c;解析为 Key-Value 修改指令
向 PD 要两个关键信息#xff1a;① 该 Key 所属的 Region 及 …一、读写——仅讨论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 节点按顺序执行① 提议ProposalRaftstore Pool接收写请求后把修改指令包装成 Raft 日志写入 WAL预写日志文件② 持久化appendRaftstore Pool将该 Raft 日志写入 RocksDB Raft 实例专门存储 Raft 日志的 RocksDB非用户数据完成持久化③ 复制ReplicateRaftstore Pool把持久化后的 Raft 日志同步给该 Region 的其他 Follower 节点④ 提交CommittedRaftstore Pool收到多数节点超过一半的日志复制确认后标记该日志 “已提交”并更新集群commitIndex已提交日志的最大索引⑤ 应用ApplyRaftstore Pool将已提交的日志推送给Apply Pool→Apply Pool解析日志中的 Key-Value 操作写入存储用户数据的RocksDB同时更新applyIndex已应用到用户数据的最大日志索引随Raftstore Pool与Apply Pool都是线程池本质都是设定好的执行容器当然其中的配置信息你可以设置需要注意的是这两个都是每个node中都有的二、读取流程两种方式读 “已提交的完整数据”方法 1ReadIndex从 Leader 读保证数据最新用户发起读取请求 → 与 TiDB Server 交互 → TiDB Server 向 PD 请求目标 Key 所属 Region 的 Leader 节点路由信息PD 返回位置信息TiDB Server 携带路由信息向目标 Leader 节点发起读请求Leader 节点确认基础方案向集群其他节点发送心跳确认自己仍是当前 Region 的 Leader会引入网络延迟优化方案Lease Read 本地读检查当前时间是否在租约有效期[当前时间, 当前时间election timeout]内若在则直接确认 Leader 身份无需心跳核心步骤确定 ReadIndex 保证线性一致性原理Region 内的所有写请求会生成按序排列的 Raft 日志日志索引单调递增先提交的日志索引小后提交的索引大。读请求需要确保读取到所有在它之前提交的写操作结果因此需要一个 “最小安全索引” 作为 ReadIndex实现Leader 直接取当前的commitIndex集群已提交的最大 Raft 日志索引作为 ReadIndex—— 这等价于 “挑一个比所有已提交写请求 ID 都大的标尺”无需额外查找Leader 节点等待本地的applyIndex已应用到 RocksDB 的日志索引追上 ReadIndex即applyIndex ReadIndex确保所有已提交的写日志都已被 Apply Pool 解析并写入 RocksDB待条件满足后Leader 节点直接从本地 RocksDB 中查找目标 Key 值并返回结果方法 2Follower Read从 Follower 读减轻 Leader 压力Follower 先从 Leader 同步最新的 “已提交日志索引”commitIndex等自己把所有已提交的日志都应用到 RocksDB 后再返回数据注哪怕 Follower 处理得比 Leader 快也会等 Leader 的最新提交信息保证读的数据和 Leader 一致问题聚合问题1问题从 TiDB Server 获取 Leader 节点路由信息到实际去该节点读取数据的这段时间内如何保证这个节点仍然是路由所指的leader呢即合法性毕竟集群可能会因热点负载均衡或手动操作触发 Leader 切换解决方法基础方案心跳确认 Leader 有效性读取请求到达目标节点后该节点会先向集群内其他节点发送心跳确认自己还是当前 Region 的 Leader。这种方式能保证 Leader 身份准确但会引入额外的网络延迟影响读取性能。优化方案Lease Read本地读消除心跳延迟Leader 节点会记录两个关键时间当前时间Raft 协议的election timeout选举超时时间Leader 会划定一个租约有效期[当前时间, 当前时间 election timeout]。在这个时间段内集群不会触发新的 Leader 选举因此该节点可以直接确认自己的 Leader 身份无需发送心跳。问题2当用户 A 修改数据的写请求执行到 “Committed提交” 阶段但未完全落地时若用户 B 此时读取该数据如何避免读到旧数据是否必须等待用户 A 的写请求完全提交落地后用户 B 的读请求才能执行核心解决思路通过 ReadIndex 机制保证读取的线性一致性线性一致性后发起的读请求必须能读到先发起的已提交写请求的结果即用户 B 读数据必定拿到用户 A 修改后的最新数据。具体实现逻辑写请求的有序性基础Region 内所有写请求会生成Region号_ID, 写入请求ID的唯一标识且按 ID 从小到大严格排序 ——ID 越小写请求越先被集群提交Committed。ReadIndex 的选取规则为读请求选取一个 “最小安全索引ReadIndex”这个索引是比当前所有已提交写请求 ID 更大的数值TiDB 中直接取 Leader 节点当前的commitIndex即集群已提交的最大写日志索引并将该 ReadIndex 记录在 Raftstore Pool 中。读请求的执行条件读请求不会立即执行必须等待本地 Apply Pool 维护的applyIndex已落地到 RocksDB 的最大日志索引追上 ReadIndexapplyIndex ≥ 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 确认日志 “已提交” 后更新commitindexApply 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 commitIndex100Follower applyIndex105也仅等待applyIndex ≥ leader_commitIndex且只读取该标尺及之前的日志对应数据避免读取本地提前应用但集群未确认的日志。2. 集群一致性的核心Leader 的 commitIndex 是全局唯一标准集群 “已提交数据” 的唯一判定依据是 Leader 确认的commitIndex需多数节点确认日志提交非单个节点的applyIndexFollower 提前 Apply 的日志如索引 101-105本质是 “未被集群认可提交的日志”仅为本地提前解析落地读请求会严格过滤这类数据确保只读取 Leader 确认的、集群一致的已提交数据二、Coprocessor协同处理器问题如果所有数据都拉到 TiDB Server 再计算(当TIDB server接收用户的sql语句调用node节点中的数据由于数据分散就会造成数据的聚合以及需要统计信息)那么网络和 Server 负载会很大。解决让 TiKV 的 Coprocessor 先做 “初步计算”—— 比如过滤不需要的数据、统计数量count/sum再把结果传给 TiDB Server 做最终整合。核心把能在数据存储端做的计算就不在 Server 端做提升效率。随因为TIDB Server在TIKV之上所以叫做计算下推这种做法减少着TiDB Server的压力