28

直播回顾 | 困扰多年的分库分表问题终于解决了

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=Mzg4NjA4NTAzNQ%3D%3D&%3Bmid=2247485927&%3Bidx=1&%3Bsn=0ee70d99a68083627621ec001fbbccbe
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

腾讯云数据库国产数据库专题线上技术沙龙正在火热进行中,3月17日郑寒的分享已经结束,没来得及参与的小伙伴不用担心,以下就是直播的视频和文字回顾。

关注“腾讯云数据库”公众号,回复“0317郑寒”,即可下载直播分享PPT。

话不多说,我们正式进入今天的分享。今天分享的主题是“亿级流量场景下的平滑扩容:TDSQL水平拓展方案实践”。

今天的分享我会主要包含这四部分:

第一部分首先介绍水平扩容的背景,主要介绍为什么要水平扩容,主要跟垂直扩容进行对比,以及讲一下一般我们水平扩容会碰到的问题。

第二部分会简单介绍TDSQL如何做水平扩容,让大家有一个直观的印象。

第三部分会详细介绍TDSQL水平扩容背后的设计原理,主要会跟第一部分进行对应,看一下TDSQL如何解决一般水平扩容碰到的问题。

第四部分会介绍实践中的案例。

一、数据库水平扩容的背景和挑战

首先我们看水平扩容的背景。扩容的原因其实非常直观,一般来说主要是随着业务的访问量,或者是需要的规模扩大,而现有的容量或者性能满足不了业务的需求,主要表现在TPS、QPS不够或者时延超过了业务的容忍范围,或者是现有的容量不能满足要求了,后者主要是指磁盘或者网络带宽。一般碰到这种问题,我们就要扩容。扩容来说,其实比较常见的就是两种方式,一种是垂直扩容,一种是水平扩容。这两种有不同的特点,优缺点其实也非常明显。

1.1 水平扩容 VS 垂直扩容

首先我们看一下垂直扩容。垂直扩容,主要是提高机器的配置,或者提高实例的配置。因为,我们知道,大家在云上购买一个数据库或者购买一个实例,其实是按需分配的,就是说对用户而言,可能当前的业务量不大,只需要两个CPU或者是几G的内存;而随着业务的增长,他可能需要对这个实例进行扩容,那么他可能当前就需要20个CPU,或者是40G的内存。

这个时候,在云上我们是可以通过对资源的控制来动态地调整,让它满足业务的需求——就是说可以在同一台机器上动态增加CPU。这个扩容的极限就是——当整台机器的CPU和内存都给它,如果发现还不够的话,就需要准备更好的机器来进行扩容。这个在MySQL里面可以通过主备切换:通过先选好一台备机,然后进行数据同步;等数据同步完成以后,进行主备切换,这样就能利用到现在比较好的那台机器。

大家可以看到,这整个过程当中,对业务来说基本上没有什么影响——进行主备切换,如果换IP的话,其实是通过前端的或者VIP的方式,对业务来说基本上没有什么影响。那么它一个最大的不好的地方就是,它依赖于单机资源:你可以给它提供一个更好的机器,从而满足一定量的要求。而随着业务更加快速的发展,你会发现现在能提供的最好的机器,可能还是满足不了,相当于扩不下去了。因此,垂直扩容最大的缺点就是,它依赖于单机的资源。

zq2yeaU.jpg!web

1.2 水平扩容

跟垂直扩容对比,另外一种方式我们叫水平扩容。水平扩容最大的优点是解决了垂直扩容的问题——理论上水平扩容可以进行无限扩容,它可以通过增加机器的方式来动态适应业务的需求。

水平扩容和垂直扩容相比,它可以解决垂直扩容的问题,但是会引入一些其他的问题。因为水平扩容比垂直扩容更加复杂,下面我们分析下可能遇见的问题,以及后面我们会介绍TDSQL的解决方案:

uI3uUzY.jpg!web

首先,在垂直扩容里面,系统经过扩容以后,其实数据总体来说还是存在一个节点,一主多备架构中,备机上也存储着所有数据。而水平扩容过程中数据会进行拆分,面临的第一个问题是,数据如何进行拆分?因为如果拆分不好,当出现热点数据时,可能结果就是,即使已经把数据拆分成很多份了,但是存储热点数据的单独节点会成为性能瓶颈。

第二点,在整个水平扩容过程中,会涉及到数据的搬迁、路由的改变。那么整个过程中能否做到对业务没有感知?或者是它对业务的侵入性大概有多少?

第三,在整个扩过程中,因为刚才有这么多步骤,如果其中一步失败了,如何能够进行回滚?同时,在整个扩容过程中,如何能保证切换过程中数据高一致性?

再者,在扩容以后,由于数据拆分到了各个节点,如何能保证扩容后的性能?因为理论上来说,我们是希望我随着机器的增加,性能也能做到线性提升,这是理想的状态。实际上在整个水平扩容的过程中,不同的架构或者不同的方式,对性能影响是比较大的。有时候会发现,可能扩容了很多,机器已经增加了,但是性能却很难做到线性扩展。

同样的,当数据已经拆分成多份,我们如何继续保证数据库分布式的特性?在单机架构下,数据存储一份,类似MySQL支持本地做到原子性——可以保证在一个事物中数据要么全部成功,要么全部失败。在分布式架构里,原子性则只能保证在单点里面数据是一致性的。因此,从全局来说,由于数据现在跨节点了,那么在跨节点过程中怎么保证全局的一致性,怎么保证在多个节点上数据要么全部写成功,要么全部回滚?这个就会涉及到分布式事务。

所以大家可以看到,水平扩容的优点很明显,它解决了垂直扩容机器的限制。但是它更复杂,引入了更多的问题。接下来大家带着这些问题,下面我会介绍TDSQL如何进行水平扩容,它又是如何解决刚才说的这些问题的。

二、TDSQL水平扩容实践

2.1 TDSQL架构

首先我们看一下TDSQL的架构。TDSQL简单来说包含几部分:

UBbQJbe.jpg!web

第一部分是SQL引擎层:主要是作为接入端,屏蔽整个TDSQL后端的数据存储细节。对业务来说,业务访问的是SQL引擎层。

接下来是由多个SET组成的数据存储层:分布式数据库中,数据存储在各个节点上,每个SET我们当做一个数据单元。它可以是一主两备或者一主多备,这个根据业务需要来部署。有些业务场景对数据安全性要求很高,可以一主三备或者一主四备都可以。这个是数据存储。

还有一个是Scheduler模块,主要负责整个系统集群的监控、控制。在系统进行扩容或者主备切换时,Scheduler模块相当于是整个系统的大脑一样的控制模块。对业务来说其实只关注SQL引擎层,不需要关注Scheduler,不需要关注数据是怎么跨节点,怎么分成多少个节点等,这些对业务来说是无感知的。

Rbauym7.jpg!web

2.2 TDSQL水平扩容过程

整个扩容流程大家可以看一下:一开始数据都放在一个Set上,也就是在一个节点里面。那么扩容其实就是会把数据扩容到——这里面有256个Set,会扩容到256台机器上。整个扩容大家可以看到有几个要点:

一开始虽然数据是在一个节点上,在一台机器上,但是其实数据已经进行了拆分,图示的这个例子来说是已经拆分成了256份。

水平扩容,简单来说就是把这些分片迁移到其他的Set上,也就是其他的节点机器上,这样就可以增加机器来为提供系统性能。

总结起来就是说,数据一开始已经切分好了,扩容过程相当于把分片迁到新的节点,整个扩容过程中,节点数是增加的,可以从1扩到2扩到3,甚至扩到最后可以到256,但是分片数是不变的。一开始256个分片在一个节点上,扩成两个节点的话,有可能是每128个分片在一个节点上;扩到最后,可以扩到256个节点上,数据在256台机器,每台机器负责其中的一个分片。因此整个扩容简单来说就是搬迁分片。具体细节我们后面会讲到。

在私有云或者是公有云上,对整个扩容TDSQL提供了一个统一的前台页面,用户在使用的过程中非常方便。

rQ367nB.jpg!web

我们看一下这个例子。现在这个案例中有两个Set,也就是两个节点,每一个节点负责一部分的路由,第一个节点负责0-31,另一个名字是3,负责的路由信息是32-63。现在是两个节点,如果要进行扩容,在前台页面上我们会有一个“添加Set”的按纽,点一下“添加Set”,就会弹出一个对话框,里面默认会自动选择之前的一个配置,用户可以自己自定义,包括现在这个Set,需要多少资源以及内存、磁盘的分配等。

uEbIZ3A.jpg!web

此外,因为扩容要进行路由切换,我们可以手动选择一个时间,可以自动切换,也可以由业务判断业务的实际情况,人工操作路由的切换。这些都可以根据业务的需要进行设置。

第一步创建好以后,刚才说大脑模块会负责分配各种资源,以及初始化,并进行数据同步的整个逻辑。最后,大家会看到,本来第一个节点——原来是两个节点,现在已经变成三个节点了。扩容之前,第一个节点负责是0-31,现在它只负责0-15,另外一部分路由由新的节点来负责。所以整个过程,大家可以看到,通过网页上点一下就可以快速地从两个节点添加到三个节点——我们还可以继续添加Set,继续根据业务的需要进行一键扩容。

bUF7biV.jpg!web

三、TDSQL水平扩容背后的设计原理

刚才主要是介绍TDSQL的核心架构,以及水平扩容的特性和前台操作,(帮助)大家建立直观的印象。

第三章,我会详细介绍一下TDSQL水平扩容背后的设计原理,主要是看一下第一章提到的水平扩容会遇到的一些问题,我们是如何来解决这些问题的。这些问题是不管在哪个系统做水平扩容,都需要解决的。

3.1 设计原理:分区键选择如何兼顾兼容性与性能

MRj2Mj2.jpg!web

首先我们刚才提到,水平扩容第一个问题是数据如何进行拆分。因为数据拆分是第一步,这个会影响到后续整个使用过程。对TDSQL来说,数据拆分的逻辑放到一个创建表的语法里面。需要业务去指定 shardkey“等于某个字段”——业务在设计表结构时需要选择一个字段作为分区键,这样的话TDSQL会根据这个分区键做数据的拆分,而访问的话会根据分区键做数据的聚合。我们是希望业务在设计表结构的时候能够参与进来,指定一个字段作为shardkey。这样一来,兼容性与性能都能做到很好的平衡。

其实我们也可以做到用户创建表的时候不指定shardkey,由我们底层这边随机选择一个键做数据的拆分,但这个会影响后续的使用效率,比如不能特别好地发挥分布式数据库的使用性能。我们认为,业务层如果在设计表结构时能有少量参与的话,可以带来非常大的性能优势,让兼容性和性能得到平衡。除此之外,如果由业务来选择shardkey——分区键,在业务设计表结构的时候,我们可以看到多个表,可以选择相关的那一列作为shardkey,这样可以保证数据拆分时,相关的数据是放在同一个节点上的,这样可以避免很多分布式情况下的跨节点的数据交互。

我们在创建表的时候,分区表是我们最常用的,它把数据拆分到各个节点上。此外,其实我们提供了另外两种——总共会提供三种类型的表,背后的主要思考是为了性能,就是说通过将global表这类数据是全量在各个节点上的表——一开始大家会看到,数据全量在各个节点上,就相当于是没有分布式的特性,没有水平拆分的特性,但其实这种表,我们一般会用在数据量比较小、改动比较少的一些配置表中,通过数据的冗余来保证后续访问,特别是在操作的时候能够尽量避免跨节点的数据交互。其他方面,shardkey来说,我们会根据user做一个Hash,这个好处是我们的数据会比较均衡地分布在各个节点上,来保证数据不会有热点。

3.2设计原理:扩容中的高可用和高可靠性

yyU7juE.jpg!web

刚才也提到,因为整个扩容过程的流程会比较复杂,那么整个扩容过程能否保证高可用或者高可靠性,以及对业务的感知是怎么样的,TDSQL是怎么做的呢?

  • 数据同步

第一步是数据同步阶段。假设我们现在有两个Set,然后我们发现其中一个SET现在磁盘容量已经比较危险了,比如可能达到80%以上了,这个时候要对它进行扩容,我们首先会新建一个实例,通过拷贝镜像,新建实例,新建同步关系。建立同步的过程对业务无感知,而这个过程是实时的同步。

  • 据校验

第二阶段,则是持续地追平数据,同时持续地进行数据校验。这个过程可能会持续一段时间,对于两个同步之间的延时差无限接近时——比如我们定一个5秒的阈值,当我们发现已经追到5秒之内时,这个时候我们会进入第三个阶段——路由更新阶段。

  • 路由更新

路由更新阶段当中,首先我们会冻结写请求,这个时候如果业务有写过来的话,我们会拒掉,让业务过两秒钟再重试,这个时候对业务其实是有秒级的影响。但是这个时间会非常短,冻结写请求之后,第三个实例同步的时候很快就会发现数据全部追上来,并且校验也没问题,这个时候我们会修改路由,同时进行相关原子操作,在底层做到存储层分区屏蔽,这样就能保证SQL接入层在假如路由来不及更新的时数据也不会写错。因为底层做了改变,分区已经屏蔽了。这样就可以保证数据的一致性。路由一旦更新好以后,第三个SET就可以接收用户的请求,这个时候大家可以发现,第一个SET和第三个SET因为建立了同步,所以它们两个是拥有全量数据的。

  • 删除冗余数据

最后一步则需要把这些冗余数据删掉。删冗余数据用的是延迟删除,保证删除过程中可以慢慢删,也不会造成比较大的IO波动,影响现网的业务。整个删除过程中,我们做了分区屏蔽,同时会在SQL引擎层会做SQL的改写,来保证当我们在底层虽然有冗余数据,但用户来查的时候即使是一个全扫描,我们也能保证不会多一些数据。

可以看到整个扩容流程,数据同步,还有校验和删除冗余这几个阶段,时间耗费相对来说会比较长,因为要建同步的话,如果数据量比较大,整个拷贝镜像或者是追binlog这段时间相对比较长。但是这几个阶段对业务其实没有任何影响,业务根本就没感知到现在新加了一个同步关系。那么假如在建立同步关系时发现有问题,或者新建备机时出问题了,也完全可以再换一个备机,或者是经过重试,这个对业务没有影响。路由更新阶段,理论上对业务写请求难以避免会造成秒级的影响,但我们会将这个影响时间窗口期控制在非常短,因为本身冻结写请求是需要保证同步已经在5秒之内这样一个比较小的阈值,同步到这个阶段以后,我们才能发起路由更新操作。同时,我们对存储层做了分区屏蔽来保证多个模块之间,如果有更新不同时也不会有数据错乱的问题。这是一个我们如何保证扩容中的高可用跟高可靠性的,整个扩容对业务影响非常小的原理过程。

3.3设计原理:分布式事务

QvQVzey.jpg!web

刚才讲的是扩容阶段大概的流程,以及TDSQL是如何解决问题的。接下来我们再看扩容完成以后,如何解决刚才说的水平扩容以后带来的问题。首先是分布式事务。

  • 原子性、去中心化、性能线性增长

扩容以后,数据是跨节点了,系统本来只有一个节点,现在跨节点的话,如何保证数据的原子性,这个我们基于两阶段提交,然后实现了分布式事务。整个处理逻辑对业务来说是完全屏蔽了背后的复杂性,对业务来说使用分布式数据库就跟使用单机MySQL一样。如果业务这条SQL只访问一个节点,那用普通的事务就可以;如果发现用户的一条SQL或者一个事务操作了多个节点,我们会用两阶段提交。到最后会通过记日志来保证整个分布式事务的原子性。同时我们对整个分布式事务在实现过程中做到完全去中心化,可以通过多个SQL来做TM,性能也可实现线性增长。除此之外,我们也做了大量的各种各样的异常验证机制,有非常健壮的异常处理和全局的试错机制,并且我们也通过了TPCC的标准验证。

3.4设计原理:如何实现扩容中性能线性增长

对于水平扩容来说,数据拆分到多个节点后主要带来两个问题:一个是刚才说事务原子性的问题,这个通过分布式事务来解决;还有一个就是性能。

垂直扩容中一般是通过更换更好的CPU或者类似的方法,来实现性能线性增加。水平扩容而言,因为数据拆分到多个节点上去,如何才能很好地利用起拆分下去的各个节点,进行并行计算,,真正把水平分布式数据库的优势发挥出来,需要大量的操作、大量的优化措施。TDSQL做了这样一些优化措施。

eMb2QnQ.jpg!web

一是相关数据存在同一个节点上。建表结构的时候,我们希望业务能参与进来一部分,在设计表结构的时候指定相关的一些键作为shardkey,这样我们就能保证后端的相关数据是在一个节点上的。如果对这些数据进行联合查询就不需要跨节点。

同样,我们通过并行计算、流式聚合来实现性能提升——我们把SQL拆分分发到各个后台的节点,然后通过每个节点并行计算,计算好以后再通过SQL引擎来做二次聚合,然后返回给用户。而为了减少从后端把数据拉到SQL,减少数据的一个拉取的话,我们会做一些下推的查询——把更多的条件下推到DB上。此外我们也做了数据冗余,通过数据冗余保证尽量减少跨节点的数据交互。

我们简单看一个聚合——TDSQL是如何做到水平扩容以后,对业务基本无感知,使用方式跟使用单机MySQL一样的。对业务来说,假设有7条数据,业务不用管这个表具体数据是存在一个节点还是多个节点,只需要插7条数据。系统会根据传过来的SQL进行语法解析,并自动把这条数据进行改写。7条数据,系统会根据分区键计算,发现这4个要发到第一个节点,另外3个发到第二个节点,然后进行改写,改写好之后插入这些数据。对用户来说,就是执行了这么一条,但是跨节点了,我们这边会用到两阶段提交,从而变成多条SQL,进而保证一旦有问题两边会同时回滚。

uiiy2eZ.jpg!web

iQz2yiV.jpg!web

数据插录完以后,用户如果要做一些查询——事实上用户不知道数据是拆分的,对他来说就是一个完整的表,他用类似聚合函数等进行查询。同样,这条SQL也会进行改写,系统会把这条SQL发到两个节点上,同时加一些平均函数,进行相应的转换。到了各个节点,系统会先做数据聚合,到这边再一次做聚合。增加这个步骤的好处是,这边过来的话,我们可以通过做一个聚合,相当于在这里不需要缓存太多的数据,并且做到一个流式计算,避免出现一次性消耗太多内存的情况。

对于比较复杂的一些SQL,比如多表或者是更多的子查询,大家有兴趣的话可以关注我们后面的分享——SQL引擎架构和引擎查询实战。

以上第三章我们比较详细地介绍了TDSQL整个水平扩容的一些原理,比如数据如何进行拆分,水平扩容实践,以及如何解决扩容过程中的问题,同样也介绍水平扩容以后带来的一些问题,TDSQL是如何解决的。

四、水平扩容实践案例

第四章,我们简单来介绍一些实践和案例。

4.1 实践:如何选择分区键

RzArM3q.jpg!web

刚才我们说,我们希望在创建表的时候业务参与进行表结构设计的时候,能考虑一下分区键的选择。如何选择分区键呢?这里根据几种类型来简单介绍一下。

如果是面向用户的互联网应用,我们可以用用户对应的字段,比如用户ID,来做分区键。这样保证在拥有大量用户时,可以根据用户ID将数据拆分到各个后端节点。

游戏类应用,业务的逻辑主体是玩家,我们可以通过玩家对应的字段;电商应用的话,可以根据买家或者卖家的一些字段来作为分区键。物联网的则可以通过比如设备的ID作为分区键。选择分区键总体来说就是要做到对于数据能比较好地做进行拆分,避免最后出现漏点。也就是说,通过这个分区键选择这个字段,可以让数据比较均衡地分散到各个节点。访问方面,当有比较多SQL请求的时候,其实是带有分区键条件的。因为只有在这种情况下,才能更好地发挥分布式的优势——如果是条件里面带分区键,那这条SQL可以直接录入到某一个节点上;如果没有带分区键,就意味着需要把这条SQL发到后端所有节点上。

这个大家可以看到,如果水平扩容到更多——从一个节点扩到256个节点,那某一条SQL写不好的话,可能需要做256个节点全部的数据的聚合,这时性能就不会很好。

总结来说,我们希望业务在创建表,在设计表结构的时候尽量参与进来。因为不管是聚合函数或者是各种事务的操作,其实对业务基本上属于无感知,而业务这时参与则意味着能够换来很大的性能提升。

4.2 实践:什么时候扩容?

我们什么时候扩容?在TDSQL里面,我们会有大量的监控数据,对于各个模块我们在本地会监控整个系统的运行状态,机器上也会有各种日志上报信息。基于这些信息,我们可以决定什么时候进行扩容。

jamQZnM.jpg!web

简单来说,比如磁盘——如果发现数据磁盘使用率太高,这个时候可以进行扩容;或者SQL请求,或者CPU使用率接近100%了——目前基本假如达到80%使用率就要进行扩容。还有一种情况是,可能现在这个时候其实请求量比较少,资源使用比较充足,但如果业务提前告诉你,某个时候将进行一个活动,这个活动到时候请求量会增长好几倍,这个时候我们也可以提前完成扩容。

QJz63mr.jpg!web

下面再看几个云上的集群案例。这个大家看到,这个集群有4个SET,每个SET负责一部分的shardkey,这个路由信息是0-127,意思是它最后能扩到128个节点,所以能扩128倍。这个“128”可以由初始化的业务预估先定下来。因为如果池子太大的话,的确最后可以扩到几千台,但是数据将比较散了。事实上今天每台云上的或者实际的机器性能已经非常好,不需要几千台的规格。

6VfMRvn.jpg!web

这是另外一个集群——它的节点数会多一点,有8个节点,每个节点也负责一部分的路由信息。这个数字只有64,所以这个最后可以扩到64个节点。这个是云上的相关例子。

今天我的分享主要是这些内容,大家如果有什么问题欢迎评论留言。

五、Q&A:

Q:没扩容之前的SET里面的表都是分区表,问一下是不是分区表?

A:是的,在没扩容之前,相当于在这个,简单说我们现在就一个节点,那么我们告诉他256,这个值我们在进行初始化的时候就定下来的。而且这个值集群初始化以后就不会再变了。假设我们这个集群定了一个值是256——因为他可能认为这个数据量后面会非常非常大,可以定256。这个时候,数据都在一个节点上。这个时候用户,按照我们刚才的语法创建了一个表,这个表在底层其实是分成256份的。所以他即使没有进行扩容,它的数据是256份。再创建另外一个表,也是256份。用户可能创建两个表,但是每个表的底层我们有256个分区的,扩容就相当于分区把它迁到其他的地方去。

Q:各个节点的备份文件做恢复时如何保证彼此之间的一致性?

A:各个节点之间没有相互关系,各个节点自己负责一部分的路由号段,只存储部分数据,水平扩容只负责一部分数据,它们之间的备份其实是没有相互的关系,所以这个备份其实是之间不相关的。每个节点我们可能有一主两备,这个其实是我们有强同步机制,在复制的时候来保证数据强一致性。大家可以参考 之前的分享,里面会比较详细地介绍《 T DSQL在单个节点里面, T DSQL一主多备 架构 是如何保证数据的强一致性的 》

Q:两阶段在协调的时候能避免单点故障吗?

A:首先在两阶段提交的时候,我们是用SQL引擎做事务的协调,这个是单个的事务。如果其他的连接发过来,可以拿其他的SQL引擎做事务协调。而且每个SQL引擎是做到无状态的,可以进行水平扩展。所以这个其实是不会有太多的故障,我们可以根据性能随机扩展的,可以做到性能的线性增长,没有中心化。日志这些都是被打散的,记日志也会记到TDSQL后端的数据节点里面,一主多备,内部保证强一致性,不会有单点故障。

TDSQL是腾讯TEG数据库工作组下三大产品系之一,是一款腾讯自研的金融级分布式数据库产品,目前广泛应用于金融、政务、物联网、智慧零售等行业,拥有大量的分布式数据库最佳实践。

特惠体验云数据库 

jaUb6fF.png!web

↓↓更多惊喜优惠请点这儿~  


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK