TiDB 如何做到无限扩展和保证节点 id 唯一

【是否原创】否
【作者】 @seiang
【首发渠道链接】http://www.seiang.com/?p=1179

相信大家对TiDB已经很熟悉了,但是很多人觉得他是分布式数据库,自己的业务是使用mysql,基本使用不上这个技术,可能不会去了解他或不会去深入了解。最近一个月,基于实际业务的应用场景,从测试环境测试基础学习,到生产环境性能压测、高可用测试、故障测试等的学习,到今天TiDB终于完成了线上业务的承接使命,而这一切只是开始,而非终点。

经过这一个月的学习,发现TiDB这些原理其实不仅仅局限于分布式数据库这一块,很多技术都是通用的,所以在这里写一下分享一下学习TiDB的一些心得。当然还有更深层次的东西需要后续慢慢的打磨。

先说说为什么选择TiDB吧,一般来说在咱们的业务中都是使用的mysql,但是单机数据库容量和并发性能都有限,对于一些大容量或者高并发的场景我们会选择mycat去做,使用mycat的确解决了问题但是增加了开发难度,开发需要对每一个表都设置分表key,并且每个查询都得带入这个key的值,这样就增加了查询限制,如果不带key的值就得所有库表都得查询一次才行,效率极低,而且也无法满足AP的业务场景。随着核心业务的逐步从SQL Server迁移到MySQL,怎么解决这个问题呢?经过对开源数据库对比测试,最终选择了TiDB。

经过一个月左右的对TiDB的学习,总体感觉,学习成本其实相对来说还是蛮高的,但是不得不说的是,TiDB的官方文档及社区非常的棒,TiDB文档是我见过做得算是比较顶级的文档了,可以说不叫做文档,其实是一个文章知识库,对新手学习来说,还是比较方便的;

在TiDB学习前,对TiDB总是会有很多的疑问:

TiDB如何保证无限扩展?因为平时使用的大多都是mycat的技术,这种其实无限扩展是比较麻烦的

如何保证id唯一,分布式数据库往往会进行分片,在单机数据库中的自增id就不成立,TiDB是如何保证的呢?

TiDB是如何保证事务的呢?

TiDB数据库是如何存储的?为什么即适用于TP场景又适用于AP场景?

带着这些问题,先来看看TiDB的架构,下面是官方的给出的架构图:

TiDB其实是典型的计算分离的架构

TiDB Server :计算层,对外暴露协议的连接端口,负责管理客户端的连接,主要做的就是执行SQL解析以及优化,生成分布式执行计划,由于这里是计算层是没有状态的,所以是可以无限扩展。

PD Server :PD是整个集群的大脑,负责存储每个 TiKV 节点实时的数据分布情况和集群的整体拓扑结构,提供 TiDB Dashboard 管控界面,需要保持高可用。

TiKV: k-v存储引擎,在tikv内部,存储数据的基本单位是Region。

Tiflash :这个是用于列式的存储引擎,类似与Clickhouse

TiDB如何做到扩展?

首先我们来看看计算层: tidb-server,刚才说过在计算层中,是无状态的,所以就可以进行无限扩展,如果你的场景并发度很高或者数据库连接很多,可以考虑多扩展tidb-server。

然后我们来看看存储层,作为保存数据的系统,首先要决定的是数据的存储模型,也就是数据以什么样的形式保存下来。TiKV 的选择是 Key-Value 模型,并且提供有序遍历方法。在每个tikv上会划分出多个Region,这个也就是我们的基本存储单位;

任何持久化的存储引擎,数据终归要保存在磁盘上,TiKV 也不例外。但是 TiKV 没有选择直接向磁盘上写数据,而是把数据保存在 RocksDB 中,具体的数据落地由 RocksDB 负责。这个选择的原因是开发一个单机存储引擎工作量很大,特别是要做一个高性能的单机引擎,需要做各种细致的优化,而 RocksDB 是由 Facebook 开源的一个非常优秀的单机 KV 存储引擎,可以满足 TiKV 对单机引擎的各种要求。这里可以简单的认为 RocksDB 是一个单机的持久化 Key-Value Map。

TiKV 利用 Raft 来做数据复制,每个数据变更都会落地为一条 Raft 日志,通过 Raft 的日志复制功能,将数据安全可靠地同步到复制组的每一个节点中。不过在实际写入中,根据 Raft 的协议,只需要同步复制到多数节点,即可安全地认为数据写入成功。

image

总结一下,通过单机的 RocksDB,TiKV 可以将数据快速地存储在磁盘上;通过 Raft,将数据复制到多台机器上,以防单机失效。数据的写入是通过 Raft 这一层的接口写入,而不是直接写 RocksDB。通过实现 Raft,TiKV 变成了一个分布式的 Key-Value 存储,少数几台机器宕机也能通过原生的 Raft 协议自动把副本补全,可以做到对业务无感知。

所以对于TiDB来说无论是存储层还是计算层,我们都可以无限扩展。

TiDB如何保证节点id唯一?

在mysql中我们可以对于主键直接设置 AUTO_INCREMENT来达到自增列的效果,mysql是怎么做到自增的呢?

在MySQL5.7及之前的版本:InnoDB引擎的自增值,自增值保存在内存里,并没有持久化。每次重启后,第一次打开表的时候,都会去找自增值的最大值max(id),然后将max(id)+步长作为这个表当前的自增值。

在MySQL8.0版本:将自增值的变更记录在了redo log中,重启的时候依靠redo log恢复重启之前的值。

在TiDB中同样支持 AUTO_INCREMENT,不能保证严格递增,只能保证趋势递增,具体原理是:对于每一个自增列,都使用一个全局可见的键值对用于记录当前已分配的最大 ID。由于分布式环境下的节点通信存在一定开销,为了避免写请求放大的问题,每个 TiDB 节点在分配 ID 时,都申请一段 ID 作为缓存,用完之后再去取下一段,而不是每次分配都向存储节点申请。

TiDB还支持 AUTO_RANDOM,可以用于解决大批量写数据入 TiDB 时因含有整型自增主键列的表而产生的热点问题。因为region是有序的如果一段时间大量有序的数据产生有可能会在同一个region上,所以我们可以使用AUTO_RANDOM来将我们的主键数据打散。

好了,今天就先聊这些吧,后面会陆分享一些TiDB相关管理的内容,包括从TiDB生产集群部署到性能压测、高可用、运维等常见问题;

也欢迎关注作者的微信公众号:
image