TIDB的体系结构学习
1.Tidb总体体系架构
2. Tidb Server架构介绍
2.1 TIDB server
TIDBserver的模块功能简介:
- 处理客户端的连接,是由 Protocol Layer 负责的。
- SQL 语句的解析和编译,是由 Parse + Compile 负责,解析和编译完成后交给 Executor 执行。
- Executor、DistSQL 和 KV 负责 SQL 语句的执行
- 对于事务是由 Transaction 和 KV 协同执行
- 在线 DDL 的执行是由 schema load、worker 和 start job 负责的
- PD Client 负责与 PD 节点进行信息交互,TiKV client 负责与 TiKV 节点进行信息交互
- 垃圾回收,MVCC 过期版本的数据是由 GC 模块负
各模块基本功能:
1.Protocaol+Parse:处理客户端连接+SQL解析和释义
2.Execuote+DistSQL:分批SQL执⾏计划执⾏下发kv
3.Transaction +KV:事务和kv转化
4.PD+TikV:进⾏⽐如时间戳交互等
5.DLL:load+worder+start job
6.memBuffer:缓存元数据,登⼊信息等
2.2 SQL执行流程说明
2.2.1 SQL解析和编译
1.TiDB的Protocol Layer 协议层监听客户端发出的SQL后开始对SQL进行处理
2.由PD Client 从PD中异步获取TSO
3.Parse模块进行词法分析和语法分析,生成AST语法树,传给Compile模块
4.Compile模块的Preprocess预处理阶段检测SQL的合法性和名称等; 并且做判断,如果SQL是点查(读取一条数据), ⾛pointget去tikv直接拿数据
如果不是点查,进入Optimize优化流程;
逻辑优化:外连接转化内连接等 sql语句优化
物理优化:结果统计信息 算⼦资源 选取效率最⾼的流程
出来的结果即物理执行计划,然后下一步到TiKV中取数据;
2.2.2 TIDB读数据流程
执行完以上的解析、编译SQL后
1.收到执⾏计划后,到Executor模块
2.获取元数据,表结构等从tidb server 缓存中读取,从pd获取region分布并缓存到tikv client中的region cache
- 读取相同region⾛缓存
- region变化(merge等)-->backoff给tidb-->重新去pd读取并更新缓存
3.点查⾛kv模块(唯⼀索引或者主键等)-->tikv client读取数据返回
4.复杂查询 distsql 转化复杂sql为单表的查询(join转化两个表的简单sql)-->tikv client-->并⾏去tikv读取 数据
5.tikv收到请求--->构建SI(特定时间点数据)-->进⼊UnifyRead Pool线程池(按照优先级读取)-->rocksdb kv读 取数据返回
6.数据在tikv中经过算⼦计算后(Cop task),过滤后给tidb(如果数据没办法在tikv计算则返回给tidb做root task)
2.2.2 TIDB读数据流程
1.读取修改数据缓存到memBuffer
2.Transaction先获取TSO-->在memBuffer中修改好数据(DML语句)
3.修改后kv-->tikvclient中发起写请求到tikv
4.Scheduler收到写请求协调事务并发写⼊(如果同时写⼀条kvlatch管理,拿到先写)
5.给raftstore按到raft log给rocksdb raft持久化
6.apply模块读取log并应⽤到rocksdb kv中返回成功信息
2.2.3 TIDB读数据流程
1.接受ddl语句
2.tidb server看⾃⼰是否是owner-->不是调⽤start job-->将job放⼊ job queue
3.owner的tidb server定期看job queue执⾏ddl-->调⽤workers模块,workers模块调⽤schema load载⼊最新 信息
4.执⾏ddl语句,完毕⼈放⼊history queque
5.owner的选择通过pd选举
6.添加索引有专有的add index queue 队列,单独执⾏
2.2.4 GC机制与相关模块
1.MVCC有历史版本数据(对同⼀⾏有多个历史版本数据),MVCC的版本数据会存在Level 0
2.GC负责定期处理过多的历史版本数据,清理MVCC的过期数据
3.TiDB Server中有⼀个GC leader负责GC处理,GC 的运行由 GC leader 来控制。
GC 会被定期触发。每次 GC 时,首先,TiDB 会计算一个称为 safe point 的时间戳,接下来 TiDB 会在保证 safe point 之后的快照全部拥有正确数据的前提下,删除更早的过期数据。每一轮 GC 分为以下三个步骤:
- "Resolve Locks" 阶段会对所有 Region 扫描 safe point 之前的锁,并清理这些锁。
- "Delete Ranges" 阶段快速地删除由于
DROP TABLE
/DROP INDEX
等操作产生的整区间的废弃数据。 - "Do GC" 阶段每个 TiKV 节点将会各自扫描该节点上的数据,并对每一个 key 删除其不再需要的旧版本。
默认配置下,GC 每 10 分钟触发一次,每次 GC 会保留最近 10 分钟内的数据(即默认 GC life time 为 10 分钟,safe point 的计算方式为当前时间减去 GC life time)。
相关参数:
1)tidb_gc_enable:
- 这个变量用于控制是否启用 TiKV 的垃圾回收 (GC) 机制。如果不启用 GC 机制,系统将不再清理旧版本的数据,因此会有损系统性能。
- 默认值:ON开启
2)tidb_gc_life_time
默认值:10分钟
- 这个变量用于指定每次进行垃圾回收 (GC) 时保留数据的时限。每次进行 GC 时,将以当前时间减去该变量的值作为 safe point。
3)tidb_gc_run_interval
默认值:10分钟
- 这个变量用于指定垃圾回收 (GC) 运行的时间间隔。变量值为 Go 的 Duration 字符串格式,如
"1h30m"
、"15m"
等。
#流程
1.tidb server计算⼀个 safe point(⽐如4⼩时)
2.时间戳内数据都保留,其余GC回收,称为 gc_life_time
2.2.4.1 GC in Compaction Filter 机制
TiDB 中会保留多版本数据,即在新插入数据覆盖老数据时,老数据不会马上被清理掉,而是和新数据一起保留,同时以时间戳来区分多版本。
这些多版本数据什么时候被删除清理掉呢? 此时有一个 GC 线程,GC 线程会通过后台去遍历,遍历超过 GC 时间的一些 key 。
然后将这些 key 标记为 tombstone key。这些 key 其实只是做一个标记,即所谓的 tombstone key。
这些 key 的清理是由 RocksDB 引擎后台发起一个线程以 Compaction 的方式来进行回收。RocksDB 整个 Compaction 过程
GC in Compaction Filter 机制是在分布式 GC 模式 (DISTRIBUTED
GC mode) 的基础上,由 RocksDB 的 Compaction 过程来进行 GC,而不再使用一个单独的 GC worker 线程。这样做的好处是避免了 GC 引起的额外磁盘读取,以及避免清理掉的旧版本残留大量删除标记影响顺序扫描性能。可以由 TiKV 配置文件中的以下开关控制:
该 GC 机制可通过在线配置变更开启:
參考官方资料:
https://docs.pingcap.com/zh/tidb/stable/rocksdb-overview#rocksdb-%E5%90%8E%E5%8F%B0%E7%BA%BF%E7%A8%8B%E4%B8%8E-compact
2.2.5 TiDB Server缓存
1.缓存内容:
SQL结果,线程缓存,元数据,统计信息
2.缓存管理相关参数:
tidb_mem_quota_query
- 单条 SQL 语句可以占用的最大内存阈值,单位为字节。
- 默认值:1G
- 超过该值的请求会被
oom-action
定义的行为所处理
oom-action
- 当 TiDB 中单条 SQL 的内存使用超出
mem-quota-query
限制且不能再利用临时磁盘时的行为。 - 目前合法的选项为 ["log", "cancel"]。设置为 "log" 时,仅输出日志。设置为 "cancel" 时,取消执行该 SQL 操作,并输出日志。
- 默认值:cancel
3 TIKV持久化
一个TIKV节点包含两个rockdb,一个region data 一个raft log data
纵向:rocksdb针对单个tikv节点 横向:raft共识多个tikv node副本存取
Coprocessor:协同处理器,处理算⼦
3.1 TIKV的读取和写入
Tikv的底层使用的是rackdb,使用LSM的存储引擎,是高性能的Key Value数据库
.RocksDB写操作
- LSM tree数据结构
- 内存数据写⼊⼀定数据flush到磁盘
3.1.1 TIKV的写入流程
⼀条数据写⼊流程
- WAL:⽇志先写,flush到磁盘⽂件中
- 写⼊内存:写⼊MemTable (write buffer size)
- 如果宕机,内存清空,则可以从WAL中进⾏CR(如果sync-log=true则每条数据都持久化,不经过操作系统缓存直接⾛磁盘)
- 数据追加到write buffer size,则memtable转存到内存的 immutable memtable,rockdb重新开辟⼀个memtable继续缓存数据
磁盘⽂件分成多层组织 Level 0 默认达到4个之后,compaction(压缩4个⽂件合层⼀个SST⽂件,key压缩+排序)到下⼀层,即level 1 每⼀层都会切换成SST Table⽂件(键值对按照key排序),利⽤⼆分查找发找到某个key Level 1 4个246MBcompaction成⼀个Level 2⽂件 ..依次压缩
***注意其中***:
immutable指的是数据插入 RocksDB 之后,会写入 WAL,然后正式的写入 memory table 的内存块。当内存块写满之后,我们会把它标记为 immutable member table
write-buffer-size指的是memtable 大小(每个cf都有)。defaultcf
默认值:"128MB",
writecf
默认值:"128MB",
lockcf
默认值:"32MB"
max-write-buffer-number,
最大 memtable 个数。当 storage.flow-control.enable
的值为 true
时,storage.flow-control.memtables-threshold
会覆盖此配置,默认值:5 。
max-total-wal-size: 指定wal的大小,默认值4G
wal-ttl-seconds:
归档 WAL 生存周期,超过该值时,系统会删除相关 WAL。默认值:0
wal-size-limit:
归档 WAL 大小限制,超过该值时,系统会删除相关 WAL。默认值:0
3.1.1.1 TIKV集群的数据写入
0.PD获取开始事物TSO
1.TiDB Server接受写⼊数据,载⼊缓存
2.client commit后从进⾏两阶段提交
=======以上执行在TIDB server层======
3.PD获取结束事物TSO
4.TiKV写⼊tikv的raftstore pool线程池,写请求转化成raft kv存储到rocksdb raft持久化raft log,然后传 输给其他node的 raftstore pool-->rocksdb raft
5.⼤多数节点返回传输成功,则认为修改成功,底层raft⽇志commit
6.raftstore pool独处raft log应⽤到apply pool,最终数据写⼊到rocksdb kv
7.事务commited正式提交,
#写⼊key=1 value=Tom
1.propose:转化为raft log
2.append:存储到raft log的kv
3.replicate:复制到其他node - append:其他node持久化raft log影响成功
4.committed:⼤多数副本返回成功后,raft log进⾏commited,可以准备写⼊正式数据
5.apply:数据写⼊kv,⽤户的commttied),成功后其他client才能读到数据
3.1.2 RocksDB查询
为了提高读取性能以及减少对磁盘的读取,RocksDB 将存储在磁盘上的文件都按照一定大小切分成 block(默认是 64KB),
读取 block 时先去内存中的 BlockCache 中查看该块数据是否存在,存在的话则可以直接从内存中读取而不必访问磁盘。
BlockCache 按照 LRU 算法淘汰低频访问的数据,TiKV 默认将系统总内存大小的 45% 用于 BlockCache,用户也可以自行修改 storage.block-cache.capacity
配置设置为合适的值,但是不建议超过系统总内存的 60%
#查询流程
1.Block Cache缓存最近最⻓的热数据,有则直接读取
2.没有读取MemTabel--->immutable
3.在找寻Level 0 -->1-->2 依次读取(读取第⼀个最新值,旧key不读取即可)SST⽂件,利⽤两个⽅式找到key
- 每个sst都有min max key排序⼆分查询法
- 每个⽂件都有bloom过滤器,快速判断集合中元素(存在误判,不存在则⼀定不存在)
3.1.3 RocksDB-Column Families
RocksDB 允许用户创建多个 ColumnFamily ,这些 ColumnFamily 各自拥有独立的内存跳表以及 SST 文件,
但是共享同一个 WAL 文件,这样的好处是可以根据应用特点为不同的 ColumnFamily 选择不同的配置,但是又没有增加对 WAL 的写次数。
每个 TiKV 实例中有两个 RocksDB 实例,一个用于存储 Raft 日志(通常被称为 raftdb),
另一个用于存储用户数据以及 MVCC 信息(通常被称为 kvdb)。kvdb 中有四个 ColumnFamily:raft、lock、default 和 write:
- raft 列:用于存储各个 Region 的元信息。仅占极少量空间,用户可以不必关注。
- lock 列:用于存储悲观事务的悲观锁以及分布式事务的一阶段 Prewrite 锁。当用户的事务提交之后,lock cf 中对应的数据会很快删除掉,因此大部分情况下 lock cf 中的数据也很少(少于 1GB)。如果 lock cf 中的数据大量增加,说明有大量事务等待提交,系统出现了 bug 或者故障。
- write 列:用于存储用户真实的写入数据以及 MVCC 信息(该数据所属事务的开始时间以及提交时间)。当用户写入了一行数据时,如果该行数据长度小于 255 字节,那么会被存储 write 列中,否则的话该行数据会被存入到 default 列中。由于 TiDB 的非 unique 索引存储的 value 为空,unique 索引存储的 value 为主键索引,因此二级索引只会占用 writecf 的空间。
- default 列:用于存储超过 255 字节长度的数据。
3.2 PD架构能功能
简介:
1. 3个etcd-raft保证单点⾼可⽤,其中1个pd为leader
2. 集群元信息存储管理,全局时钟管理,Region调度等
Store:⼀个服务器⽤来运⾏TiKV
Peer:每个副本叫⼀个Peer
multi raft:多个raft group组成
主要功能:
1.整体TiKV元数据信息存储
2.分配全局ID和事务ID
3.⽣产全局时间戳TSO
4.收集集群信息进⾏调度
5.提供label功能⽀撑⾼可⽤
6.提供TiDB Dashboard服务
3.2.1 PD路由功能
1.执⾏计划到TiDB server-->Executor-->TiKV Client-->PD寻找region1
2.PD-->发送请求region1的tikv
- 元信息缓存到region cache,节约⽹络开销(如果cache失效,leader漂移),则进⾏backoff重新询问pd更新信息刚 更新cache
3.1.3 TSO分配
TSO 指 Time Stamp Oracle,是 PD (Placement Driver) 为每个事务提供的单调递增的时间戳。
TSO 是一串数字,包含以下两部分:
- 一个物理时间戳
- 一个逻辑计数器
BEGIN; SELECT TIDB_PARSE_TSO(@@tidb_current_ts); ROLLBACK; +-----------------------------------+ | TIDB_PARSE_TSO(@@tidb_current_ts) | +-----------------------------------+ | 2021-05-26 11:33:37.776000 | +-----------------------------------+
TSO分配流程
TSO= physical time(ms) + logical time(1ms分成262144 TSO区分),int64
1.TSO请求给PD-Client
2.PD-client-->PD leader收到请求,返回异步对象 tsFuture-->TSO请求
3.PD分配TSO帶着 tsfuture-->TSO请求
3.1.4 TSO分配-时间窗⼝
TiDB⼤流量分配TSO问题?
1.PD批量给TiDB Server批量时间戳(未来3s),变成间隔批量请求,减轻压⼒
2.其他TiDB Server请求按照间隔时间段分配。
3.如果PD宕机,会接管已经分配最新TSO继续进⾏进⾏TSO分配
3.1.5 PD的调度原理
调度流程
信息收集--》⽣成调度--》 执⾏调度
信息收集
1.tikv周期汇报两种⼼跳信息
store heartbeat:容量等
region heartbeat:region分配情况
⽣成调度
1.balance
- leader
- region
2.hot region
3.集群拓扑
4.缩容
5.故障恢复
6.Region merge
执⾏调度
发送给region,执⾏operator