TiDB 演进与架构分析
基本介绍
分布式的产生
随着数据量和计算量的增长,集中式系统已无法承载,此时分布式系统则就是数据爆发增长的刚需。分布式本质上也是进行数据和计算的分制,再结合多副本的机制满足了可用性。最终展现出分布式系统的价值。
纵观分布式的发展历史,Google在06年推出了大数据的三驾马车(GFS、BigTable、MapReduce)。其中GFS解决了分布式文件系统问题,BigTable解决了分布式KV存储问题。MapReduce则解决了在之前两者的基础上如何做分布式计算和分析的问题。
数据库现状
如今各种模型的数据库非常多, 而关系型模型在在线交易场景一直占据很大的权重(72%)。而作为关系型数据库就一定会具备事务,事务的本质就是并发控制单元(操作序列作为工作单位不可分割) 。这里也就涉及了常见的四大特性ACID
- A原子性:事务包含的整体操作是不可分割
- C一致性:事务的前后,所有的数据都保持一致的状态,不可违反数据的一致性检测
- I隔离性:事务之间相互影响的程度,多个事务访问同一资源的行为在不同的隔离性下不同
- D持久性:事务完成后将数据变更的记录存储下来,包括了数据本身和多副本的备份
所以有没有既是关系型数据库模型又能发挥分布式系统的机制的数据呢,这就是如今的NewSQL数据库也就是原生分布式关系型数据库(分布式+SQL+事务)
NewSQL: 是一类关系数据库,它寻求为在线交易处理(OLTP)工作提供NoSQL系统的可扩展性,同时维护传统数据库系统的ACID保证
主角亮相
TiDB作为NewSQL分类的代表是一款PingCAP公司设计,兼容MySQL协议,底层采用RocksDB作为单机存储,通过Raft协议保障一致性高可用的分布式关系型数据库,同时支持在线事务处理,适合高可用、强一致要求较高、数据规模较大等各种应用场景。
NewSQL设计目标
在我们熟悉的面向业务场景中,可以总结出这么NewSQL需要支持以下几种能力:
- 扩展性
- 强一致高可用性
- 标准SQL支持ACID事务
- 云原生
- 混合数据服务(HTAP)
- 解决数据容量的前提下,OLTP & OLAP的数据服务融合
- 行列混合
- 更彻底的资源隔离
- 需要开放的、兼容数据库与大数据生态
- 统一数据查询服务
- 兼容主流生态与协议
计算存储分离
计算与存储强绑定意味着两种资源总有一种是浪费的,另外在对服务器选型时,计算型or存储型也会增加选择的复杂度和通用性。而在云计算的背景下,弹性的颗粒度是机器,并不能做到真正的资源弹性。
因此TiDB在设计之初就将弹性作为架构核心的主要考量点,所以选择了更为主流的面向未来的计算与存储分离的架构。
在架构逻辑上,TiDB主要分为三层:
- 支持标准SQL的计算引擎:TiDB-Server
- 本身并不存储数据,只进行计算
- 分布式存储引擎:TiKV
- 元信息管理与调度的引擎:Placement Driver(PD)
- 集群的元信息管理,包括分片分分布,拓扑结构等
- 分布式事务ID的分配
- 调度中心,每个TiKV节点会在一个周期内定向发送元信息给PD,包括所在节点的分数量、Leader数量等
系统构建
构建分布式存储系统
对于分布式存储引擎,设计的目标总结为
- 更细粒度的弹性扩缩容
- 支持高并发读写
- 数据准确,不错的容错性(不丢不错)
- 多副本保障一致性以及高可用
- 支持分布式事务
而TiKV是如何实现保证以上这些重要的特性和目标的呢
数据结构
数据结构是计算机存储和祖师数据的方式,是数据库的核心基础技术,首先选择了Key-Value数据模型
而数据结构在传统的OLTP得系统里,写入是最昂贵的成本。在传统的MySQL中也是最常见的索引结构是B-Tree。
B-Tree在一次写入需要写两次数据,一次是预写日志(WAL),一次是写入树本身。而B-Tree是严格平衡的数据结构,它的设计本身就是对读友好。数据的写入触发的B-Tree的分裂和平衡的成本是非常高的。而在传统的主从架构里,集群的写入容量都是无法扩展的,集群的写入容量完全由主库的机器配置决定。在扩容上只能通过非常昂贵的集群拆分(分库)。
在分布式的场景,对于写入的容量需求会越来越大,因此TiKV节点选择了LSM-Tree结构作为RocksDB引擎的基础。而LSM结构本质上是一个用空间置换写入延迟,用顺序写替换随机写入的数据结构。
RocksDB引擎是一款非常成熟的LSM-tree存储引擎,它本身还具有其他重要特性,比如支持批量写入,无锁快照读(在数据副本迁移过程中会有用)
数据副本选择
数据的冗余决定了系统的可用性,在强一致的场景下,最终选择了强一致的复制算法,在一致性算法中,最成功的就属Raft和Paxos算法。而Raft是一种用于替换Paxos的共识算法,相比Paxos,Raft的目标是提供更加清晰的逻辑分工使用算法本身能被更好的理解,也就是说他更容易理解以及工程化实现,同时他的安全性也更高,,并提供了一些额外的特性。
因此就可以基于RocksDB构建一个多副本的集群
如何实现扩展
数据分片是分布式数据里面的关键设计,如果要实现扩展就是需要对数据做分片。而分片则需要注意是预先分片(静态分片)还是自动分片(动态分片)。
传统的分库分表或者分区的方案都是预先分片,这种分片只解决表容量的问题。因此我们需要自动的分片算法,另外分片总是需要一个维度和算法。
常见的分片算法有哈希、范围、列举。在TiKV系统中采用了范围Range算法。主要考虑到
- Range分片可以更加高效的扫描数据记录,这种在OLTP业务中是非常常见的
- 范围分片可以简单的实现自动完成分裂与合并
- 弹性优先,分片需要可以自由调度
在TiKV中有个重要实现就是Region来做数据的分散。在系统中通过范围的方式将整个K-V空间分成若干段。每一段也是一系列连续的Key-Value。而这每一段就是Region,实际上就是一个分片的概念。每个Region中保存的数据不超过一定的大小(默认96M,可配置)。
如何分离与调度
当Region的大小超过了一定的限制(默认144MB)。TiKV会将他分裂成两个或者更多个的Region以保证各个Regon的大小是大致接近的,这样也有利于PD进行调度决策。同样的,当某个Region有大量的删除请求导致Region过小时,TiKV会将两个小的相邻的Region合并为一个。也就是常说的自动Split 和自动Merge。
为了保证客户端能够访问所需要的数据,TiKV系统中存在组件提供了记录Region在节点上面的分布情况。换句话说,通过一个key就能查询到这个key所在哪个Region以及这个Region在哪个存储节点上。而对于Region通过Raft又进行了三副本的复制
分布式事务
TiKV的MVCC实现是通过在Key后面添加版本号来实现的。TiKV支持分布式事务后,用户可以一次性写入多个kv而不用关心这些kv是否在同一个Region上,是否在同一个物理节点上。
TiKV通过两阶段提交来保证这一批次的读写请求的ACID约束。分布式事务普遍采用了两阶段提交的方式,而两阶段提交往往需要事务管理器(GTM),而事务管理器也会成为整个集群的性能瓶颈。
因此TiKV采取了一个去中心化的两阶段提交:在每个TiKV存储节点上会单独分配一个存储锁信息的地方,TiKV的锁基于列簇,因此这个锁信息我们称为CF Lock。
PD来保证所有的TiKV节点的顺序的一致性,TiDB的默认隔离级别的是SI,而SI和RR可重复度的隔离级别非常近,同时TiDB也支持RC(提交读)。
构建分布式SQL引擎
基于KV实现逻辑表
TiKV对于每个表分配了一个TableID,每个索引也会分配一个IndexID,每一行数据也会分配RowID。一张表如果有证书的Primary Key,则pk为RowID。因此可以简单的理解为RowID+indexID 作为kv中的Key
Value则可以看成所有的列按照等位偏移的方式进行的connect进行连接
在数据查询时通过等位偏移量对Value进行反解析,然后再对应于Schema的元信息进行的列信息映射。
在TiKV中,二级索引也是全局有序的Key-Value map
它的Key就是索引的列信息,Value则是原表的Primary Key的主键。通过主键在原表的Key-Value map进行一次扫描找到Value,按照等位偏移量进行列解析。
SQL存储过程
SQL是一个非过程的声明性语言,只描述结果不解读过程。因此SQL引擎最重要的就是优化器。
TiDB采用了兼容MySQL的策略,也就是说会按照MySQL的语法进行解析以及语义解析。中间过程就包含了权限控制以及对表队列的合理性校验。
AST抽象语法树主要试讲SQL从文本解析成一个结构化的数据
SQL逻辑优化部分会将各种SQL等价改写以及优化,比如说将子查询改成表关联,也可以进行一些不必要的信息的裁剪,如列裁剪、分区裁剪、left join裁剪等
物理优化,优化器会基于统计信息与成本进行生产执行计划。这一步十分关键也是整个SQL优化中最重要,优化空间最大的部分。
执行殷勤会根据优化器定下来的执行路径进行相应的得数据寻址、数据的计算
分布式SQL引擎的优化策略
简单来说就是让数据表咋不同的存储节点的分片进行预计算完成本地的数据过滤以及统计,然后再将本地存储节点的临时结果、中间结果上报至Server进行集中计算
表DDL构建
首先在TiDB中没有分表的概念,所以整体DDL的完成过程是非常快的
再者,TiDB中会把DDL过程分成Public、Delete-only、Write-only 等几个状态,每个状态在多节点之间同步和一致,最终完成完整的DDL。
TiDB-Server
最上面是MySQL协议层,按照MyDQL的协议进行编码解析,下面是SQL的核心层也是TiDB-Server的最主要的部分会进行SQL的Parser分析、逻辑优化、物理优化、统计信息收集等
最后再按照数据收集的方式交给执行器进行执行
HTAP的应用分析
TiDB是一款支撑HTAP数据服务的数据库
HTAP的必然性
HTAP需要同时支持OLTP和OLAP场景,基于创新的计算存储架构,在同一份数据上保证了事务的同时又支持实时分析,省去了费时的ETL过程
分布式技术的发展逐步解决了数据容量爆炸的问题,而分布式关系型数据库同时满足了OLTP的需求也解决了数据容量的问题,在此基础上,很多传统的OLAP技术可以在此架构上进行再融合,实现了更大树容量的混合数据库(HTAP)
TiDB应用HTAP
最早的TiDB是为了解决业务分录分表的问题。早期的定位也是OLTP系统。而之后提供的解决方案将多众多业务系统实时同步至一个TiDB集群,渐渐地也被应用在数据中台,主要源于以下特性:
- TiDB支持海量存储,允许多数据源汇聚,数据实时同步
- 支持标准的SQL,多边关联快速出结果
- 透明多业务模块、支持分表聚合后可以任务维度查询
- 最大下推策略以及并行hash join等算子决定了TiDB在表关联上的优势
对于TiDB面向OLAP用户场景需求,通过引入大数据生态的Spark,让Spark识别TiKV的数据格式、统计信息、索引、执行器最终构建了一个能跑在TiKV上的Spark的计算引擎(封装成TiSpark)
进而实现了一个分布式的技术的平台,在面向大数据量的报表以及重要级的AdHhoc里提供了可行的方案
但是Spark只能提供低并发的重量级查询,在很多中小规模的轻量AP查询,也需要高兵大,相对低延迟技术的能力,在这种需求场景下,Spark的技术模型过于厚重,资源消耗也大。这个时候OLTP的查询和TiSpark公用的一套底层存储系统显然是不太合理的,面向软件层面去优化OLAP和OLTP的资源隔离是很难的
从数据库资源隔离的角度看,依次是数据库软件层、副本调度、容器、虚拟机、物理机等。因此越接近物理机的隔离性会更好(物理隔离是最好的资源隔离)。因此列存天然对OLAP的查询友好,因此借鉴传统的主从架构分离的思想,可以将这个副本放到一个列式引擎上。
如何进行行列数据同步
因为要强调数据的时效性,因此采用了复制协议更底层更高效的raft复制
通过对raft进行进一步的改造,将一个副本的raft设置为只负责同步没有投票权的角色(Learner)
这样既保证了Raft Group的写入效率又保证了列存副本的极低延迟,同时还可以避免复制组脑裂问题