TiDB:重新定义下一代关系型数据库

前言

从宏观的角度来看,计算机科学的基础模型早已被定义完整。从数学思维发散嵌入硬件产品,一些晦涩难懂的概念经过层层的封装,对于大多数人,甚至大部分从业人员来说都是黑盒。

随着硬件技术的进步,底层技术的完善,加上规则的制定,包括我们共同认同的协议和标准。计算机科学开始走出实验室,但有人的地方就有江湖,尤其是互联网的爆发,所有人都融入了网络的环境中。

机器是冰冷的,但人是多变的,或许计算机革命真的带来了所谓的便利性,但人总想着让它更便利,从而任何一个行业都想用计算机网络来解决痛点。最终导致网络技术另外一个维度的难点变成了如何应对复杂多变的业务,如何承受突如其来的并发量和如何处理堆积如山的数据。


一:数据库面临的挑战:

上文所说的几个难点都和数据库技术息息相关。

当我们做计算的时候要求相关的数据建立某种关系。一个简单的线性结构并不能满足我们的需求,需要一个数据模型存储这种关系。这些数据不仅要参与结算,还要落地,关系型数据库应运而生。尤其是后来面向对象思想的发展,以及越来越多复杂业务数据的存储要求,关系型数据库更变得不可或缺。

但从硬件方面来说,却存在一些驳论。摩尔定律认为集成电路每18个月集成度会成倍增长,计算机的产品性能会提升一倍。安迪-比尔定律认为硬件提升的性能会被软件的升级所吃掉(搞来搞去,最终好像结果没有多少变化)。具体到数据库,最初一块磁盘可以搞定,一块软盘可以搞定,后来一块硬盘可以搞定,100块硬盘可以搞定。最后不好意思,某小哥的数据库需要一座山才可以搞定。

还是不行,量大了,但是速度变慢了。贪婪的人类还喜欢瞎凑热闹,某一天半夜12点,某爸爸公司的数据层1秒钟处理了4200万次请求 …

仅从软件层面来说,我们习惯和喜欢通过解耦的方式去处理痛点。抽象和分层是很好的实践。所有程序的核心思维都是对真实世界的抽象。我们把程序分成三层也好,四层也罢,最终的痛点都卡在了数据层。针对业务层,控制层我们可以通过各种各样的复杂的简单的有用的没用的技术去实现。到了数据层,不好意思,你得等一会。为啥?前几层都可以在内存中操作,数据层是硬盘,哪怕是SSD,在上亿的数据面前也无能为力,何况还要求对数据层的操作遵从各种事务原则(从另外一个层面上来说,硬件技术还有太多提升的空间)。

没关系,可以考虑分库分表,可以整合卡车数据库,可以整合NoSQL。但复杂的业务sharding规则,以及后续的动态水平扩展,Cache数据库的同步和持久化,即时业务NoSQL的支持都是后续不可避免的问题。

有没有一种数据库,遵循jdbc原则,学习成本低,强关系型,强一致性,不用担心主从配置,不用考虑分库分表,还可以无缝动态扩展 …

做梦呢,有也不让你免费用啊。

直到有一天我看到了TiDB…


TiDB的解决方案

官方给出三篇文章了解TiDB技术内幕:

- 说存储

- 说计算

- 谈调度

下面的内容仅是我对三篇文章自己的总结,如果你认真看完了上面三篇文章,本节内容完全没必要读,因为大部分是摘抄,并且很多理解也仅限于我有限的理解。

概述

TiDB是传统的数据库中间件,数据库分库分表等sharding方案非常优雅而理想的替代和解决方案。

核心特征:水平扩展和高可用

说白了:就是上层封装了mysql协议,让程序员完全无感知,和使用mysql基本没有异同。下层使用KV数据库,保证了数据的扩展性和高可用。

而TiDB如何做到这一点:

以上是官方的一张TiDB整体架构图片。

可以看出TiDB集群分为三个组件:

TiKV Server:

我们暂时不关注数据之间的关系性,也就是说暂时先忘记SQL中table的概念。先从最底层了解一下数据的存储模型。

对于TiKV我们只需要记住两点:

1:这是一个巨大的Map,也就是存储的是Key-Value键值对。

2:这个Map中的Key-Valuepair按照key的二进制顺序排序,也就是我们可以查找到某一个key的位置,然后不断的调用next方法递增的获取数据。

TiKV使用了Facebook开源的存储引擎RocksDB。在此可以将RocksDB当做一个简单的单机Key-ValueMap。

现在我们已经为数据找到了一个高效可靠的本地存储方法。随后解决的问题是怎样让此方案高可用。在此,TiKV使用Raft协议。Raft是一个一致性算法。如果你原来配置过redis的高可用集群,肯定了解Paxos算法,Raft和Paxos等价。

Raft作为一致性协议,提供以下几个重要的功能:

1:leader选举。

2:成员变更。

3:日志复制。

TiKV利用Raft做数据复制。每个数据变更都会落地为一条Raft日志。通过Raft日志的复制功能,可以将数据安全可靠的同步到Group的多数节点中。

至此,我们简单总结一下:

通过单机的RocksDB,我们可以将数据快速的存储在磁盘上,通过Raftkey将数据复制到多台机器上,防止单机失效。数据的写入通过Raft这一层的接口写入,而不是直接写RocksDB,通过实现Raft,我们拥有了一个分布式的KV,现在再也不用担心某台数据库挂掉了。

下面我们聊一个非常重要的概念:Region

对于一个KV系统,将数据分散在多台机器上有两种比较经典的方案:一种按照key做Hash,根据Hash值选择对应的存储节点;另一种是分Range(范围)。TiKV选择了第二种。将整个KV空间分成很多段。每一段是一系列连续的key,我们将每一段叫做一个Region,并且会尽量保持每个Region中保存的数据不超过一定的大小。

也就是说我们将数据根据一系列连续的key将分段,每一段根据Raft协议复制成三份组成Raft Group分布式存储在不同的节点中,这样即使任何一台节点出现异常,我们都可以保证数据的完整性。

同时为了上层客户端能够访问所需要的数据,系统中也会有一个组件记录Region在节点上的分布情况,也就是通过任意一个Key就能查到这个key在哪个Region中,以及这个Region目前在哪个节点上,这个组件就是后面我们讲到的PD。

很多数据库都会实现多版本控制,TiKV的实现是比较常见的在每一个Key后面添加一个version字段。从而事务来说,TiKV采用Percolator模型,但核心只提一点:使用了乐观锁的机制实现。

TiDB Server:

TiDB Server解决的问题就是关系模型和KV模型的映射。

在这里我们将关系模型简单理解为Table的SQL语句,解决的问题就是如何在KV结构上运行SQL语句。

对于一个Table来说,需要存储的数据包括三部分:

1:表的元信息 2:Table中的Row 3:索引数据

现在我们使用TiKV已经有了一个全局有序的分布式KV引擎。全局有序这一点很重要,可以帮助我们解决很多问题。比如对于快速获取一行数据,我们利用TiKV提供的Seek方法就可以快速的定义到这一行数据所在的位置。再比如说对于全表扫描的需求,如果能够映射为一个Key的Range,从StartKey扫描到EndKey,那么久可以简单的通过这种方式获取全表数据。操作索引数据也是类似的思路。TiDB的做法是这样的:

TiDB为每张表分配一个TableID,每一个索引都会分配一个IndexID,每一行分配一个RowID(如果表有整数型的PrimaryKey,那么会用Parimary Key的值当做RowID,从这方面来说,如果我们从零开始使用TiDB,主键尽量使用整型)。其中TableID在整个集群内唯一,IndexID/RowID在表内唯一。这些ID都是长整型,并且每行数据都按照一定的规则进行编码。

也就是说,一个Table内部所有的Row都有相同的前缀,一个Index的数据也都相同的前缀。这样具体相同前缀的数据,在TiKV的Key空间内,是排列在一起的。同时只要我们小心的设计后缀部分的编码方案,就可以将Row或者Index数据有序的保存在TiKV中。

这样就极大的方便了我们后续对数据的查询操作。

下面所做的工作就是SQL运算了,也就是说TiDB Server负责接收sql请求,然后对sql语句做语法解析,查询计划制定和优化,最终将SQL语句映射为KV的查询。

PD:

总结上面文章中提到的一些信息:TiKV集群是TiDB数据库的分布式存储引擎,但同样也会面临很多问题,包括Region具体如何负载均衡,如何跨机房容灾,TiKV集群数据的有效迁移等。

这样就需要一个掌握全局信息,可以对全局进行调度,并且可以配置的组件。因此我们需要一个中心节点,来对系统进行整体状况的把控和调整,这就是PD模块。

调度的基本操作:

上述需求看起来很复杂,但是整理下来无非就三件事:

增加一个Replica

删除一个Replica

将Leader角色在一个Raft Group的不同Replica之间transfer

刚好Raft协议能够满足这三个需求,通过AddReplica,RemoveReplice。TransgerLeader这个三个命令,可以支撑上述三个基本操作。

信息收集:

每个TiKV节点都会定期向PD汇报节点的整体信息:

TiKV和PD之间存在心跳包。一方面PD通过心跳包检测每个TiKV节点是否存活,以及是否有新加入的TiKV节点。另一方面,心跳包中也会携带这个节点的状态信息。

每个Raft Group的Leader会定期向PD汇报信息。

PD不断的通过这两类心跳信息收集这个集群的信息,再以这些信息作为决策的依据。

调度的策略:

PD收集了这些信息后,还需要一些策略来指定具体的调度计划:

一个Region的Replica数量正确。

一个Raft Group中的多个Replica不在同一个位置。

副本在Store(TiKV节点)之间的分布均匀分配。

Leader数量在Store之间均匀分配。

每个Store的存储空间占用大致相等。

控制调度速度,避免影响在线服务。

支持手动下线节点。


三:TiDB实践

TiDB集群部署简单,如果仅仅是测试功能,完全可以使用Docker部署一个简单的TiDB集群

官方推荐使用Ansible部署方案

我司使用TiDB官方最低配置要求进行测试,目前已插入数据1亿2千万+,持续运行良好。这用mysql中是不可想象的,依据单表千万级别考虑分库分表的话如果使用mysql集群估计sharding规则就已经复杂到一定的级别了。这对运维人员和开发人员都是不小的挑战,而TiDB完全避免了这些问题。

针对数据进行操作,简单的查询都在毫秒级别,复杂的查询根据业务情况在where条件加索引,也可以正常满足需求。只不过需要全表扫描的情况下(比如count操作)比较耗时。在业务中进行sql优化根据TiDB的存储规则尽量避免全表扫描。


总结:

根据自己的业务体量,如果数据量在很长一段时间内都不会达到千万级别,完全没必要考虑TiDB。

如果现在的数据量单数据库已无法支持,已经开始考虑分库分表,此时不妨了解一下TiDB。

如果从一开始就预见了数据量的规模,5000万+的数据量可以考虑TiDB。

并且现在TiDB针对复杂的OLAP还推出了解决方案TiSpark,再加上拥有完善的监控界面这样的小亮点,有理由相信TiDB会成为数据库领域的一个颠覆者。

快掏出你的大手机扫我

快掏出你的大手机扫我