一、TIDB设计原理

1 数据及数据库产品的发展

1.1 数据的种类

文字、字母、数字、图形、视频、音频、地理位置等。基于不同的数据种类,需要去选择专业的软件或系统去存储、管理、应用这些数据。
这种软件,我们通常称之为:DBMS,数据库管理系统。
数据库管理系统的种类: RDBMS、NoSQL、NewSQL、HTAP。

1.2 数据的增长


IDC统计,2020年,全球数据总量达到了44ZB。中国8000+EB,占全球总量18%。全球500亿个终端和设备。50%左右的物联网网络达到了比较大的瓶颈。
1个人,每天平均会产生1.5G,1辆车4TB,1架飞机40TB,1个工厂1PB。

1.3 数据库产品历史

1970+,EF CODD.
  • 里程碑一:关系型商业数据库

    1980+,Oracle、MSSQL、DB2、sysbase、informix等。
  • 里程碑二: 开源数据库萌芽

    MySQL、PG
  • 里程碑三:开源数据库盛行、NoSQL、大数据生态来临

    MySQL、PG,GFS、Bigtable、MAP,Hadoop、Hbase、redis.....
  • 里程碑四:数据库技术百花齐放(NewSQL)

    Spannner\TiDB\OB\DBaas产品
  • 里程碑五: HTAP的强需求

    TiDB

2 海量数据场景的机遇和挑战

2.1 海量数据给企业带来的挑战

  • 业务发展

海量数据带来的问题:存储量、吞吐量、读写QPS、TPS

  • 场景创新发展

  • 硬件和云计算的发展
    架构上的变迁,读写分离、分库分表、分布式系统、云原生。
  • 传统方案痛点之一:容量

  • 传统方案痛点之二:多种数据存储需求
    文本,视频,音频,图片等多种数据

3 海量数据场景需要什么样的数据库

3.1 认识分布式

分布式系统是计算机程序的集合,这些程序利用跨多个独立计算节点的计算资源来实现共同的目标。

  • 分布式系统代表产品
    2006 ,google GFS、bigtable、MapReduce
  • 分布式技术的主要挑战
    最大程度分治
    实现全局一致性
    故障容错
    网络不可靠和网络分区
  • 认识CAP

CAP: C 一致性 A 可用性 P 分区容错性
三个条件无法同时满足,只能同时满足其中两个。

  • 关系模型与事务
    不同类型数据库占比

ACID:

  • NewSQL原生分布式关系数据库
    分布式系统 + SQL + 事务

3.2 TiDB需要考虑的事情

  • 弹性扩展
  • 强一致高可用
  • 标准SQL事务ACID
  • 云原生
  • HTAP
  • 兼容主流生态与协议

3.3 数据库技术栈常见基础因素有哪些?

3.4 TiDB如何实现分层

PD调度:

  • 维持整个集群的 Leader 分布均匀
  • 维持每个节点的储存容量均匀
  • 维持访问热点分布均匀
  • 控制 Balance 的速度,避免影响在线服务
  • 管理节点状态,包括手动上线/下线节点,以及自动下线失效节点

TiDB Server:


4 TiDB-分布式存储结构TiKV

4.1 TiKV中数据是如何存储的

4.1.1 传统B+Tree结构

4.1.2 TiKV LSM Tree

TiKV使用RocksDB作为存储引擎,RocksDB是一个持久化的key-value数据库,RocksDB底层使用LSM Tree来存储数据。

传统关系型数据库使用btree或一些变体作为存储结构,能高效进行查找。但保存在磁盘中时它也有一个明显的缺陷,那就是逻辑上相离很近但物理却可能相隔很远,这就可能造成大量的磁盘随机读写。随机读写比顺序读写慢很多,为了提升IO性能,我们需要一种能将随机操作变为顺序操作的机制,于是便有了LSM树。

从概念上说,最基本的LSM是很简单的 。将之前使用一个大的查找结构(造成随机读写,影响写性能),变换为将写操作顺序的保存到一些相似的有序文件(也就是sstable)中。所以每个文件包 含短时间内的一些改动。因为文件是有序的,所以之后查找也会很快。文件是不可修改的,他们永远不会被更新,新的更新操作只会写到新的文件中。读操作检查所有的文件。通过周期性的合并这些文件来减少文件个数。

4.2 TiKV如何做到更细粒度的弹性扩容

  • 将一组key-value划分成Region
    Key是有序的

  • Region是数据管理的基本单位

    • 以 Region 为单位,将数据分散在集群中所有的节点上,并且尽量保证每个节点上服务的 Region 数量差不多
    • 以 Region 为单位做 Raft 的复制和成员管理

通过Region实现数据多副本管理(实现高并发,高可用):

  • 数据在不同节点上分布,可以避免单点故障数据丢失问题(高可用)
  • 数据很容易横向扩展(扩展性)
  • Region可以多个节点移动,实现数据均匀分布(海量数据架构)
  • 可以根据访问量调整Region分布,实现各个节点并发量的平衡(高并发)

4.3 TiKV如何实现高并发读写

  • 请求通过Region分散到各个节点上 (高可用)
  • 可以根据访问量调整Region分布,实现各个几点并发量的平衡(高并发)

4.4 TiKV如何保证多副本的一致性

如上图,TiKV 是以 Region 为单位做数据的复制,也就是一个 Region 的数据会保存多个副本,我们将每一个副本叫做一个 Replica。Replica 之间是通过 Raft 来保持数据的一致,一个 Region 的多个 Replica 会保存在不同的节点上,构成一个 Raft Group。其中一个 Replica 会作为这个 Group 的 Leader,其他的 Replica 作为 Follower。所有的读和写都是通过 Leader 进行,再由 Leader 复制给 Follower。

4.5 PD Cluster

调度集群

4.6 TiDB分布式事务

TiKV 源码中的 Storage 模块,它位于 Service 与底层 KV 存储引擎之间,主要负责事务的并发控制。TiKV 端事务相关的实现都在 Storage 模块中。
TiKV 通过 raft 来保证多副本之间的强一致,事务这块 TiKV 参考了 Google 的 Percolator 事务模型,并进行了一些优化。

Percolator事务处理流程

Percolator模型的事务处理流程大致可以分为两个阶段:预写阶段(Prewrite)和提交阶段(Commit)。

1.预写阶段(Prewrite):

  • 事务首先通过Coordinator(协调者)向Timestamp Oracle(时间戳服务器)获取一个开始时间戳(start_ts)。
  • 事务选择一个主行(Primary Line),并在其上写入时间戳和锁的记录,标记自己是Primary Lock。
  • 事务在其他需要修改的行中写入锁信息,标记自己不是Primary Lock,并指出Primary Lock的拥有者。
  • 如果在预写阶段发现冲突(如其他事务已在该行上设置了锁),则事务会进行回滚。

2.提交阶段(Commit):

  • 事务通过Coordinator向Timestamp Oracle获取一个提交时间戳(commit_ts)。
  • 事务首先提交主行的修改,将新的数据版本写入write列,并清除主行上的锁。
  • 一旦主行提交成功,事务会提交其他行的修改,并清除其他行上指向主行的锁。
  • 如果在提交阶段发现锁已被清除或已过期,事务会取消提交。

4.7 TiDB 事务隔离级别

Isolation LevelDirty WriteDirty ReadFuzzy ReadPhantom
READ UNCOMMITTEDNot PossiblePossiblePossiblePossible
READ COMMITTEDNot PossibleNot possiblePossiblePossible
REPEATABLE READNot PossibleNot possibleNot possiblePossible
SERIALIZABLENot PossibleNot possibleNot possibleNot possible

4.8 TiKV的MVCC机制

TiKV 的 MVCC 实现是通过在 Key 后面添加 Version 来实现,简单来说,没有 MVCC 之前,可以把 TiKV 看做这样的:

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。然后可以直接 Seek(Key-Version),定位到第一个大于等于这个 Key-Version 的位置。

5 TiDB-分布式SQL引擎

5.1 如何在KV上实现逻辑表转换

TiDB 对每个表分配一个 TableID,每一个索引都会分配一个 IndexID,每一行分配一个 RowID(如果表有整数型的 Primary Key,那么会用 Primary Key 的值当做 RowID),其中 TableID 在整个集群内唯一,IndexID/RowID 在表内唯一,这些 ID 都是 int64 类型。

每行数据按照如下规则进行编码成 Key-Value pair:

Key: tablePrefix{tableID}_recordPrefixSep{rowID}
Value: [col1, col2, col3, col4]

对于唯一 Index 数据,会按照如下规则编码成 Key-Value pair:

Key: tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue
Value: rowID

对应非唯一索引,因为同一个 Index 的 tablePrefix{tableID}_indexPrefixSep{indexID} 都一样,可能有多行数据的 ColumnsValue 是一样的,所以对于非唯一索引的编码做了一点调整:

Key: tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue_rowID
Value: null

这样能够对索引中的每行数据构造出唯一的 Key。

例子:
假设表中有 3 行数据:

1, "TiDB", "SQL Layer", 10
2, "TiKV", "KV Engine", 20
3, "PD", "Manager", 30

那么首先每行数据都会映射为一个 Key-Value pair,注意这个表有一个 Int 类型的 Primary Key,所以 RowID 的值即为这个 Primary Key 的值。假设这个表的 Table ID 为 10,其 Row 的数据为:

t10_r1 --> ["TiDB", "SQL Layer", 10]
t10_r2 --> ["TiKV", "KV Engine", 20]
t10_r3 --> ["PD", "Manager", 30]

除了 Primary Key 之外,这个表还有一个 Index,假设这个 Index 的 ID 为 1,则其数据为:

t10_i1_10_1 --> null
t10_i1_20_2 --> null
t10_i1_30_3 --> null

sql优化在TiDB-Server层完成,sql处理在TiKV层处理,根据TiKV和关系表的编码规则,一条查询语句可以可以确定表的key的范围[Start_Key, End_Key),确定这样一个区间,然后再TiKV层完成对条件的过滤,返回结果到TiDB Server层。

例子:
比如 Select count(*) from user where name="TiDB"; 这样一个语句,我们需要读取表中所有的数据,然后检查 Name 字段是否是 TiDB,如果是的话,则返回这一行。这样一个操作流程转换为 KV 操作流程:

  • 构造出 Key Range:一个表中所有的 RowID 都在 [0, MaxInt64) 这个范围内,那么我们用 0 和 MaxInt64 根据 Row 的 Key 编码规则,就能构造出一个 [StartKey, EndKey) 的左闭右开区间
  • 扫描 Key Range:根据上面构造出的 Key Range,读取 TiKV 中的数据
  • 过滤数据:对于读到的每一行数据,计算 name="TiDB" 这个表达式,如果为真,则向上返回这一行,否则丢弃这一行数据
  • 计算 Count:对符合要求的每一行,累计到 Count 值上面

这个方案肯定是可以 Work 的,但是并不能 Work 的很好,原因是显而易见的:

1.在扫描数据的时候,每一行都要通过 KV 操作同 TiKV 中读取出来,至少有一次 RPC 开销,如果需要扫描的数据很多,那么这个开销会非常大
2.并不是所有的行都有用,如果不满足条件,其实可以不读取出来
3.符合要求的行的值并没有什么意义,实际上这里只需要有几行数据这个信息就行

分布式 SQL 运算

如何避免上述缺陷也是显而易见的,首先我们需要将计算尽量靠近存储节点,以避免大量的 RPC 调用。其次,我们需要将 Filter 也下推到存储节点进行计算,这样只需要返回有效的行,避免无意义的网络传输。最后,我们可以将聚合函数、GroupBy 也下推到存储节点,进行预聚合,每个节点只需要返回一个 Count 值即可,再由 tidb-server 将 Count 值 Sum 起来。

这里有一个数据逐层返回的示意图:

5.3 TiDB SQL引擎的优化

5.3.1 执行器构建:最大程度下推计算

将计算下推到TiKV,避免无意义的网络传输。
比如: select * from age > 20 and age < 30,将过滤操作下推到TiKV去执行,那么TiDB Server直接拿到过滤后的结果。
详情参考文章 MPP and SMP in TiDB

5.3.2 关键算子分布式优化

将算子并行处理,分别并行获取两个表的数据,然后再做连接操作

6 TiDB的发展

6.1 HTAP发展的必然性

HTAP是OLAP(online transactional processing) 和 OLTP(online analytical processing)的结合体

6.2 TiSpark引入Spark 加强计算能力

6.3 TiDB引入列式副本TiFlash进行统一计算

  • 列式存储

  • TiFlash 只会作为 Raft Learner,并不会成为 Raft Leader / Follower
    这样的实现方式,缺点是 Learner 上的数据可能有一定延迟,优点是大大减少了引入 TiFlash 造成的额外数据复制开销。当数据同步到TiFlash的时候,会从行格式转为列格式

  • 数据查询和结果统计分开处理

6.4 TiDB在HTAP目前发展程度

6.5 HTAP下一步发展

6.6 数据库选型

数据库选型考虑的的因素

参考

关系模型到 Key-Value 模型的映射
LSM-Tree
MPP and SMP in TiDB


杜若
70 声望3 粉丝