PCTA学习笔记,依据官方文档和官方提供的视频进行整理的。
今天学习,TiDB数据库的架构,Lesson 01-=03
官方文档:https://pingkai.cn/docs/tidb/stable/tidb-computing
PCTA必学课程:101-TiDB 数据库核心原理与架构:https://learn.pingkai.cn/learner/course/1290025
Module 01:TiDB数据库架构
1、Lesson 01: TiDB数据库架构概述
1.1、TiDB数据库整体架构及核心功能
特性:水平扩容或缩容、金融级高可用、HTAP混合负载(实时HTAP)、云原生的分布式数据库、兼容MySQL5.7协议。
三大核心组件:TiDB Server(计算层、接收SQL、无状态)、PD(调度层、元数据管理、负载均衡)、TiKV(存储层、分布式KV存储、数据持久化)
TiFlash:列式存储引擎,擅长统计分析

1.2、TiDB Server、TiKV、PD、TiFlash 的主要特性
-
TiDB Server功能
- 处理客户端的连接
- SQL语句的解析和编译
- 关系型数据与KV(key-value)的转化
- SQL语句的执行
- 执行online DDL
- 垃圾回收(GC)
-
PD功能(Placement Driver)
- 整个集群TiKV的元数据存储
- 分配全局ID和事务ID
- 生成全局时间戳
- 收集集群信息进行调度
- 提供TiDB Dashboard服务
-
TiKV功能
- 数据持久化
- 副本的强一致性和高可用性
- MVCC(多版本并发控制)
- 分布式事务支持
- Coprocessor(算子下推)分布式计算
-
层级关系rocksdbraft\rocksdbkv--Raft--MVCC--Transation
-
TiFlash 功能
- 异步复制
- 一致性
- 列式存储提高分析查询效率
- 业务隔离
- 智能选择
2、Lesson 02: TiDB Server
2.1、TiDB Server介绍
TiDB 的 SQL 层,即 TiDB Server,负责将 SQL 翻译成 Key-Value 操作,将其转发给共用的分布式 Key-Value 存储层 TiKV,然后组装 TiKV 返回的结果,最终将查询结果返回给客户端。
SQL 层,对外暴露 MySQL 协议的连接 endpoint,负责接受客户端的连接,执行 SQL 解析和优化,最终生成分布式执行计划。TiDB 层本身是无状态的,实践中可以启动多个 TiDB 实例,通过负载均衡组件(如 TiProxy、LVS、HAProxy、ProxySQL 或 F5)对外提供统一的接入地址,客户端的连接可以均匀地分摊在多个 TiDB 实例上以达到负载均衡的效果。TiDB Server 本身并不存储数据,只是解析 SQL,将实际的数据读取请求转发给底层的存储节点 TiKV(或 TiFlash)。
这一层的节点都是无状态的,节点本身并不存储数据,节点之间完全对等。
2.2、TiDB Server 的架构
Protocal Layer :处理客户端的连接
Parse | Complise :解析编译SQL
Executor | DistSQL|KV :执行SQL执行计划
Transaction | KV :事务相关的执行计划
PD Client | TiKV Client : 负责与PD和TiKV之间的交互
schema | worker | start job: Online DDL语句不会阻塞读写
memBuffer : 用于缓存读取出来的数据集、元数据, 登录认证信息、统一信息保存
cache table:开启了缓冲区,特性表数据量小,访问量大,修改量小
GC:TiDB Server会定期将存储在TiKV中的无效数据清理回收
TiDB 的 SQL 层要复杂得多,模块以及层次非常多,下图列出了重要的模块以及调用关系:

2.3、TiDB Server作用、进程
- 处理客户端的连接:Protocal Layer
- SQL语句的解析和编译:Parse 、Compile 生成执行计划
- 关系型数据与KV:(key-value)的转化,TiKV client相互间交互,所有的SQL请求都是通过TiKV Client
- SQL语句的执行:Executor、DistSQL、KV来执行SQL语句,如果有事务的需要Transaction和KV来执行
- 执行online DDL:schema load 、worker、start job执行
- 垃圾回收(GC):TiDB Server会定期将存储在TiKV中的无效数据清理回收。
- 热点小表缓存(V6.0):cache table
2.3.1、SQL语句的解析和编译
由Protocal Layer线程处理客户端连接,发送SQL信息后,由Parse 、Compile 生成执行计划。
Parse:通过词法分析(lex)、语法分析(yacc)、将SQL解析成抽象语法树(AST),传递给Compile模块。
Compile模块负责(合法性验证、逻辑优化、物理优化),生成执行计划,可以到TiKV中存取数据。
memBuffer缓存元数据、统计信息。
- 合法性验证:语句相关的表是否存在等等
- 逻辑优化:依据关系型代数的等价规则做一些逻辑变换,如列裁剪将不需要的列去掉、最大最小消除、投影消除、算子下推、子查询、外连接变内连接等等一系列在sql层面可以做的语句优化
- 物理优化:根据逻辑优化的结果,考虑数据分布、大小决定用哪个算子,即结合统计信息,决定走索引还是全表扫描,走索引的话走哪个索引
2.3.2、关系型数据与KV(官方文档介绍)
表数据与 Key-Value 的映射关系
在关系型数据库中,一个表可能有很多列。要将一行中各列数据映射成一个 (Key, Value) 键值对,需要考虑如何构造 Key。首先,OLTP 场景下有大量针对单行或者多行的增、删、改、查等操作,要求数据库具备快速读取一行数据的能力。因此,对应的 Key 最好有一个唯一 ID(显示或隐式的 ID),以方便快速定位。其次,很多 OLAP 型查询需要进行全表扫描。如果能够将一个表中所有行的 Key 编码到一个区间内,就可以通过范围查询高效完成全表扫描的任务。
- 为了保证同一个表的数据放在一起,方便查找,TiDB 会为每个表分配一个表 ID,用
TableID表示。表 ID 是一个整数,在整个集群内唯一。 - TiDB 会为表中每行数据分配一个行 ID,用
RowID表示。行 ID 也是一个整数,在表内唯一。对于行 ID,TiDB 做了一个小优化,如果某个表有整数型的主键,TiDB 会使用主键的值当做这一行数据的行 ID。
每行数据按照如下规则编码成 (Key, Value) 键值对:
Key: tablePrefix{TableID}_recordPrefixSep{RowID}
Value: [col1, col2, col3, col4]
其中 tablePrefix 和 recordPrefixSep 都是特定的字符串常量,用于在 Key 空间内区分其他数据。
索引数据和 Key-Value 的映射关系
TiDB 同时支持主键和二级索引(包括唯一索引和非唯一索引)。与表数据映射方案类似,TiDB 为表中每个索引分配了一个索引 ID,用 IndexID 表示。
对于主键和唯一索引,需要根据键值快速定位到对应的 RowID,因此,按照如下规则编码成 (Key, Value) 键值对:
Key: tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue
Value: RowID
对于不需要满足唯一性约束的普通二级索引,一个键值可能对应多行,需要根据键值范围查询对应的 RowID。因此,按照如下规则编码成 (Key, Value) 键值对:
Key: tablePrefix{TableID}_indexPrefixSep{IndexID}_indexedColumnsValue_{RowID}
Value: null
2.3.3、SQL读写相关模块
Executor、DistSQL、KV来执行SQL语句,如果有事务的需要Transaction和KV来执行。
- 表连接(join)、复杂的SQL、嵌套查询等等,Executor会将其交给DistSQL转化成单表的操作组合,再发给TiKV。
- 点查会走KV的模块,如简单的单行请求(按照主键索引查询、唯一值索引查询)。
- 存在事务相关操作,会先经过Transaction(负责两阶段提交、锁操作)后再到KV;Transaction会在开始时和提交时通过PD Client从PD中获取TSO时间戳。
- DistSQL、KV都会通过TiKV Client,发送读写请求到TiKV中
2.3.4、在线 DDL相关模块
在线DDL模块包括:schema load 、worker、start job,主要作用是执行的时候不会阻塞业务的读写。
- 集群中存在多个TiDB Server,都可以接收DDL操作请求,但同一时刻只允许一个TiDB Server(Owner)执行DDL操作。
- start job:接收用户发起的DDL操作语句,将其放到TiKV中的job队列中
- worker:负责执行DDL操作,由Owner角色的TiDB Server中的worker去取job队列中的第一个job,然后执行;执行结束后放入到history queue中。
- 同一时间只有1个owner角色,不是固定的,owner存在一个任期,到达任期后会重新发起选举决定新的owner。
- schema load:成为owner的TiDB Server,会将最新的表schema的信息同步到其内部的缓存中,根据这些信息去执行job队列。
- job队列放在tikv是为了持久化。
2.3.5、垃圾回收(GC)机制与相关模块
- TiDB Server的GC会定期将存储在TiKV中的无效数据清理回收。
- 其中一个TiDB Server会作为GC leader来进行清理。
- 基于safe point进行清理:如safe point为10:00am,在这之后的历史版本数据还存在,之前的会被清理。
- GC lift time:默认为10min,即10分钟内的历史版本数据会保留。
2.3.6、TiDB Server 的缓存(memBuffer)
-
TiDB Server缓存组成
- SQL执行结果:数据结果可能分散在多个Tikv节点中,需要在TiDB Server(memBuffer)来汇聚数据,尤其是大表join这样的操作;事务处理的数据也会先放到缓存中。
- 线程缓存
- 元数据,统计信息
-
TiDB Server缓存管理
- tidb_mem_quoto_query:限制SQL占用缓存的大小,控制每条语句默认使用的存储量
- omm-action:当SQL内存使用超过tidb_mem_quoto_query以后,决定行为(中断SQL返回error或者记录日志)
-
热点小表缓存(V6.0)
- 适用于查询频繁、数据量不大、极少修改的场景
- TiDB Server对于每张缓存表的大小限制为 64 MB以下。
- 在租约(
tidb_table_cache_lease)时间内,写操作会被阻塞 - 当租约(
tidb_table_cache_lease)到期时,读性能会下降 - 不支持对缓存表直接做 DDL 操作,需要先关闭
- 对于表加载较慢或者极少修改的表,可以适当延长
tidb_table_cache_lease保持读性能稳定
-
cache table开启了缓冲区,表的数据量不大、只读表或者修改不频繁的表、表的访问很频繁
-
防治TiKV和TiDB Sever的数据不一致问题,增加参数tidb_table_cache_lease=5,缓存租约,到期后才能写TiKV中,然后载入cache table中,如未到期,会出现写等待。租约期内读的都是TiDB Server中的cache table缓存。
-
设置命令:ALTER TABLE XX CACHE;
3、Lesson 03: TiKV
3.1、TiKV架构概述
与传统的整节点备份方式不同,TiKV 参考 Spanner 设计了 multi-raft-group 的副本机制。将数据按照 key 的范围划分成大致相等的切片(下文统称为 Region),每一个切片会有多个副本(通常是 3 个),其中一个副本是 Leader,提供读写服务。TiKV 通过 PD 对这些 Region 以及副本进行调度,以保证数据和读写负载都均匀地分散在各个 TiKV 上,这样的设计保证了整个集群资源的充分利用并且可以随着机器数量的增加水平扩展。
3.1.1、Region 与 RocksDB
虽然 TiKV 将数据按照范围切割成了多个 Region,但是同一个节点的所有 Region 数据仍然是不加区分地存储于同一个 RocksDB 实例上,而用于 Raft 协议复制所需要的日志则存储于另一个 RocksDB 实例。这样设计的原因是因为随机 I/O 的性能远低于顺序 I/O,所以 TiKV 使用同一个 RocksDB 实例来存储这些数据,以便不同 Region 的写入可以合并在一次 I/O 中。
3.1.2、Region 与 Raft 协议
- Region 与副本之间通过 Raft 协议来维持数据一致性,任何写请求都只能在 Leader 上写入,并且需要写入多数副本后(默认配置为 3 副本,即所有请求必须至少写入两个副本成功)才会返回客户端写入成功。
- TiKV 会尽量保持每个 Region 中保存的数据在一个合适的大小,目前默认是 96 MB,这样更有利于 PD 进行调度决策。当某个 Region 的大小超过一定限制(默认是 144 MiB)后,TiKV 会将它分裂为两个或者更多个 Region。同样,当某个 Region 因为大量的删除请求而变得太小时(默认是 20 MiB),TiKV 会将比较小的两个相邻 Region 合并为一个。
- 当 PD 需要把某个 Region 的一个副本从一个 TiKV 节点调度到另一个上面时,PD 会先为这个 Raft Group 在目标节点上增加一个 Learner 副本(虽然会复制 Leader 的数据,但是不会计入写请求的多数副本中)。当这个 Learner 副本的进度大致追上 Leader 副本时,Leader 会将它变更为 Follower,之后再移除操作节点的 Follower 副本,这样就完成了 Region 副本的一次调度。
- Leader 副本的调度原理也类似,不过需要在目标节点的 Learner 副本变为 Follower 副本后,再执行一次 Leader Transfer,让该 Follower 主动发起一次选举成为新 Leader,之后新 Leader 负责删除旧 Leader 这个副本。
3.2、TiKV 架构和作用
数据持久化(系统参数、元数据信息、表统计信息、用户数据)、分布式一致性、MVCC、分布式事务、Coprocessor
与传统的整节点备份方式不同,TiKV 参考 Spanner 设计了 multi-raft-group 的副本机制。将数据按照 key 的范围划分成大致相等的切片(统称为 Region),每一个切片会有多个副本(通常是 3 个),其中一个副本是 Leader,提供读写服务。TiKV 通过 PD 对这些 Region 以及副本进行调度,以保证数据和读写负载都均匀地分散在各个 TiKV 上,这样的设计保证了整个集群资源的充分利用并且可以随着机器数量的增加水平扩展。

3.3、TiKV 的数据持久化和读取
3.3.1、RocksDB 简介
-
RocksDB 是由 Facebook 基于 LevelDB 开发的一款提供键值存储与读写功能的 LSM-tree 架构引擎。用户写入的键值对会先写入磁盘上的 WAL (Write Ahead Log),然后再写入内存中的跳表(SkipList,这部分结构又被称作 MemTable)。LSM-tree 引擎由于将用户的随机修改(插入)转化为了对 WAL 文件的顺序写,因此具有比 B 树类存储引擎更高的写吞吐。
-
RocksDB 针对 Flash 存储进行优化,延迟极小,使用 LSM 存储引擎
- 高性能的 Key-Value 数据库
- 完善的持久化机制,同时保证性能和安全性
- 良好的支持范围查询
- 为需要存储 TB 级别数据到本地 FLASH 或者 RAM 的应用服务器设计
- 针对存储在高速设备的中小键值进行优化 — 可以存储在 FLASH 或者直接存储在内存
- 性能随 CPU 数量线性提升,对多核系统友好
-
任何持久化的存储引擎,数据终归要保存在磁盘上,TiKV 也不例外。但是 TiKV 没有选择直接向磁盘上写数据,而是把数据保存在 RocksDB 中,具体的数据落地由 RocksDB 负责。这个选择的原因是开发一个单机存储引擎工作量很大,特别是要做一个高性能的单机引擎,需要做各种细致的优化,而 RocksDB 是由 Facebook 开源的一个非常优秀的单机 KV 存储引擎,可以满足 TiKV 对单机引擎的各种要求。这里可以简单的认为 RocksDB 是一个单机的持久化 Key-Value Map。
3.1.2、RocksDB:写入
1) WAL 预写日志写入
-
业务写入数据优先存入磁盘WAL 预写日志,以此保证数据操作原子性。
-
写入流程:数据先存入操作系统缓存,再批量刷入 WAL 日志文件,该方式存在数据丢失风险。
-
sync-log参数配置
- 设为
true:数据直接写入 WAL 日志文件,数据安全性高,写入性能下降约 30%,官方建议开启。 - 设为
false:走系统缓存批量写入,性能更高,存在丢数隐患。
- 设为
2) 内存写入与落盘机制
-
MemTable
- 存储磁盘落盘前的临时数据,依靠跳跃表、搜索树维持数据有序性。
- 数据量达到
write buffer size阈值后,转为不可变内存表,同时新建空白 MemTable 承接新写入数据。
-
immutable MemTable(不可变内存表)
-
数据转为只读状态,无法再修改。
-
作用:规避磁盘写入 IO 等待,内存写入与磁盘落盘相互分离,避免写入阻塞。
-
数量规则
- 存在单个不可变内存表时,自动执行磁盘落盘。
- 高并发写入场景下可生成多个,默认满5 个触发 TiDB 写入流控,可通过
write stall调整阈值。
-
3) SST 文件分层存储机制
-
SST 文件生成 内存数据达到指定阈值后,批量刷盘生成SST (Sorted String Table) 有序字符串表文件,数据按层级划分存储,默认最大层级为 6 层。
-
层级容量规则
- 下层数据存储容量为上层的 10 倍,整体 90% 数据最终存储在最底层
- 最大层级不固定,可通过参数 max_levels 自定义调整
-
各层级特性
-
Level 0 为不可变内存表(immutable MemTable)的数据复刻,默认数量达到 4 个时,触发Compaction 压缩合并,向下一层整理排序存储。
-
Level 1 及以下层级
- 数据拆分生成多个独立 SST 文件,以 KV 形式有序存放
- 单层级数据达到容量上限后,自动执行压缩合并,流转至下一层级
-
-
数据查询方式 所有层级 SST 文件数据均有序排列,查询时采用二分查找快速定位数据。
-
数据读写逻辑
- 删除操作 无需直接删除磁盘数据,仅在 MemTable 中写入删除标记;读取时优先识别删除标识,直接终止下层查询。
- 新增 / 修改操作 直接向 MemTable 写入对应 KV 数据即可,依靠二分查找有序存入对应层级,写入流程简洁高效。
3.1.3、RocksDB:查询
1) 数据读取缓存与查询规则
- Block Cache 块缓存 优先将近期访问、高频读取的热数据存入块缓存,提升查询速度。
-
整体读取顺序
- 优先查询 Block Cache
- 未命中则查询 MemTable
- 依次向下逐层查找各级 SST 文件
- 找到目标数据立即终止查询,不再向下遍历
- 数据遵循新数据在上、旧数据在下存储规律
- 单 SST 文件读取逻辑
每个 SST 文件记录数据最小 Key 与最大 Key 范围,通过二分查找快速定位目标数据,缩小检索范围。
- 布隆过滤器 bloom filter
- 判定 key 存在:有可能存在,继续读取文件查找
- 判定 key 不存在:一定不存在,直接跳过该 SST 文件
- 作用:大幅减少无效 SST 文件读取,优化查询性能
3.1.4 RocksDB:Column Families(列簇)
- RocksDB 允许用户创建多个 ColumnFamily(列簇 CF),这些 ColumnFamily 各自拥有独立的内存跳表以及 SST 文件,但是共享同一个 WAL 文件,这样的好处是可以根据应用特点为不同的 ColumnFamily 选择不同的配置,但是又没有增加对 WAL 的写次数。
- 可以分别存取不同表的数据,实现数据分片。
- 示例:Write(cf1,id,name,age,xxx),cf1为指定的列簇,id为key,后面为值。
- 未指定列簇时,有一个默认的列簇default。
3.4、TiKV如何提供MVCC和分布式事务支持
3.4.1、分布式事务概览
- TiDB 支持分布式事务,提供乐观事务与悲观事务两种事务模式。TiDB 3.0.8 及以后版本,TiDB 默认采用悲观事务模式。
- TiKV 支持分布式事务,用户(或者 TiDB)可以一次性写入多个 key-value 而不必关心这些 key-value 是否处于同一个数据切片 (Region) 上,TiKV 通过两阶段提交保证了这些读写请求的 ACID 约束,详见 TiDB 乐观事务模型。
3.4.2、分布式事务流程(同一个事务处理数据在相同的TiKV上)
- 两阶段提交:一个事务开启后获取时间戳(begin timestamp=100),第一阶段prewrite(修改数据+锁印象),第二阶段,获取事务结束时间戳(timestamp=110)commit
- beigin:start timestamp=100。从PD中获取开始的TSO
- 第一阶段prewrite:修改数据+锁信息。 锁信息如果只写在TiKV节点的内存中,其他节点感知不到,只有最后commit时才知道,这叫做乐观事务锁(TiDB早期版本是只实现的乐观锁); 锁信息提前写入到TiKV节点中,其他节点能感知到,这叫悲观事务锁。 修改数据+锁信息:通过3个列簇存储,Default、Lock、Write; 其中Default中的key包含数据key_TSO; Lock存储锁信息,注意一个事务中操作如果存在多行操作,只会给第一行加一个主锁,其他行均依附于该主锁。下图示例中3代表数据keyID,W代表为写锁,100为事务的开始时间戳
- 第二阶段commit:commit timestamp=110。 从PD中获取事务结束的TSO,在write列簇中写入,如下图所示包含keyid+提交的时间戳,以及对应事务开始的时间戳。 清理锁信息,在Lock中删除对应的锁。注意删除并非直接删除数据,而是新增一个D的锁数据,代表其为删除。
- 其他人想要读取key为3的值时,先到Write列簇中最近一次修改是什么时间,得知最后一次如下图所示为3_100时间戳,就能在Default中找到3_100的值为Frank;如果在事务提交前读取,在Write中没找到信息,Lock中存在3_100的锁信息,此时不能读取3_100的值。
3.4.3、Writer和Default存储长度
- Write列:当用户写入了一行数据时,如果该行数据长度小于255字节,那么会被存储在write列中,否则的话该行数据会被存入到default列中。
- Default列:用于存储超过255字节长度的数据。
3.4.4、分布式事务流程(同一个事务处理数据在不同TiKV上)
- 基本流程与同一个TiKV上事务流程一致,下图示例中key1和key2分别存在2个节点中。
- TiKV Node1数据key1的锁为主锁,标识为pk;TiKV Node2数据非主锁,依附于主锁,标识为@1。
- commit时流程一致,分3步先获取提交的TSO,然后在write中写入,再删除锁。
- 由于分布式是多节点,加入commit时,Node1节点提交成功,此时Node2宕机导致提交失败,这时读取Node1的key1数据能正常读取结果;Node2恢复后读取2时未在write中找到2_110的提交信息,但通过查锁发现存在W的@1的锁信息,再查node1的主锁信息,发现已被删除,这里可以知道之前是因为宕机等原因没提交成功,这时恢复完善write和删除@1锁信息,就可以正常读取。(解决了分布式事务的原子性问题)
3.4.5、MVCC
- TiKV 支持多版本并发控制 (Multi-Version Concurrency Control, MVCC)。假设有这样一种场景:某客户端 A 在写一个 Key,另一个客户端 B 同时在对这个 Key 进行读操作。如果没有数据的多版本控制机制,那么这里的读写操作必然互斥。在分布式场景下,这种情况可能会导致性能问题和死锁问题。有了 MVCC,只要客户端 B 执行的读操作的逻辑时间早于客户端 A,那么客户端 B 就可以在客户端 A 写入的同时正确地读原有的值。即使该 Key 被多个写操作修改过多次,客户端 B 也可以按照其逻辑时间读到旧的值。
- TiKV 的 MVCC 是通过在 Key 后面添加版本号来实现的。
- 下图有2个事务100和115:
-
MVCC(多版本并发控制)可以寄解决事务处理过程中的读阻塞问题。
-
假设读取的事务TS0=120时,读取id为1、4的数据,在Writer中没有找到事务1_115和4_115的数据,此时只能获取到1_110和4_90的数据(开始事务的数据1_100和4_80)在Default中找到对应的值,1_100的值为Jack,4_80的值为Tony;如果没有MVCC,id=1、id=4都不能读取。如果此时对id=1、4的数据进行修改,Lock中有锁是无法修改的。
3.5、TikV基于raft 算法的分布式一致性
3.5.1、基本概念
- Raft:TiKV 利用 Raft 来做数据复制,每个数据变更都会落地为一条 Raft 日志,通过 Raft 的日志复制功能,将数据安全可靠地同步到复制组的每一个节点中。不过在实际写入中,根据 Raft 的协议,只需要同步复制到多数节点,即可安全地认为数据写入成功。数据的写入是通过 Raft 这一层的接口写入,而不是直接写 RocksDB。通过实现 Raft,TiKV 变成了一个分布式的 Key-Value 存储,少数几台机器宕机也能通过原生的 Raft 协议自动把副本补全,可以做到对业务无感知

-
Region:数据存储的逻辑单元,TiKV中默认为3副本,不同TiKV节点中同一数据的Region组成一个raft group。
- Region是按照key的顺序存储的,一个Region的默认大小为96MB(≤ v8.3),超过144MB时将分裂成2个Region
- Region是左闭右开的区间,例如Region1为[1,1000),Region2为[1000,1999)
-

-
leader:管理者,所有读写请求都只能在 Leader上完成,其他follower不负责,会周期性向follower 发出心跳信息,并将写的数据以日志的方式传递给follower,同一个raft group中只有1个leader,
-
follower:被管理者,只会对其他的服务作出响应,接收leader的日志。如果长时间没收到leader的反馈,将会变成candidate(候选者),投票以选举出新的leader。(问题:如果leader网络故障,follower1和follower是同的,如果处理?)
3.5.2、Raft日志复制
- propose(提交写入请求):客户端发起写入请求,把数据操作信息发送到对应Region的Leader主节点,正式提交写入提议,开启后续Raft同步流程。
-
append(追加写入)
- Leader收到写入请求后,先把数据整理成固定格式存入自身Raft日志库
- 格式:
regionId_日志序号, 日志内容{操作类型+数据}示例:r1_10, log{PUT key=xx,name=xx} - 专门存这类日志的数据库叫做RaftDB,只用来存放Raft同步日志,不存业务真实数据。
-
replicate(转发同步):领导者(Leader)把日志数据转发、下发传递给所有跟随者(Follower)节点,完成日志分发传输动作。
-
append(追加保存):跟随者(Follower)收到日志后,各自独立执行写入操作,把日志追加保存到自身节点本地日志里。
-
committed(日志正式提交生效)
- 仅针对Raft同步日志,和SQL事务提交不是一回事
- Leader把日志同步发给其他节点
- 只要集群过半节点回复同步完成
- 这条日志就判定为 committed 提交成功
- 提交成功后日志才算正式生效,后续就能正常执行落地
- apply:将raft log写入到rocksdb kv中,此时数据真正存到KV中,可以SQL读取。
3.5.3、Raft Leader选举
- term:把集群运行时间分成一段段独立任期,用递增数字编号,没有固定时长。代表一轮完整的领导管理周期,任期数字越大,代表阶段越新。
- election timeout:节点长时间收不到领导者消息,达到这个时长就认定没有Leader,自动发起竞选。
- heartbeat time interval:Leader定时发送心跳包给所有跟随者,跟随者收到就重置计时,确认领导在线
1、Raft选举完整流程(集群刚启动):
- 集群刚启动,所有节点默认都是跟随者(Follower),静静等待Leader心跳。
- 一直收不到心跳(election timeout超时),触发选举超时,最先超时的节点变成候选人(Candidate),任期号自动加一,开始拉票。
- 候选人向其他节点发起投票请求,别的节点发现对方任期更新,就同意投票。
- 拿到半数及以上票数,候选人直接转正成为领导者(Leader)。
- 当选Leader后,持续定时发送心跳稳住地位,其余节点保持跟随状态。
2、集群运行中Leader突然宕机下线:**
- 跟随者长时间收不到Leader发送的心跳(heartbeat time interval=xx),超时达到设定间隔时长
- 自动判定主节点失效,身份从Follower转为Candidate候选人
- 任期Term数值递增,进入新一轮任期
- 立刻向其他节点发起投票,启动新一轮选举选出新Leader
3、Raft异常选举场景**
-
原Leader节点恢复上线
- 宕机的节点恢复重启后,默认变为候选人准备参选
- 比对发现集群内其他节点任期编号比自己更高
- 主动放弃竞选,自动降级变回跟随者
-
多节点同时超时选举冲突
- 集群初始化阶段,多个节点一同达到选举超时时间
- 一起发起竞选,票数分散导致选举失败
- 系统会不断重试选举,耗费资源
-
TiDB解决方案
- 给选举超时时间设置随机区间范围(random),例如100ms~300ms
- 每个节点超时时间随机不等,错开竞选时机
- 大幅避免扎堆投票冲突,快速选出新主节点
4、Raft 时间单位与配置规则**
-
tick 基础时间单位,1个tick对应1个间隔,默认1秒。
-
核心配置关系
raft-election-timeout-ticks:选举超时节拍数raft-heartbeat-ticks:心跳发送节拍数- 硬性规则:选举超时节拍数(raft-election-timeout-ticks) 必须大于等于 心跳节拍数(raft-heartbeat-ticks)
-
TiKV 默认规范 选举超时时长,默认设置为心跳间隔的2倍及以上 保证正常心跳能持续送达,不会误判主节点下线
3.6、Coprocessor(计算加速,协处理器)
- TiKV 通过协处理器 (Coprocessor) 可以为 TiDB 分担一部分计算:TiDB 会将可以由存储层分担的计算下推。能否下推取决于 TiKV 是否可以支持相关下推。计算单元仍然是以 Region 为单位,即 TiKV 的一个 Coprocessor 计算请求中不会计算超过一个 Region 的数据。
- 这里的写入暂时忽略MVCC、Transaction的过程,只关注Raft的写入。
3.6.1 数据的写入
- PD 集群,两大核心作用
- 统一分配全局时间戳,保证事务时序有序
- 负责数据路由,查询写入数据该存到哪个TiKV、哪个Region分片
- raftstore pool 线程池
- 处理Raft层所有流程
- 接收读写请求,转换成标准Raft日志
- 完成日志本地持久化,同步转发给其他副本节点
- 同时接收副本节点同步成功的回执响应
- apply pool 线程池
- 专门负责日志落地
- 把已经集群确认提交成功的Raft日志
- 正式解析写入底层KV存储引擎
- 读写规则
- 客户端查询读取到的数据,必须是已经写入KV引擎的数据
- 仅存放在Raft日志里未落地KV的数据,无法被查询读取
3.6.2 数据读取流程
- 发起读请求先访问PD,获取全局时间戳,同时查询目标key所属Region对应的Leader节点地址
- 定位到Leader节点后发起读取
- 读取期间会发送心跳交互,校验当前节点身份,确认依旧是有效Leader,避免读到旧主节点旧数据,保障读取正确性
3.6.2.1、ReadIndex Read
线性一致性读,同一数据修改完成后,后续所有读取操作,一定能查到最新修改结果 注意:此逻辑只针对Raft协议层,不涉及MVCC多版本控制
执行流程
- 10:00 修改数据(1 tom->jack),生成Raft log1-95
- 10:05 发起读请求,获取当前集群最新已提交日志进度1-97,该数值即为ReadIndex
- 此时1-95日志还未落地写入KV存储
- 10:08 日志1-95完成apply,正式存入KV
- 读请求不会立即返回结果,持续等待
- 直到集群进度推进到ReadIndex对应的1-97全部apply落地
- 确认1-95早于1-97,必然已经完成落地,此时再执行读取
- 最终成功读到最新修改数据(1,jack),严格保证读写顺序与数据一致性
3.6.2.2、Lease Read
- 作用:优化读取性能,快速确认当前节点仍是合法Leader
- 原理:Leader发送心跳后会持有一段租赁有效期
- 有效期内无需反复跟其他节点通信校验身份,直接判定自己还是主节点
- 省去多余心跳确认流程,大幅加快读取速度
- 租赁到期后再重新同步心跳续约,保证身份不出错
Lease Read 租赁读原理
- 10点Leader成功发送心跳后,在心跳间隔(heartbeat time interval)内,确定自己稳稳是主节点
- 从发心跳开始,一直到选举超时(election timeout)这段时间里 就算其他节点暂时没收到心跳,也没到发起选举的时间,没法换新主
- 这段安全窗口期就是租赁有效期
- 有效期内直接本地读数据,不用再和其他节点校验身份,提升读取速度
3.6.2.3、Follower Read
- 含义:直接去 从节点(Follower) 读取数据,分流主节点 Leader 的读请求,减轻主节点压力。
- 核心难点:从节点数据同步比主节点慢,容易读到旧数据,最难解决的就是保证线性一致性。
- 简单理解:读压力大就分给从节点读,但必须做好时序校验,确保读到的是最新数据,不出现读写不一致。
Follower Read 时序流程梳理
-
10:00 Leader 写入数据,生成日志 1-95;此时 Follower 和 Leader 日志落地进度不一致,属于正常现象。
-
10:05 用户直接在 Follower 节点发起读请求,记下当前集群最新提交日志编号 1-97,也就是CommitIndex。
-
规则:只要编号更大的 1-97 完成落地应用,更早的 1-95 必然早已完成落地。
-
10:08 Leader 端把 1-95 数据正式写入 KV 存储。
-
10:10 Follower 节点才完成 1-97 日志的 apply 落地,此时再执行读取操作。
-
依靠 CommitIndex 做判断,确定 1-95 早已同步完成,顺利读出最新正确数据,守住线性一致性。
Follower Read 特殊情况
-
正常是 Leader 先落地数据,也会出现从节点同步更快的情况
-
10:00 Leader 生成日志 1-95 写入 Raft 日志,此时主从节点数据落地进度不同步
-
10:06 Follower 节点性能好、同步快,提前完成 1-97 日志 apply 落地
-
10:08 Leader 才把 1-95 数据正式存入 KV 存储
-
读逻辑不变 依旧以全局最新 CommitIndex(1-97) 为标准
-
只要该编号日志在当前节点完成应用,就能确定更早的 1-95 日志早已生效
-
哪怕从节点落地比主节点还快,依旧能保证读到最新正确数据,不破坏线性一致性
3.6.3 Coprocessor 协处理器
业务表数据分散存储在多个 TiKV 节点中,若把全量原始数据统一汇总至 TiDB 再做统计计算,会产生巨大网络传输开销,同时大幅增加 TiDB 的 CPU 运算压力。
-
核心原理 利用 TiKV 节点本地完成数据过滤、聚合计算,仅将最终计算结果回传给 TiDB,该机制即为Coprocessor 协处理器,主要负责实现算子下推。
-
主要作用
- 本地执行各类物理执行算子,就近完成数据运算,有效降低网络开销与 TiDB 计算压力
- 支持常用扫描操作:全表扫描table scan、索引扫描index scan等
- 完成数据统计信息采集、数据采样等辅助工作
Coprocessor 协处理器