TiDB是开源分布式关系型数据库,是NewSQL的一种。业界类似产品有Google Spanner、阿里OceanBase。
架构图
-
计算节点 -- TiDB Server
- 对外提供兼容MySQL协议接入节点,处理客户端链接。
- 本身无状态,不存储数据。
- 协议SQL解析,生成执行计划,并负责部分执行。
-
存储 -- TiKV
- 负责存储数据,是一个分布式的提供事务的 Key-Value 存储引擎
- TiKV 中的数据都会自动维护多副本(默认为三副本),支持高可用和自动故障转移。
-
管理 -- PD(Placement Driver)
- 整个 TiDB 集群的元信息管理模块,负责数据分布的调度
- 为分布式事务分配事务 ID
存储
单机存储
单机存储使用RocksDB。RocksDB由facebook维护,是由Google的基于LSM-Tree的LevelDB扩展开发而来,提供提供如下能力:
- 优异的随机修改(插入)和读取性能
- key顺序排列,提供遍历key能力
- 快照访问
分布式共识
单机存储可能因单机故障而丢失数据,可通过多副本机制避免单机故障。但如何保证多副本之间保持一致,就是分布式共识的问题。TiDB中,通过Raft协议实现。
Raft简述
Raft是分布式共识协议,以唯一主节点提供读写简化共识了算法,提供如下功能:
- Leader 选举
- 成员变更
- 日志复制
日志复制
- 主节点新增日志
- 发送给从节点
- 成功发送给多数从节点,主节点应用日志
- 从节点应用日志
选主
选主可以视为特殊的日志复制。
成员变更
成员变更也可以视为特殊的日志复制,不过需在新旧两个集群中分别进行一次,共两次。
TiDB中的Raft
数据分片分布与共识(Region)
数据分布方式:
- Hash:对key hash后选择对应的存储节,redis cluster的一致性hash。
- Range:某一段连续的 key 都保存在一个存储节点上。
TiDB选择Range,用 StartKey 到 EndKey 这样一个左闭右开区间作为一个Region,同时控制大小不超过96MB。
- 以 Region 为单位,将数据分散在集群中所有的节点上,并且尽量保证每个节点上服务的 Region 数量差不多
- 以 Region 为单位做 Raft 的复制和成员管理,规避单Raft性能瓶颈。
\
MVCC
TiKV 的 MVCC 实现是通过在 Key 后面添加 Version 来实现:
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
……
计算
表的存储
TiDB 对每个表分配一个 TableID,每一个索引都会分配一个 IndexID,每一行分配一个 RowID(如果表有整数型的主键,那么会用主键的值当做 RowID)。其中 TableID 在整个集群内唯一,IndexID/RowID 在表内唯一,这些 ID 都是 int64 类型。
行数据的KV存储:
Key: tablePrefix{tableID}_recordPrefixSep{rowID}
Value: [col1, col2, col3, col4]
唯一索引的存储:
Key: tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue
Value: rowID
非唯一索引的数据:
Key: tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue_rowID
Value: null
Prefix:
tablePrefix = []byte{'t'}
recordPrefixSep = []byte("_r")
indexPrefixSep = []byte("_i")
元信息的存储
数据库/表的元信息,以单独的m_为前缀存储在KV中
SQL的执行
调度
调度的目标
- 一个 Region 的 Replica 数量正确
- 一个 Raft Group 中的多个 Replica 不在同一个位置
- 副本在 Store (TiKV节点) 之间的分布均匀分配
- Leader 数量在 Store 之间均匀分配
- 访问热点数量在 Store 之间均匀分配
- 各个 Store 的存储空间占用大致相等
- 控制调度速度,避免影响在线服务
- 支持手动下线节点
调度的操作
- 增加/删除一个副本
- 将 Leader 角色在一个 Region Raft Group 的不同副本之间迁移
调度的实现
- 调度由PD生成策略,Region Raft Group自主执行
- TiKV定期向PD发有心跳包,汇报TiKV机器负载情况
- Region的Leader定期向PD发心跳包,汇报Region的情况
- PD通过心跳包收集信息,获得整个集群的详细数据,并且根据这些信息以及调度策略生成调度操作序列。
- PD通过给Region的Leader心跳包回包中下发策略,通知Leader执行调度,并有后续的心跳包监控执行结果。
与MySQL的兼容性
- 高度兼容MySQL 5.7 协议,支持分布式事务。
- 字符集近支持
ascii、latin1、binary、utf8、utf8mb4、gbk。
- 不支持外键约束、存储过程与函数、触发器、事件、自定义函数、全文语法与索引、空间类型函数等
AUTO_INCREMENT仅保证唯一,不保证集群全局连续与自增(单个TiDB内可保持)。仅为唯一的情况,建议使用AUTO_RANDOM
- 多行结果查询SQL行顺序不稳定,因结果可能由多个TiKV汇总。
- 执行计划分析
explain与MySQL有差异
- 业务自定义生成的自增字段可能导致热点数据问题
- 删除数据为标记删除,由后台异步GC实现物理删除
试用
# 安装tiup
curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh
# 启动tidb(会自动下载相应组件)
tiup playground v6.1.0 --db 1 --pd 1 --kv 1
引入TiDB实践
背景
业务存在如下问题:
- 原数据库为MongoDB,单节点部署,存在单机故障风险
- 接口性能遇到瓶颈,磁盘IOPS到达瓶颈
性能测试
3台8C16G机器,混合部署TiDB Server、TiKV、PD,Sysbench select性能测试:
| 平均CPU使用率 | 并发数 | select qps | 平均耗时ms | 99耗时ms |
|---|---|---|---|---|
| 39% | 16 | 2.38w | 0.67 | 0.88 |
| 60% | 32 | 4.1w | 0.78 | 0.98 |
| 81% | 64 | 5.93w | 1.08 | 3.24 |
| 89% | 128 | 7.54w | 1.69 | 7.52 |
| 92% | 256 | 8.86w | 2.89 | 14.0 |
| 94% | 512 | 10w | 5.10 | 17.9 |
| 96% | 1024 | 10.7w | 9.58 | 31.4 |
| 97% | 2048 | 11.4w | 18.17 | 60.6 |
引入方案
- 改动代码,引入TiDB组件,原有MongoDB平行,对TiDB不进行读写,T日发布生产,观察1周
2. T+1w,进行冷同步
3. T+8d,停写、增量同步
- T+8d,完成增量同步后,开双写 ,观察业务约一周
- T+2w,切读TiDB,并注意观察业务情况一周
- T+3w,切写TiDB,关闭双写,并注意观察业务情况一周
-
观察期注意点
- 业务流程是否正常
- 业务接口吞吐量、耗时情况
- TiDB机器资源消耗情况,CPU、内存、磁盘等
- TiDB数据库指标情况,SQL耗时、吞吐量等