TiDB集群体系结构
TiDB集群体系结构 作者:周万春 微信:lovemysql3306 1、CAP分布式 CAP 理论是分布式系统的一个基础理论,它描述了任何一个分布式系统最多只能满足以下三个特性中的两个: 一致性(Consistency) 可用性(Availability) 分区容错性(Partition tolerance 2、TiDB整体架构 TiDB 有以下的一些优势: 纯分布式架构,拥有良好的扩展性,支持弹性的扩缩容 支持 SQL,对外暴露 MySQL 的网络协议,并兼容大多数 MySQL 的语法,在大多数场景下可以直接替换 MySQL 默认支持高可用,在少数副本失效的情况下,数据库本身能够自动进行数据修复和故障转移,对业务透明 支持 ACID 事务,对于一些有强一致需求的场景友好,例如:银行转账 具有丰富的工具链生态,覆盖数据迁移、同步、备份等多种场景 三大模块:每个模块都是分布式的架构。 1、计算层(SQL)------------》TiDB 2、分布式存储(K-V键值对)----》PD 3、元信息系统、分布式调度-----》TiKV + TiFlash TiDB 1、无状态、不存储数据 2、接受客户端连接 3、执行 SQL 解析和优化 4、生成分布式执行计划 5、数据读取请求转发给底层的存储层 TiKV TiKV + TiFlash TiKV 分布式 KV 存储(默认分布式存储引擎)。 支持弹性的扩容和缩容。 默认3个多副本。 支持高可用和自动故障转移。 TiFlash 把数据以列式的形式进行存储(是为了分析型的场景加速)。 PD 整个 TiDB 集群的元信息存储。 存储每个 TiKV 节点实时的数据分布情况和集群的整体拓扑结构。 为分布式事务分配事务 ID。 支持高可用。 TiKV 节点实时上报的数据分布状态。 PD 下发数据调度命令给具体的 TiKV 节点。 3、TiKV说存储 本地存储(RocksDB) TiKV 的 KV 存储模型和 SQL 中的 Table 无关! 数据保存在 RocksDB 中,再由 RocksDB 将数据落盘。 一个TiKV里面有两个 RocksDB,一个用于存储数据,一个用于存储 Raft。 TiKV 利用 Raft 来做数据复制,每个数据变更都会落地为一条 Raft 日志,通过 Raft 的日志复制功能,将数据安全可靠地同步到复制组的每一个节点中。 Raft 是一个一致性协议。 Raft 负责: Leader(主副本)选举 成员变更(如添加副本、删除副本、转移 Leader 等操作) 日志复制(通过 Raft,将数据复制到其他 TiKV 节点) 数据写入: 数据的写入是通过 Raft 这一层的接口写入,而不是直接写 RocksDB。 数据写入 ---》Raft ---》RocksDB ---》磁盘 Region 对于一个 KV 系统,将数据分散在多台机器上有两种比较典型的方案: Hash:按照 Key 做 Hash,根据 Hash 值选择对应的存储节点。 Range:按照 Key 分 Range,某一段连续的 Key 都保存在一个存储节点上。(TiKV选择此方案) 连续的 key-value 划分为一个 Region,默认大小 96 M。 TiKV: Region1: key1-value1 key2-value2 key3-value3 Region2: key4-value4 key5-value5 key6-value6 以 Region 为单位做数据的分散和复制: 以 Region 为单位,将数据分散在集群中所有节点上,并且保证每个节点上 Region 数量尽可能相同。(此均匀分布是 PD 干的活) 以 Region 为单位做 Raft 的数据复制和成员管理。 TiKV 是以 Region 为单位做数据的复制,也就是一个 Region 的数据会保存多个副本,TiKV 将每一个副本叫做一个 Replica。 Replica 之间是通过 Raft 来保持数据的一致,一个 Region 的多个 Replica 会保存在不同的节点上,构成一个 Raft Group。 其中一个 Replica 会作为这个 Group 的 Leader,其他的 Replica 作为 Follower。 所有的读和写都是通过 Leader 进行,读操作在 Leader 上即可完成,而写操作再由 Leader 复制给 Follower。 TiKV 的 MVCC 实现是通过在 Key 后面添加版本号来实现。 Key1_Version3 -> Value Key1_Version2 -> Value Key1_Version1 -> Value ...... Key2_Version4 -> Value Key2_Version3 -> Value Key2_Version2 -> Value Key2_Version1 -> Value ...... KeyN_Version2 -> Value KeyN_Version1 -> Value ...... 通过 RocksDB 的 SeekPrefix(Key_Version) API,定位到第一个大于等于这个 Key_Version 的位置。 分布式事务ACID TiKV 的事务采用的是 Google 在 BigTable 中使用的事务模型:Percolator。 在 TiKV 层的事务 API 的语义类似下面的伪代码: tx = tikv.Begin() tx.Set(Key1, Value1) tx.Set(Key2, Value2) tx.Set(Key3, Value3) tx.Commit() 4、TiDB谈计算 表数据与 Key-Value 的映射关系 TiDB 会为每个表分配一个表 ID,用 TableID 表示。表 ID 是一个整数,在整个集群内唯一。 TiDB 会为表中每行数据分配一个行 ID,用 RowID 表示。行 ID 也是一个整数,在表内唯一。 如果某个表有整数型的主键,TiDB 会使用主键的值当做这一行数据的行 ID。 每行数据构成的 (Key, Value) 键值对: Key: tablePrefix{TableID}_recordPrefixSep{RowID} Value: [col1, col2, col3, col4] 索引数据和 Key-Value 的映射关系 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 一个表内所有的行都有相同的 Key 前缀,一个索引的所有数据也都有相同的前缀。 元信息管理 元信息也是以 Key-value 存储在了 TiKV 中。 每个 Database/Table 都被分配了一个唯一的 ID。这个 ID 会编码到 Key 中,再加上 m_ 前缀。这样可以构造出一个 Key,Value 中存储的是序列化后的元信息。 当前所有表结构信息的最新版本号,也会构成 Key-value 键值对,存储在 pd-server 内置的 etcd 中。 其Key 为"/tidb/ddl/global_schema_version",Value 是类型为 int64 的版本号值。 有一个后台线程在不断的检查 etcd 中存储的表结构信息的版本号是否发生变化,并且保证在一定时间内一定能够获取版本的变化。 TiDB 的 SQL层,即 tidb-server 负责将 SQL 翻译成 Key-Value 操作,将其转发给共用的分布式 Key-Value 存储层 TiKV,然后组装 TiKV 返回的结果,最终将查询结果返回给客户端。 将 SQL 查询映射为对 KV 的查询,再通过 KV 接口获取对应的数据,最后执行各种计算。 这一层的节点都是无状态的,节点本身并不存储数据,节点之间完全对等。 5、PD讲调度 作为一个分布式高可用存储系统,必须满足的需求,包括四种: 副本数量不能多也不能少 副本需要分布在不同的机器上 新加节点后,可以将其他节点上的副本迁移过来 节点下线后,需要将该节点的数据迁移走 作为一个良好的分布式系统,需要优化的地方,包括: 维持整个集群的 Leader 分布均匀 维持每个节点的储存容量均匀 维持访问热点分布均匀 控制 Balance 的速度,避免影响在线服务 管理节点状态,包括手动上线/下线节点,以及自动下线失效节点 信息收集 调度依赖于整个集群信息的收集,简单来说,我们需要知道每个 TiKV 节点的状态以及每个 Region 的状态。TiKV 集群会向 PD 汇报两类消息: 每个 TiKV 节点会定期向 PD 汇报节点的整体信息。 TiKV 节点(Store)与 PD 之间存在心跳包,一方面 PD 通过心跳包检测每个 Store 是否存活,以及是否有新加入的 Store;另一方面,心跳包中也会携带这个 Store 的状态信息: 总磁盘容量 可用磁盘容量 承载的 Region 数量 数据写入速度 发送/接受的 Snapshot 数量(Replica 之间可能会通过 Snapshot 同步数据) 是否过载 标签信息(标签是具备层级关系的一系列 Tag) 每个 Raft Group 的 Leader 会定期向 PD 汇报信息。 每个 Raft Group 的 Leader 和 PD 之间存在心跳包,用于汇报这个 Region 的状态,主要包括下面几点信息: Leader 的位置 Followers 的位置 掉线 Replica 的个数 数据写入/读取的速度 PD 不断的通过这两类心跳消息收集整个集群的信息,再以这些信息作为决策的依据。 默认是 30 分钟,如果一直没有心跳包,就认为是 Store 已经下线。 再决定需要将这个 Store 上面的 Region 都调度走。但是有的时候,是运维人员主动将某台机器下线,这个时候,可以通过 PD 的管理接口通知 PD 该 Store 不可用,PD 就可以马上判断需要将这个 Store 上面的 Region 都调度走。 调度的策略 一个 Region 的 Replica 数量正确 当 PD 通过某个 Region Leader 的心跳包发现这个 Region 的 Replica 数量不满足要求时,需要通过 Add/Remove Replica 操作调整 Replica 数量。出现这种情况的可能原因是: 某个节点掉线,上面的数据全部丢失,导致一些 Region 的 Replica 数量不足 某个掉线节点又恢复服务,自动接入集群,这样之前已经补足了 Replica 的 Region 的 Replica 数量多过,需要删除某个 Replica 管理员调整了副本策略,修改了 max-replicas 的配置 一个 Raft Group 中的多个 Replica 不在同一个位置 副本在 Store 之间的分布均匀分配 访问热点数量在 Store 之间均匀分配 各个 Store 的存储空间占用大致相等 控制调度速度,避免影响在线服务 如果希望加快调度(比如已经停服务升级,增加新节点,希望尽快调度),那么可以通过 pd-ctl 手动加快调度速度。 支持手动下线节点 当通过 pd-ctl 手动下线节点后,PD 会在一定的速率控制下,将节点上的数据调度走。当调度完成后,就会将这个节点置为下线状态。 调度的实现 PD 不断的通过 Store 或者 Leader 的心跳包收集信息,获得整个集群的详细数据,并且根据这些信息以及调度策略生成调度操作序列, 每次收到 Region Leader 发来的心跳包时,PD 都会检查是否有对这个 Region 待进行的操作,通过心跳包的回复消息, 将需要进行的操作返回给 Region Leader,并在后面的心跳包中监测执行结果。 注意这里的操作只是给 Region Leader 的建议,并不保证一定能得到执行, 具体是否会执行以及什么时候执行,由 Region Leader 自己根据当前自身状态来定。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!