TiDB学习
TiDB调研
一、介绍
TiDB 是一款定位于在线事务处理/在线分析处理( HTAP: Hybrid Transactional/Analytical Processing)的融合型数据库产品,实现了一键水平伸缩,强一致性的多副本数据安全,分布式事务,实时 OLAP 等重要特性。同时兼容 MySQL 协议和生态,迁移便捷,运维成本极低。
二、TiDB 基本功能
本文详细介绍 TiDB 具备的基本功能。
数据类型
- 数值类型:BIT、BOOL|BOOLEAN、SMALLINT、MEDIUMINT、INT|INTEGER、BIGINT、FLOAT、DOUBLE、DECIMAL。
- 日期和时间类型:DATE、TIME、DATETIME、TIMESTAMP、YEAR。
- 字符串类型:CHAR、VARCHAR、TEXT、TINYTEXT、MEDIUMTEXT、LONGTEXT、BINARY、VARBINARY、BLOB、TINYBLOB、MEDIUMBLOB、LONGBLOB、ENUM、SET。
- JSON 类型。
运算符
- 算术运算符、位运算符、比较运算符、逻辑运算符、日期和时间运算符等。
字符集及排序规则
- 字符集:UTF8、UTF8MB4、BINARY、ASCII、LATIN1。
- 排序规则:UTF8MB4_GENERAL_CI、UTF8MB4_GENERAL_BIN、UTF8_GENERAL_CI、UTF8_GENERAL_BIN、BINARY。
函数
- 控制流函数、字符串函数、日期和时间函数、位函数、数据类型转换函数、数据加解密函数、压缩和解压函数、信息函数、JSON 函数、聚合函数、窗口函数等。
SQL 语句
- 完全支持标准的 Data Definition Language (DDL) 语句,例如:CREATE、DROP、ALTER、RENAME、TRUNCATE 等。
- 完全支持标准的 Data Manipulation Language (DML) 语句,例如:INSERT、REPLACE、SELECT、Subqueries、UPDATE、LOAD DATA 等。
- 完全支持标准的 Transactional and Locking 语句,例如:START TRANSACTION、COMMIT、ROLLBACK、SET TRANSACTION 等。
- 完全支持标准的 Database Administration 语句,例如:SHOW、SET 等。
- 完全支持标准的 Utility 语句,例如:DESCRIBE、EXPLAIN、USE 等。
- 完全支持 SQL GROUP BY 和 ORDER BY 子语句。
- 完全支持标准 SQL 语法的 LEFT OUTER JOIN 和 RIGHT OUTER JOIN。
- 完全支持标准 SQL 要求的表和列别名。
分区表
- 支持 Range 分区(TiDB主要采用Range分区)。
- 支持 Hash 分区。
视图
- 支持普通视图。
约束
- 支持非空约束。
- 支持主键约束。
- 支持唯一约束。
安全
- 支持基于 RBAC (role-based access control) 的权限管理。
- 支持密码管理。
- 支持通信、数据加密。
- 支持 IP 白名单。
- 支持审计功能。
工具
- 支持快速备份功能。
- 支持通过工具从 MySQL 迁移数据到 TiDB。
- 支持通过工具部署、运维 TiDB。
三、TiDB 整体架构
与传统的单机数据库相比,TiDB 具有以下优势:
- 纯分布式架构,拥有良好的扩展性,支持弹性的扩缩容
- 支持 SQL,对外暴露 MySQL 的网络协议,并兼容大多数 MySQL 的语法,在大多数场景下可以直接替换 MySQL
- 默认支持高可用,在少数副本失效的情况下,数据库本身能够自动进行数据修复和故障转移,对业务透明
- 支持 ACID 事务,对于一些有强一致需求的场景友好,例如:银行转账
- 具有丰富的工具链生态,覆盖数据迁移、同步、备份等多种场景
- TiDB Server:SQL 层,对外暴露 MySQL 协议的连接 endpoint,负责接受客户端的连接,执行 SQL 解析和优化,最终生成分布式执行计划。TiDB 层本身是无状态的,实践中可以启动多个 TiDB 实例,通过负载均衡组件(如 LVS、HAProxy 或 F5)对外提供统一的接入地址,客户端的连接可以均匀地分摊在多个 TiDB 实例上以达到负载均衡的效果。TiDB Server 本身并不存储数据,只是解析 SQL,将实际的数据读取请求转发给底层的存储节点 TiKV(或 TiFlash)。
- PD Server:整个 TiDB 集群的元信息管理模块,负责存储每个 TiKV 节点实时的数据分布情况和集群的整体拓扑结构,提供 TiDB Dashboard 管控界面,并为分布式事务分配事务 ID。PD 不仅存储元信息,同时还会根据 TiKV 节点实时上报的数据分布状态,下发数据调度命令给具体的 TiKV 节点,可以说是整个集群的“大脑”。此外,PD 本身也是由至少 3 个节点构成,拥有高可用的能力。建议部署奇数个 PD 节点。
- 存储节点
- TiKV Server:负责存储数据,从外部看 TiKV 是一个分布式的提供事务的 Key-Value 存储引擎。存储数据的基本单位是 Region,每个 Region 负责存储一个 Key Range(从 StartKey 到 EndKey 的左闭右开区间)的数据,每个 TiKV 节点会负责多个 Region。TiKV 的 API 在 KV 键值对层面提供对分布式事务的原生支持,默认提供了 SI (Snapshot Isolation) 的隔离级别,这也是 TiDB 在 SQL 层面支持分布式事务的核心。TiDB 的 SQL 层做完 SQL 解析后,会将 SQL 的执行计划转换为对 TiKV API 的实际调用。所以,数据都存储在 TiKV 中。另外,TiKV 中的数据都会自动维护多副本(默认为三副本),天然支持高可用和自动故障转移。
- TiFlash:TiFlash 是一类特殊的存储节点。和普通 TiKV 节点不一样的是,在 TiFlash 内部,数据是以列式的形式进行存储,主要的功能是为分析型的场景加速。
四、TiDB 数据库的存储
Key-Value Pairs(键值对)
作为保存数据的系统,首先要决定的是数据的存储模型,也就是数据以什么样的形式保存下来。TiKV 的选择是 Key-Value 模型,并且提供有序遍历方法。 TiKV 数据存储的两个关键点:
- 这是一个巨大的 Map(可以类比一下 C++ 的 std::map),也就是存储的是 Key-Value Pairs(键值对)
- 这个 Map 中的 Key-Value pair 按照 Key 的二进制顺序有序,也就是可以 Seek 到某一个 Key 的位置,然后不断地调用 Next 方法以递增的顺序获取比这个 Key 大的 Key-Value。
本地存储 (RocksDB)
任何持久化的存储引擎,数据终归要保存在磁盘上,TiKV 也不例外。但是 TiKV 没有选择直接向磁盘上写数据,而是把数据保存在 RocksDB 中,具体的数据落地由 RocksDB 负责。这个选择的原因是开发一个单机存储引擎工作量很大,特别是要做一个高性能的单机引擎,需要做各种细致的优化,而 RocksDB 是由 Facebook 开源的一个非常优秀的单机 KV 存储引擎,可以满足 TiKV 对单机引擎的各种要求。这里可以简单的认为 RocksDB 是一个单机的持久化 Key-Value Map。
Raft 协议
接下来 TiKV 的实现面临一件更难的事情:如何保证单机失效的情况下,数据不丢失,不出错?
简单来说,需要想办法把数据复制到多台机器上,这样一台机器无法服务了,其他的机器上的副本还能提供服务; 复杂来说,还需要这个数据复制方案是可靠和高效的,并且能处理副本失效的情况。TiKV 选择了 Raft 算法。Raft 是一个一致性协议,本文只会对 Raft 做一个简要的介绍,细节问题可以参考它的论文。Raft 提供几个重要的功能:
- Leader(主副本)选举
- 成员变更(如添加副本、删除副本、转移 Leader 等操作)
- 日志复制
TiKV 利用 Raft 来做数据复制,每个数据变更都会落地为一条 Raft 日志,通过 Raft 的日志复制功能,将数据安全可靠地同步到复制组的每一个节点中。不过在实际写入中,根据 Raft 的协议,只需要同步复制到多数节点,即可安全地认为数据写入成功。
总结一下,通过单机的 RocksDB,TiKV 可以将数据快速地存储在磁盘上;通过 Raft,将数据复制到多台机器上,以防单机失效。数据的写入是通过 Raft 这一层的接口写入,而不是直接写 RocksDB。通过实现 Raft,TiKV 变成了一个分布式的 Key-Value 存储,少数几台机器宕机也能通过原生的 Raft 协议自动把副本补全,可以做到对业务无感知。
Region
TiKV 使用range来分散数据,将整个 Key-Value 空间分成很多段,每一段是一系列连续的 Key,将每一段叫做一个 Region,并且会尽量保持每个 Region 中保存的数据不超过一定的大小,目前在 TiKV 中默认是 96MB。每一个 Region 都可以用 [StartKey,EndKey) 这样一个左闭右开区间来描述。
将数据划分成 Region 后,TiKV 将会做两件重要的事情:
- 以 Region 为单位,将数据分散在集群中所有的节点上,并且尽量保证每个节点上服务的 Region 数量差不多。
- 以 Region 为单位做 Raft 的复制和成员管理。
这两点非常重要:
- 先看第一点,数据按照 Key 切分成很多 Region,每个 Region 的数据只会保存在一个节点上面(暂不考虑多副本)。TiDB 系统会有一个组件(PD)来负责将 Region 尽可能均匀的散布在集群中所有的节点上,这样一方面实现了存储容量的水平扩展(增加新的节点后,会自动将其他节点上的 Region 调度过来),另一方面也实现了负载均衡(不会出现某个节点有很多数据,其他节点上没什么数据的情况)。同时为了保证上层客户端能够访问所需要的数据,系统中也会有一个组件(PD)记录 Region 在节点上面的分布情况,也就是通过任意一个 Key 就能查询到这个 Key 在哪个 Region 中,以及这个 Region 目前在哪个节点上(即 Key 的位置路由信息)。至于负责这两项重要工作的组件(PD),会在后续介绍。
- 对于第二点,TiKV 是以 Region 为单位做数据的复制,也就是一个 Region 的数据会保存多个副本,TiKV 将每一个副本叫做一个 Replica。Replica 之间是通过 Raft 来保持数据的一致,一个 Region 的多个 Replica 会保存在不同的节点上,构成一个 Raft Group。其中一个 Replica 会作为这个 Group 的 Leader,其他的 Replica 作为 Follower。默认情况下,所有的读和写都是通过 Leader 进行,读操作在 Leader 上即可完成,而写操作再由 Leader 复制给 Follower。 大家理解了 Region 之后,应该可以理解下面这张图:
以 Region 为单位做数据的分散和复制,TiKV 就成为了一个分布式的具备一定容灾能力的 KeyValue 系统,不用再担心数据存不下,或者是磁盘故障丢失数据的问题。
MVCC
很多数据库都会实现多版本并发控制 (MVCC),TiKV 也不例外。设想这样的场景:两个客户端同时去修改一个 Key 的 Value,如果没有数据的多版本控制,就需要对数据上锁,在分布式场景下,可能会带来性能以及死锁问题。 TiKV 的 MVCC 实现是通过在 Key 后面添加版本号来实现,简单来说,没有 MVCC 之前,可以把 TiKV 看做这样的:
Key1 -> Value
Key2 -> Value
……
KeyN -> Value
有了 MVCC 之后,TiKV 的 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
……
注意,对于同一个 Key 的多个版本,版本号较大的会被放在前面,版本号小的会被放在后面(见 Key-Value 一节, Key 是有序的排列),这样当用户通过一个 Key + Version 来获取 Value 的时候,可以通过 Key 和 Version 构造出 MVCC 的 Key,也就是 Key_Version。然后可以直接通过 RocksDB 的 SeekPrefix(Key_Version) API,定位到第一个大于等于这个 Key_Version 的位置。
五、TiDB 数据库的计算
表数据与 Key-Value 的映射关系
首先,OLTP 场景下有大量针对单行或者多行的增、删、改、查等操作,要求数据库具备快速读取一行数据的能力。因此,对应的 Key 最好有一个唯一 ID(显示或隐式的 ID),以方便快速定位。其次,很多 OLAP 型查询需要进行全表扫描。如果能够将一个表中所有行的 Key 编码到一个区间内,就可以通过范围查询高效完成全表扫描的任务。
基于上述考虑,TiDB 中的表数据与 Key-Value 的映射关系作了如下设计:
- 为了保证同一个表的数据放在一起,方便查找,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
Key-Value 映射关系示例
假设 TiDB 中有如下这个表:
CREATE TABLE User {
ID int,
Name varchar(20),
Role varchar(20),
Age int,
PRIMARY KEY (ID),
KEY idxAge (Age)
};
假设该表中有 3 行数据:
1, "TiDB", "SQL Layer", 10
2, "TiKV", "KV Engine", 20
3, "PD", "Manager", 30
首先每行数据都会映射为一个 (Key, Value) 键值对,同时该表有一个 int
类型的主键,所以 RowID
的值即为该主键的值。假设该表的 TableID
为 10,则其存储在 TiKV 上的表数据为:
t10_r1 --> ["TiDB", "SQL Layer", 10]
t10_r2 --> ["TiKV", "KV Engine", 20]
t10_r3 --> ["PD", "Manager", 30]
除了主键外,该表还有一个非唯一的普通二级索引 idxAge
,假设这个索引的 IndexID
为 1,则其存储在 TiKV 上的索引数据为:
t10_i1_10_1 --> null
t10_i1_20_2 --> null
t10_i1_30_3 --> null
元信息管理
TiDB 中每个 Database
和 Table
都有元信息,也就是其定义以及各项属性。这些信息也需要持久化,TiDB 将这些信息也存储在了 TiKV 中。
每个 Database
/Table
都被分配了一个唯一的 ID,这个 ID 作为唯一标识,并且在编码为 Key-Value 时,这个 ID 都会编码到 Key 中,再加上 m_
前缀。这样可以构造出一个 Key,Value 中存储的是序列化后的元信息。
除此之外,TiDB 还用一个专门的 (Key, Value) 键值对存储当前所有表结构信息的最新版本号。这个键值对是全局的,每次 DDL 操作的状态改变时其版本号都会加 1。目前,TiDB 把这个键值对持久化存储在 PD Server 中,其 Key 是 "/tidb/ddl/global_schema_version",Value 是类型为 int64 的版本号值。TiDB 参考了 Google F1 的 Online Schema 变更算法,有一个后台线程在不断地检查 PD Server 中存储的表结构信息的版本号是否发生变化,并且保证在一定时间内一定能够获取版本的变化。
SQL 层简介
TiDB 的 SQL 层,即 TiDB Server,负责将 SQL 翻译成 Key-Value 操作,将其转发给共用的分布式 Key-Value 存储层 TiKV,然后组装 TiKV 返回的结果,最终将查询结果返回给客户端。
这一层的节点都是无状态的,节点本身并不存储数据,节点之间完全对等。
SQL 运算
最简单的方案就是通过上一节所述的表数据与 Key-Value 的映射关系方案,将 SQL 查询映射为对 KV 的查询,再通过 KV 接口获取对应的数据,最后执行各种计算。
比如 select count(*) from user where name = "TiDB"
这样一个 SQL 语句,它需要读取表中所有的数据,然后检查 name
字段是否是 TiDB
,如果是的话,则返回这一行。具体流程如下:
- 构造出 Key Range:一个表中所有的
RowID
都在[0, MaxInt64)
这个范围内,使用0
和MaxInt64
根据行数据的Key
编码规则,就能构造出一个[StartKey, EndKey)
的左闭右开区间。 - 扫描 Key Range:根据上面构造出的 Key Range,读取 TiKV 中的数据。
- 过滤数据:对于读到的每一行数据,计算
name = "TiDB"
这个表达式,如果为真,则向上返回这一行,否则丢弃这一行数据。 - 计算
Count(*)
:对符合要求的每一行,累计到Count(*)
的结果上面。
SQL 层架构
用户的 SQL 请求会直接或者通过 Load Balancer
发送到 TiDB Server,TiDB Server 会解析 MySQL Protocol Packet
,获取请求内容,对 SQL 进行语法解析和语义分析,制定和优化查询计划,执行查询计划并获取和处理数据。数据全部存储在 TiKV 集群中,所以在这个过程中 TiDB Server 需要和 TiKV 交互,获取数据。最后 TiDB Server 需要将查询结果返回给用户。
六、官方建议服务器配置
Linux 操作系统版本要求
Linux 操作系统平台 | 版本 |
---|---|
Red Hat Enterprise Linux | 7.3 及以上 |
CentOS | 7.3 及以上 |
Oracle Enterprise Linux | 7.3 及以上 |
Ubuntu LTS | 16.04 及以上 |
注意:
- TiDB 只支持 Red Hat 兼容内核 (RHCK) 的 Oracle Enterprise Linux,不支持 Oracle Enterprise Linux 提供的 Unbreakable Enterprise Kernel。
- TiDB 在 CentOS 7.3 的环境下进行过大量的测试,同时社区也有很多该操作系统部署的最佳实践,因此,建议使用 CentOS 7.3 以上的 Linux 操作系统来部署 TiDB。
- 以上 Linux 操作系统可运行在物理服务器以及 VMware、KVM、XEN 主流虚拟化环境上。
软件配置要求
中控机软件配置
软件 | 版本 |
---|---|
sshpass | 1.06 及以上 |
TiUP | 0.6.2 及以上 |
注意:
中控机需要部署 TiUP 软件来完成 TiDB 集群运维管理。
目标主机建议配置软件
软件 | 版本 |
---|---|
sshpass | 1.06 及以上 |
numa | 2.0.12 及以上 |
服务器建议配置
TiDB 支持部署和运行在 Intel x86-64 架构的 64 位通用硬件服务器平台或者 ARM 架构的硬件服务器平台。对于开发,测试,及生产环境的服务器硬件配置(不包含操作系统 OS 本身的占用)有以下要求和建议:
开发及测试环境:
组件 | CPU | 内存 | 本地存储 | 网络 | 实例数量(最低要求) |
---|---|---|---|---|---|
TiDB | 8 核+ | 16 GB+ | 无特殊要求 | 千兆网卡 | 1(可与 PD 同机器) |
PD | 4 核+ | 8 GB+ | SAS, 200 GB+ | 千兆网卡 | 1(可与 TiDB 同机器) |
TiKV | 8 核+ | 32 GB+ | SSD, 200 GB+ | 千兆网卡 | 3 |
TiFlash | 32 核+ | 64 GB+ | SSD, 200 GB+ | 千兆网卡 | 1 |
TiCDC | 8 核+ | 16 GB+ | SAS, 200 GB+ | 千兆网卡 | 1 |
生产环境:
组件 | CPU | 内存 | 硬盘类型 | 网络 | 实例数量(最低要求) |
---|---|---|---|---|---|
TiDB | 16 核+ | 32 GB+ | SAS | 万兆网卡(2 块最佳) | 2 |
PD | 4核+ | 8 GB+ | SSD | 万兆网卡(2块最佳) | 3 |
TiKV | 16 核+ | 32 GB+ | SSD | 万兆网卡(2 块最佳) | 3 |
TiFlash | 48 核+ | 128 GB+ | 1 or more SSDs | 万兆网卡(2 块最佳) | 2 |
TiCDC | 16 核+ | 64 GB+ | SSD | 万兆网卡(2 块最佳) | 2 |
监控 | 8 核+ | 16 GB+ | SAS | 千兆网卡 | 1 |
注意:
- 生产环境中的 TiDB 和 PD 可以部署和运行在同服务器上,如对性能和可靠性有更高的要求,应尽可能分开部署。
- 生产环境强烈推荐使用更高的配置。
- TiKV 硬盘大小配置建议 PCI-E SSD 不超过 2 TB,普通 SSD 不超过 1.5 TB。
- TiFlash 支持多盘部署。
- TiFlash 数据目录的第一块磁盘推荐用高性能 SSD 来缓冲 TiKV 同步数据的实时写入,该盘性能应不低于 TiKV 所使用的磁盘,比如 PCI-E SSD。并且该磁盘容量建议不小于总容量的 10%,否则它可能成为这个节点的能承载的数据量的瓶颈。而其他磁盘可以根据需求部署多块普通 SSD,当然更好的 PCI-E SSD 硬盘会带来更好的性能。
- TiFlash 推荐与 TiKV 部署在不同节点,如果条件所限必须将 TiFlash 与 TiKV 部署在相同节点,则需要适当增加 CPU 核数和内存,且尽量将 TiFlash 与 TiKV 部署在不同的磁盘,以免互相干扰。
- TiFlash 硬盘总容量大致为:
整个 TiKV 集群的需同步数据容量 / TiKV 副本数 * TiFlash 副本数
。例如整体 TiKV 的规划容量为 1 TB、TiKV 副本数为 3、TiFlash 副本数为 2,则 TiFlash 的推荐总容量为1024 GB / 3 * 2
。用户可以选择同步部分表数据而非全部,具体容量可以根据需要同步的表的数据量具体分析。 - TiCDC 硬盘配置建议 200 GB+ PCIE-SSD。
- TiCDC 目前为实验特性,不建议在生产环境中使用。
网络要求
组件 | 默认端口 | 说明 |
---|---|---|
TiDB | 4000 | 应用及 DBA 工具访问通信端口 |
TiDB | 10080 | TiDB 状态信息上报通信端口 |
TiKV | 20160 | TiKV 通信端口 |
PD | 2379 | 提供 TiDB 和 PD 通信端口 |
PD | 2380 | PD 集群节点间通信端口 |
TiFlash | 9000 | TiFlash TCP 服务端口 |
TiFlash | 8123 | TiFlash HTTP 服务端口 |
TiFlash | 3930 | TiFlash RAFT 服务和 Coprocessor 服务端口 |
TiFlash | 20170 | TiFlash Proxy 服务端口 |
TiFlash | 20292 | Prometheus 拉取 TiFlash Proxy metrics 端口 |
TiFlash | 8234 | Prometheus 拉取 TiFlash metrics 端口 |
Pump | 8250 | Pump 通信端口 |
Drainer | 8249 | Drainer 通信端口 |
CDC | 8300 | CDC 通信接口 |
Prometheus | 9090 | Prometheus 服务通信端口 |
Node_exporter | 9100 | TiDB 集群每个节点的系统信息上报通信端口 |
Blackbox_exporter | 9115 | Blackbox_exporter 通信端口,用于 TiDB 集群端口监控 |
Grafana | 3000 | Web 监控服务对外服务和客户端(浏览器)访问端口 |
Alertmanager | 9093 | 告警 web 服务端口 |
Alertmanager | 9094 | 告警通信端口 |
七、TiKV介绍
TiKV 是一个分布式事务型的键值数据库,提供了满足 ACID 约束的分布式事务接口,并且通过 Raft 协议 保证了多副本数据一致性以及高可用。TiKV 作为 TiDB 的存储层,为用户写入 TiDB 的数据提供了持久化以及读写服务,同时还存储了 TiDB 的统计信息数据。
整体架构
与传统的整节点备份方式不同,TiKV 参考 Spanner 设计了 multi raft-group 的副本机制。将数据按照 key 的范围划分成大致相等的切片(下文统称为 Region),每一个切片会有多个副本(通常是 3 个),其中一个副本是 leader,提供读写服务。TiKV 通过 PD 对这些 Region 以及副本进行调度,以保证数据和读写负载都均匀地分散在各个 TiKV 上,这样的设计保证了整个集群资源的充分利用并且可以随着机器数量的增加水平扩展。
Region 与 RocksDB
虽然 TiKV 将数据按照范围切割成了多个 Region,但是同一个节点的所有 Region 数据仍然是不加区分地存储于同一个 RocksDB 实例上,而用于 Raft 协议复制所需要的日志则存储于另一个 RocksDB 实例。这样设计的原因是因为随机 I/O 的性能远低于顺序 I/O,所以 TiKV 使用同一个 RocksDB 实例来存储这些数据,以便不同 Region 的写入可以合并在一次 I/O 中。
Region 与 Raft 协议
Region 与副本之间通过 Raft 协议来维持数据一致性,任何写请求都只能在 leader 上写入,并且需要写入多数副本后(默认配置为 3 副本,即所有请求必须至少写入两个副本成功)才会返回客户端写入成功。
当某个 Region 的大小超过一定限制(默认是 144MB)后,TiKV 会将它分裂为两个或者更多个 Region,以保证各个 Region 的大小是大致接近的,这样更有利于 PD 进行调度决策。同样的,当某个 Region 因为大量的删除请求导致 Region 的大小变得更小时,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 这个副本。
分布式事务
TiKV 支持分布式事务,用户(或者 TiDB)可以一次性写入多个 key-value 而不必关心这些 key-value 是否处于同一个数据切片 (Region) 上,TiKV 通过两阶段提交保证了这些读写请求的 ACID 约束,详见 TiDB 乐观事务模型。
计算加速
TiKV 通过协处理器 (Coprocessor) 可以为 TiDB 分担一部分计算:TiDB 会将可以由存储层分担的计算下推。能否下推取决于 TiKV 是否可以支持相关下推。计算单元仍然是以 Region 为单位,即 TiKV 的一个 Coprocessor 计算请求中不会计算超过一个 Region 的数据。
八、TiDB的乐观事务说明
TiDB 在处理一个事务时,处理流程如下:
- 客户端 begin 了一个事务。
a. TiDB 从 PD 获取一个全局唯一递增的版本号作为当前事务的开始版本号,这里我们定义为该事务的start_ts
。 - 客户端发起读请求。
a. TiDB 从 PD 获取数据路由信息,数据具体存在哪个 TiKV 上。
b. TiDB 向 TiKV 获取start_ts
版本下对应的数据信息。 - 客户端发起写请求。
a. TiDB 对写入数据进行校验,如数据类型是否正确、是否符合唯一索引约束等,确保新写入数据事务符合一致性约束,将检查通过的数据存放在内存里。 - 客户端发起 commit。
- TiDB 开始两阶段提交将事务原子地提交,数据真正落盘。
a. TiDB 从当前要写入的数据中选择一个 Key 作为当前事务的 Primary Key。
b. TiDB 从 PD 获取所有数据的写入路由信息,并将所有的 Key 按照所有的路由进行分类。
c. TiDB 并发向所有涉及的 TiKV 发起 prewrite 请求,TiKV 收到 prewrite 数据后,检查数据版本信息是否存在冲突、过期,符合条件给数据加锁。
d. TiDB 收到所有的 prewrite 成功。
e. TiDB 向 PD 获取第二个全局唯一递增版本,作为本次事务的commit_ts
。
f. TiDB 向 Primary Key 所在 TiKV 发起第二阶段提交 commit 操作,TiKV 收到 commit 操作后,检查数据合法性,清理 prewrite 阶段留下的锁。
g. TiDB 收到 f 成功信息。 - TiDB 向客户端返回事务提交成功。
- TiDB 异步清理本次事务遗留的锁信息。
九、TiDB权限管理
TiDB 的权限管理系统按照 MySQL 的权限管理进行实现,TiDB 支持大部分的 MySQL 的语法和权限类型。
权限相关操作
授予权限
授予 xxx
用户对数据库 test
的读权限:
GRANT SELECT ON test.* TO 'xxx'@'%';
为 xxx
用户授予所有数据库,全部权限:
GRANT ALL PRIVILEGES ON *.* TO 'xxx'@'%';
GRANT
为一个不存在的用户授予权限时,默认并不会自动创建用户。该行为受 SQL Mode 中的 NO_AUTO_CREATE_USER
控制。 如果从 SQL Mode 中去掉 NO_AUTO_CREATE_USER
,当 GRANT
的目标用户不存在时,TiDB 会自动创建用户。
select @@sql_mode;
收回权限
REVOKE
语句与 GRANT
对应:
REVOKE ALL PRIVILEGES ON `test`.* FROM 'genius'@'localhost';
注意:
REVOKE
收回权限时只做精确匹配,若找不到记录则报错。而 GRANT
授予权限时可以使用模糊匹配。
查看为用户分配的权限
SHOW GRANTS
语句可以查看为用户分配了哪些权限。例如:
查看当前用户的权限:
SHOW GRANTS;
或者
SHOW GRANTS FOR CURRENT_USER();
TiDB 各操作需要的权限
TiDB 用户目前拥有的权限可以在 INFORMATION_SCHEMA.USER_PRIVILEGES
表中查找到。
权限类型 | 权限变量名 | 权限简述 |
---|---|---|
ALL | AllPriv | 所有权限 |
Drop | DropPriv | 删除 schema/table |
Index | IndexPriv | 创建/删除 index |
Alter | AlterPriv | 执行 ALTER 语句 |
Super | SuperPriv | 所有权限 |
Grant | GrantPriv | 授予其他用户权限 |
Create | CreatePriv | 创建 schema/table |
Select | SelectPriv | 读取表内容 |
Insert | InsertPriv | 插入数据到表 |
Update | UpdatePriv | 更新表中数据 |
Delete | DeletePriv | 删除表中数据 |
Reload | ReloadPriv | 执行 FLUSH 语句 |
Config | ConfigPriv | 动态加载配置 |
Trigger | TriggerPriv | 尚未使用 |
Process | ProcessPriv | 显示正在运行的任务 |
Execute | ExecutePriv | 执行 execute 语句 |
Drop Role | DropRolePriv | 执行 drop role |
Show View | ShowViewPriv | 执行 show create view |
References | ReferencesPriv | 尚未使用 |
Create View | CreateViewPriv | 创建视图 |
Create User | CreateUserPriv | 创建用户 |
Create Role | CreateRolePriv | 执行 create role |
Show Databases | ShowDBPriv | 显示 database 内的表情况 |
ALTER
- 对于所有的
ALTER
语句,均需要用户对所操作的表拥有ALTER
权限。 - 除
ALTER...DROP
和ALTER...RENAME TO
外,均需要对所操作表拥有INSERT
和CREATE
权限。 - 对于
ALTER...DROP
语句,需要对表拥有DROP
权限。 - 对于
ALTER...RENAME TO
语句,需要对重命名前的表拥有DROP
权限,对重命名后的表拥有CREATE
和INSERT
权限。
注意:
根据 MySQL 5.7 文档中的说明,对表进行
ALTER
操作需要INSERT
和CREATE
权限,但在 MySQL 5.7.25 版本实际情况中,该操作仅需要ALTER
权限。目前,TiDB 中的ALTER
权限与 MySQL 实际行为保持一致。
CREATE DATABASE
需要拥有全局 CREATE
权限。
CREATE INDEX
需要对所操作的表拥有 INDEX
权限。
CREATE TABLE
需要对要创建的表所在的数据库拥有 CREATE
权限;若使用 CREATE TABLE...LIKE...
需要对相关的表拥有 SELECT
权限。
CREATE VIEW
需要拥有 CREATE VIEW
权限。
注意:
如果当前登录用户与创建视图的用户不同,除需要
CREATE VIEW
权限外,还需要SUPER
权限。
DROP DATABASE
需要对数据库拥有 DROP
权限。
DROP INDEX
需要对所操作的表拥有 INDEX
权限。
DROP TABLES
需要对所操作的表拥有 DROP
权限。
LOAD DATA
LOAD DATA
需要对所操作的表拥有 INSERT
权限。
TRUNCATE TABLE
需要对所操作的表拥有 DROP
权限。
RENAME TABLE
需要对重命名前的表拥有 ALTER
和 DROP
权限,对重命名后的表拥有 CREATE
和 INSERT
权限。
ANALYZE TABLE
需要对所操作的表拥有 INSERT
和 SELECT
权限。
SHOW
SHOW CREATE TABLE
需要任意一种权限。
SHOW CREATE VIEW
需要 SHOW VIEW
权限。
SHOW GRANTS
需要拥有对 mysql
数据库的 SELECT
权限。如果是使用 SHOW GRANTS
查看当前用户权限,则不需要任何权限。
CREATE ROLE/USER
CREATE ROLE
需要 CREATE ROLE
权限。
CREATE USER
需要 CREATE USER
权限
DROP ROLE/USER
DROP ROLE
需要 DROP ROLE
权限。
DROP USER
需要 CREATE USER
权限
ALTER USER
ALTER USER
需要 CREATE USER
权限。
GRANT
GRANT
需要 GRANT
权限并且拥有 GRANT
所赋予的权限。
如果在 GRANTS
语句中创建用户,需要有 CREATE USER
权限。
GRANT ROLE
操作需要拥有 SUPER
权限。
REVOKE
REVOKE
需要 GRANT
权限并且拥有 REVOKE
所指定要撤销的权限。
REVOKE ROLE
操作需要拥有 SUPER
权限。
SET GLOBAL
使用 SET GLOBAL
设置全局变量需要拥有 SUPER
权限。
ADMIN
需要拥有 SUPER
权限。
SET DEFAULT ROLE
需要拥有 SUPER
权限。
KILL
使用 KILL
终止其他用户的会话需要拥有 SUPER
权限。