自去年12月底发布后,腾讯会议40天更新14个版本,8天紧急扩容超过10万台云主机,投入的计算资源超100万核。疫情复工期间,每周都有数万家企业和政府相关机构使用腾讯会议复工复产,通过腾讯会议开拓了云签约、云招标、云面试、云培训等云上协同场景。腾讯会议这款云视频会议产品,日活跃账户数已超1000万,成为当前中国多人使用的视频会议专用应用。目前,腾讯会议国际版也已经在超过100个国家和地区上线,助力全球战疫。作为腾讯会议核心数据库,近期腾讯分布式数据库 TDSQL 持续支撑腾讯会议应对快速增长的存储容量和性能需求,为用户提供高速流畅、稳定可靠的服务,在平稳应对流量突增,实现让用户无感知的情况下进行快速无损在线扩容的场景方面提供了佳实践案例。
面对流量突增场景,保障系统高可用的要务是进行系统扩容,满足业务的性能和容量需求。回顾腾讯会议数据库面对流量突增的过程,作为腾讯会议的重要系统基础支持,随着流量的持续暴涨,优化之后 TDSQL 进行了一轮快速的数据库机器水平扩容实践:
通过 TDSQL 策略丰富的读写分离技术,数据库层面快速响应了持续增长的容量和性能需求。
为了尽可能的将读请求分离,进一步降低对主节点的影响,TDSQL通过读写账号分离、灾备只读实例等措施,将纯只读业务分离出来,进一步降低主节点的压力提高整体的吞吐量。终,25%的复杂查询根据读写分离策略发往只读实例,快速达到降低主节点的负载的效果。
健壮的分布事务能力支撑,持续不断地进行性能优化。
SQLEngine 作为协调节点,无状态,能满足业务层几乎无限制的水平扩容需求。
不停机无损线性水平扩容,保障系统高可用、高性能,数据库技术架构如何做到?中间有哪些看不见的坑,有没有经过了实际验证的佳方案?数字化转型全局发展正在提速,流量洪峰渐成常态,未来,我们需要怎样的分布式技术架构系统?
就扩容来说,比较常见的就是两种方式,一种是垂直扩容,一种是水平扩容。这两种有不同的特点,优缺点其实也非常明显。水平扩容即将数据从一台机器上拆分到多台机器,通过将一台物理机的请求分散到多台来实现吞吐量的提升。垂直扩容是将数据从一台低规格的物理机迁移到一台高规格的设备,通过物理硬件的提升实现吞吐量的提升。跟垂直扩容对比,分布式水平扩容大的优点是解决了垂直扩容的瓶颈问题——理论上水平扩容可以进行无限扩容,它可以通过增加机器的方式来动态适应业务的需求。水平扩容和垂直扩容相比,它可以解决垂直扩容的问题,但是会引入一些其他的问题。因为水平扩容比垂直扩容更加复杂,下面我们分析下可能遇见的问题,以及介绍TDSQL架构设计的解决方案:,系统经过垂直扩容,其实数据总体还是存在一个节点,一主多备架构中,备机上也存储着所有数据。而水平扩容过程中数据会进行拆分,这里面临的个问题是,数据如何进行拆分?因为如果拆分不好,当出现热点数据时,存储热点数据的单独节点也有可能成为性能的瓶颈。第二,水平扩容会涉及到数据的搬迁、路由的改变。那么整个过程中能否做到对业务没有感知?或者是对业务的侵入如何尽可能地降低?第三,扩过程中的诸多步骤,如果其中一步失败了,如何进行回滚?同时,在整个扩容过程中,如何能保证切换过程中数据一致性?第四,由于扩容后数据拆分到了各个节点,如何能保证扩容后的性能?在整个水平扩容的过程中,不同的架构或者不同的方式,对性能影响都比较大。第五,当数据已经拆分成多份,如何继续保证数据库分布式的特性?在分布式架构里,如何保证数据跨节点过程中的全局的一致性?这个就会涉及到分布式事务。综上考虑,水平扩容的优点很明显,它解决了垂直扩容机器的限制。但是它更复杂,引入了更多的问题。那么 TDSQL 是如何进行水平扩容,又是如何解决上述问题的呢?
1. TDSQL架构
首先看TDSQL的架构。TDSQL简单来说包含几部分:部分是SQL引擎层:主要是作为接入端,屏蔽整个 TDSQL 后端的数据存储细节。对业务来说,业务访问的是SQL引擎层。接下来是由多个Set组成的数据存储层:分布式数据库中,数据存储在各个节点上,每个Set我们当做一个数据单元,可以根据业务需要来部署一主两备或者一主多备。对于对数据安全性要求更高的业务场景,可以一主三备甚至一主四备。Scheduler模块:主要负责整个系统集群的监控、控制。在系统进行扩容或者主备切换时,Scheduler模块相当于整个系统的大脑,来对系统进行控制。而对业务来说是无感知的,业务只需关注SQL引擎层,不需要关注Scheduler,不需要关注数据是怎么跨节点,怎么分成多少个节点等。2. TDSQL水平扩容过程:一键扩容
整个扩容流程总结起来大概是:一开始数据都放在一个Set上,也就是在一个节点里面。扩容则会把数据扩容到多个节点上。比如有256个Set,就会扩容到256台机器上。整个扩容过程有几个要点:总结来说,数据一开始已经切分好了,扩容过程相当于把分片迁到新的节点,整个扩容过程中,节点数是增加的,可以从1扩到2扩到4,甚至扩到后可以到256,但是分片数是不变的。一开始256个分片在一个节点上,扩成两个节点的话,有可能是每128个分片在一个节点上;扩到后,可以扩到256个节点上,数据在256台机器,每台机器负责其中的一个分片。因此整个扩容简单来说就是搬迁分片。无论是在私有云或公有云上, TDSQL提供了一个统一的前台页面,方便用户使用管理整个数据库系统。我们看一下这个例子。现在这个案例中有两个Set,也就是两个节点,每一个节点负责一部分的路由,个节点负责0-31的路由信息,另一个节点负责的是32-63。如果要进行扩容,在前台页面上我们会有一个“添加Set”的按纽,点一下“添加Set”,就会弹出一个对话框,里面默认会自动选择之前的一个配置,用户可以自己自定义,包括现在这个Set,需要多少资源以及内存、磁盘的分配等。此外,因为扩容要进行路由切换,我们可以手动选择一个时间(比如凌晨切换尽可能减少对业务的影响)自动切换,也可以由业务判断业务的实际情况,人工操作路由的切换。这些都可以根据业务的需要进行设置。步创建好以后,前文提到大脑模块(Scheduler模块)会负责分配资源以及初始化,并进行整个数据同步的逻辑。后,由原来的一个节点变为现在的两个节点,扩容之前,个节点负责0-31的路由信息,现在它只负责0-15,另外一部分路由由新的节点来负责。整个过程通过网页上点一下就可以快速地从一个节点添加到两个节点——我们还可以继续添加Set,继续根据业务的需要进行一键扩容。
为了保证分布式数据库系统的数据强一致性、快速线性水平扩容、高性能、高可用等,解决前文提到的问题,TDSQL架构方案进行了相应的设计。事实上,这些问题不管在哪个系统做水平扩容,都是需要解决的。1. 设计原理:分区键选择如何兼顾兼容性与性能
水平扩容个问题是数据如何进行拆分。数据拆分是步,将影响后续整个使用过程。在TDSQL中,数据拆分的逻辑放到一个创建表的语法里面,需要业务去指定 “shardkey等于某个字段”——业务在设计表结构时需要选择一个字段作为分区键,TDSQL会根据这个分区键进行数据拆分,而访问会根据分区键进行数据聚合。我们是希望业务在设计表结构的时候能够参与进来,指定一个字段作为shardkey。这样一来,兼容性与性能都能做到很好的平衡。TDSQL也可以做到用户创建表的时候不指定shardkey,由系统底层随机选择一个键进行数据拆分,但这个会影响后续的使用效率,比如不能特别好地发挥分布式数据库的使用性能。因此,业务层如果在设计表结构时能有少量参与,可以带来非常大的性能优势,让兼容性和性能得到平衡。除此之外,如果由业务来选择shardkey——分区键,在业务设计表结构的时候,我们可以看到多个表,可以选择相关的那一列作为shardkey,这样可以保证数据拆分时,相关的数据是放在同一个节点上的,这样可以避免很多分布式情况下的跨节点的数据交互。
2. 设计原理:扩容中的高可用和高可靠性
扩容流程比较复杂,那么整个扩容过程能否保证高可用或者高可靠性,以及对业务无感知?TDSQL是怎么做的呢?步是数据同步阶段。假设我们现在有两个Set,然后我们发现其中一个Set现在的磁盘容量已经比较危险了,比如可能达到80%以上了,这个时候要对它进行扩容。我们首先会新建一个实例,通过拷贝镜像、新建实例、新建同步关系。建立同步的过程对业务无感知,而且这个过程是实时同步。第二阶段,则是持续地追平数据,同时持续进行数据校验。这个过程可能会持续一段时间,对于两个同步之间的延时差无限接近时——比如我们定一个5秒的阈值,当我们发现已经追到5秒之内时,这个时候我们会进入第三个阶段——路由更新阶段。路由更新阶段当中,首先我们会冻结写请求,这个时候如果业务有写过来的话,我们会拒掉,让业务两秒后重试——这个时候对业务其实是有秒级的影响。但是这个时间会非常短。冻结写请求之后,第三个实例同步的时候很快就会发现数据全部追上来,并且校验也没问题,这个时候我们会修改路由,同时进行相关原子操作,在底层做到存储层分区屏蔽,这样就能保证SQL接入层在路由来不及更新的时数据也不会写错。因为底层做了改变,分区已经屏蔽了。这样就可以保证数据的一致性。路由一旦更新好以后,第三个Set就可以接收用户的请求——到了这里,因为个Set和第三个Set已经建立了同步,所以它们两个是拥有全量数据的。后一步则需要把这些冗余数据删掉。删冗余数据用的是延迟删除,保证删除过程中可以慢慢删,也不会造成比较大的IO波动,影响现网的业务。整个删除过程中,我们做了分区屏蔽,同时会在SQL引擎层做SQL的改写,这样就能保证:我们在底层虽然有冗余数据,但用户来查的时候即使是一个全扫描,我们也能保证不会多一些数据。可以看到整个扩容流程,数据同步,还有校验和删除冗余这几个阶段,时间耗费相对来说会比较长,因为要建同步的话,如果数据量比较大,整个拷贝镜像或者是追binlog这段时间相对比较长。但是这几个阶段对业务其实没有任何影响,业务根本就没感知到现在新加了一个同步关系。那么假如在建立同步关系时发现有问题,或者新建备机时出问题了,也完全可以再换一个备机,或者是经过重试,这个对业务没有影响。路由更新阶段,理论上对业务写请求难以避免会造成秒级的影响,但我们会将这个影响时间窗口期控制在非常短,因为本身冻结写请求是需要保证同步已经在5秒之内这样一个比较小的阈值,同步到这个阶段以后,我们才能发起路由更新操作。同时,我们对存储层做了分区屏蔽来保证多个模块之间,如果有更新不同时也不会有数据错乱的问题。这是一个我们如何保证扩容中的高可用跟高可靠性的,整个扩容对业务影响非常小的原理过程。3. 设计原理:分布式事务
刚才讲的是扩容阶段大概的流程,以及TDSQL是如何解决问题的。接下来我们再看扩容完成以后,如何解决刚才说的水平扩容以后带来的问题。首先是分布式事务:原子性、去中心化、性能线性增长。系统本来只有一个节点,扩容以后,数据是跨节点了,如何保证数据的原子性?我们基于两阶段的提交,实现了分布式事务。对业务屏蔽了整个处理逻辑背后的复杂性,对业务来说使用分布式数据库就跟使用单机MySQL一样。如果业务这条SQL只访问一个节点,那用普通的事务就可以;如果发现用户的一条SQL或者一个事务操作了多个节点,我们会用两阶段提交。到后会通过记日志来保证整个分布式事务的原子性。同时我们对整个分布式事务在实现过程中做到完全去中心化,可以通过多个SQL来做TM,性能也可实现线性增长。除此之外,TDSQL也做了大量的各种各样的异常验证机制,有非常健壮的异常处理和全局的试错机制,并且通过了TPCC的标准验证。4. 设计原理:如何实现扩容中性能线性增长
对于水平扩容来说,数据拆分到多个节点后主要带来两个问题:一个是事务原子性的问题,可以通过分布式事务来解决;此外还带来了性能方面的问题。垂直扩容中一般是通过更换更好的CPU等方法来实现性能线性增加。对于水平扩容,因为数据拆分到多个节点上,如何才能很好地利用到拆分下去的各个节点,进行并行计算,真正把水平分布式数据库的优势发挥出来,需要大量的操作、大量的优化措施。TDSQL做了这样一些优化措施:一是相关数据存在同一个节点上。建表结构的时候,我们希望业务能参与进来一部分,在设计表结构的时候指定相关的一些键作为shardkey,这样我们就能保证后端的相关数据是在一个节点上的。如果对这些数据进行联合查询就不需要跨节点。同样,我们通过并行计算、流式聚合来实现性能提升——我们把SQL拆分分发到各个后台的节点,然后通过每个节点并行计算,计算好以后再通过SQL引擎来做二次聚合,然后返回给用户。为了减少数据的拉取,我们会做一些下推的查询——把更多的条件下推到DB上。此外我们也做了数据冗余,通过数据冗余保证尽量减少跨节点的数据交互。我们简单看一个聚合——TDSQL是如何做到水平扩容以后,对业务基本无感知,使用方式跟使用单机MySQL一样的。对业务来说,假设有7条数据,业务不用管这个表具体数据是存在一个节点还是多个节点,只需要插7条数据。系统会根据传过来的SQL进行语法解析,并自动把这条数据进行改写。7条数据,系统会根据分区键计算,发现这4个要发到个节点,另外3个发到第二个节点,然后进行改写,改写好之后插入这些数据。对用户来说,就是执行了这么一条,但是跨节点了,我们这边会用到两阶段提交,从而变成多条SQL,进而保证一旦有问题两边会同时回滚。
数据插录完以后,用户如果要做一些查询——事实上用户不知道数据是拆分的,对他来说就是一个完整的表,他用类似聚合函数等进行查询。同样,这条SQL也会进行改写,系统会把这条SQL发到两个节点上,同时加一些平均函数,进行相应的转换。到了各个节点,系统会先做数据聚合,到这边再一次做聚合。增加这个步骤的好处是,这边过来的话,我们可以通过做聚合——相当于在这里不需要缓存太多的数据,并且做到流式计算,避免出现一次性消耗太多内存的情况。以上是TDSQL水平扩容方案设计原理以及实践过程的介绍。
除了腾讯会议,疫情期间,TDSQL 还基于腾讯云快速支持了多地区健康码、市政防控平台小程序等高并发、筹备时间短的项目应用顺利上线、稳定运行,服务民众数亿,日均调用数十亿次。基础技术的积累让我们今天拥有顺利应对一切突发变化的能力,未来基于新型业务形态,我们的底层基础技术也将不断迭代演进,开启下一个数字化时代。来源:https://mp.weixin.qq.com/s/4OTVKhWGWQUYOIpGBMP-tA