设计原理:分区键抉择如何兼顾兼容性与性能
首先咱们方才提到,程度扩容第一个问题是数据如何进行拆分。因为数据拆分是第一步,这个会影响到后续整个应用过程。对 TDSQL 来说,数据拆分的逻辑放到一个创立表的语法外面。须要业务去指定 shardkey“等于某个字段”——业务在设计表构造时须要抉择一个字段作为分区键,这样的话 TDSQL 会依据这个分区键做数据的拆分,而拜访的话会依据分区键做数据的聚合。咱们是心愿业务在设计表构造的时候可能参加进来,指定一个字段作为 shardkey。这样一来,兼容性与性能都能做到很好的均衡。
其实咱们也能够做到用户创立表的时候不指定 shardkey,由咱们底层这边随机抉择一个键做数据的拆分,但这个会影响后续的应用效率,比方不能特地好地施展分布式数据库的使用性能。咱们认为,业务层如果在设计表构造时能有大量参加的话,能够带来十分大的性能劣势,让兼容性和性能失去均衡。除此之外,如果由业务来抉择 shardkey——分区键,在业务设计表构造的时候,咱们能够看到多个表,能够抉择相干的那一列作为 shardkey,这样能够保证数据拆分时,相干的数据是放在同一个节点上的,这样能够防止很多分布式状况下的跨节点的数据交互。
咱们在创立表的时候,分区表是咱们最罕用的,它把数据拆分到各个节点上。此外,其实咱们提供了另外两种——总共会提供三种类型的表,背地的次要思考是为了性能,就是说通过将 global 表这类数据是全量在各个节点上的表——一开始大家会看到,数据全量在各个节点上,就相当于是没有分布式的个性,没有程度拆分的个性,但其实这种表,咱们个别会用在数据量比拟小、改变比拟少的一些配置表中,通过数据的冗余来保障后续拜访,特地是在操作的时候可能尽量避免跨节点的数据交互。其余方面,shardkey 来说,咱们会依据 user 做一个 Hash,这个益处是咱们的数据会比拟平衡地散布在各个节点上,来保证数据不会有热点。
设计原理:扩容中的高可用和高可靠性
方才也提到,因为整个扩容过程的流程会比较复杂,那么整个扩容过程是否保障高可用或者高可靠性,以及对业务的感知是怎么样的,TDSQL 是怎么做的呢?
数据同步
第一步是数据同步阶段。假如咱们当初有两个 Set,而后咱们发现其中一个 SET 当初磁盘容量曾经比拟危险了,比方可能达到 80% 以上了,这个时候要对它进行扩容,咱们首先会新建一个实例,通过拷贝镜像,新建实例,新建同步关系。建设同步的过程对业务无感知,而这个过程是实时的同步。
数据校验
第二阶段,则是继续地追平数据,同时继续地进行数据校验。这个过程可能会继续一段时间,对于两个同步之间的延时差有限靠近时——比方咱们定一个 5 秒的阈值,当咱们发现曾经追到 5 秒之内时,这个时候咱们会进入第三个阶段——路由更新阶段。
路由更新
路由更新阶段当中,首先咱们会解冻写申请,这个时候如果业务有写过来的话,咱们会拒掉,让业务过两秒钟再重试,这个时候对业务其实是有秒级的影响。然而这个工夫会十分短,解冻写申请之后,第三个实例同步的时候很快就会发现数据全副追上来,并且校验也没问题,这个时候咱们会批改路由,同时进行相干原子操作,在底层做到存储层分区屏蔽,这样就能保障 SQL 接入层在如果路由来不及更新的时数据也不会写错。因为底层做了扭转,分区曾经屏蔽了。这样就能够保证数据的一致性。路由一旦更新好当前,第三个 SET 就能够接管用户的申请,这个时候大家能够发现,第一个 SET 和第三个 SET 因为建设了同步,所以它们两个是领有全量数据的。
删除冗余数据
最初一步则须要把这些冗余数据删掉。删冗余数据用的是提早删除,保障删除过程中能够缓缓删,也不会造成比拟大的 IO 稳定,影响现网的业务。整个删除过程中,咱们做了分区屏蔽,同时会在 SQL 引擎层会做 SQL 的改写,来保障当咱们在底层尽管有冗余数据,但用户来查的时候即便是一个全扫描,咱们也能保障不会多一些数据。
能够看到整个扩容流程,数据同步,还有校验和删除冗余这几个阶段,工夫消耗相对来说会比拟长,因为要建同步的话,如果数据量比拟大,整个拷贝镜像或者是追 binlog 这段时间绝对比拟长。然而这几个阶段对业务其实没有任何影响,业务基本就没感知到当初新加了一个同步关系。那么如果在建设同步关系时发现有问题,或者新建备机时出问题了,也齐全能够再换一个备机,或者是通过重试,这个对业务没有影响。路由更新阶段,实践上对业务写申请难以避免会造成秒级的影响,但咱们会将这个影响工夫窗口期管制在十分短,因为自身解冻写申请是须要保障同步曾经在 5 秒之内这样一个比拟小的阈值,同步到这个阶段当前,咱们能力发动路由更新操作。同时,咱们对存储层做了分区屏蔽来保障多个模块之间,如果有更新不同时也不会有数据错乱的问题。这是一个咱们如何保障扩容中的高可用跟高可靠性的,整个扩容对业务影响十分小的原理过程。
设计原理:分布式事务
方才讲的是扩容阶段大略的流程,以及 TDSQL 是如何解决问题的。接下来咱们再看扩容实现当前,如何解决方才说的程度扩容当前带来的问题。首先是分布式事务。
原子性、去中心化、性能线性增长
扩容当前,数据是跨节点了,零碎原本只有一个节点,当初跨节点的话,如何保证数据的原子性,这个咱们基于两阶段提交,而后实现了分布式事务。整个解决逻辑对业务来说是齐全屏蔽了背地的复杂性,对业务来说应用分布式数据库就跟应用单机 MySQL 一样。如果业务这条 SQL 只拜访一个节点,那用一般的事务就能够;如果发现用户的一条 SQL 或者一个事务操作了多个节点,咱们会用两阶段提交。到最初会通过记日志来保障整个分布式事务的原子性。同时咱们对整个分布式事务在实现过程中做到齐全去中心化,能够通过多个 SQL 来做 TM,性能也可实现线性增长。除此之外,咱们也做了大量的各种各样的异样验证机制,有十分强壮的异样解决和全局的试错机制,并且咱们也通过了 TPCC 的规范验证。
设计原理:如何实现扩容中性能线性增长
对于程度扩容来说,数据拆分到多个节点后次要带来两个问题:一个是方才说事务原子性的问题,这个通过分布式事务来解决;还有一个就是性能。
垂直扩容中个别是通过更换更好的 CPU 或者相似的办法,来实现性能线性减少。程度扩容而言,因为数据拆分到多个节点下来,如何能力很好地利用起拆分上来的各个节点,进行并行计算,,真正把程度分布式数据库的劣势施展进去,须要大量的操作、大量的优化措施。TDSQL 做了这样一些优化措施。
一是相干数据存在同一个节点上。建表构造的时候,咱们心愿业务能参加进来一部分,在设计表构造的时候指定相干的一些键作为 shardkey,这样咱们就能保障后端的相干数据是在一个节点上的。如果对这些数据进行联结查问就不须要跨节点。
同样,咱们通过并行计算、流式聚合来实现性能晋升——咱们把 SQL 拆分散发到各个后盾的节点,而后通过每个节点并行计算,计算好当前再通过 SQL 引擎来做二次聚合,而后返回给用户。而为了缩小从后端把数据拉到 SQL,缩小数据的一个拉取的话,咱们会做一些下推的查问——把更多的条件下推到 DB 上。此外咱们也做了数据冗余,通过数据冗余保障尽量减少跨节点的数据交互。
咱们简略看一个聚合——TDSQL 是如何做到程度扩容当前,对业务根本无感知,应用形式跟应用单机 MySQL 一样的。对业务来说,假如有 7 条数据,业务不必管这个表具体数据是存在一个节点还是多个节点,只须要插 7 条数据。零碎会依据传过来的 SQL 进行语法解析,并主动把这条数据进行改写。7 条数据,零碎会依据分区键计算,发现这 4 个要发到第一个节点,另外 3 个发到第二个节点,而后进行改写,改写好之后插入这些数据。对用户来说,就是执行了这么一条,然而跨节点了,咱们这边会用到两阶段提交,从而变成多条 SQL,进而保障一旦有问题两边会同时回滚。
![file](/img/bVcUL7R)
数据插录完当前,用户如果要做一些查问——事实上用户不晓得数据是拆分的,对他来说就是一个残缺的表,他用相似聚合函数等进行查问。同样,这条 SQL 也会进行改写,零碎会把这条 SQL 发到两个节点上,同时加一些均匀函数,进行相应的转换。到了各个节点,零碎会先做数据聚合,到这边再一次做聚合。减少这个步骤的益处是,这边过去的话,咱们能够通过做一个聚合,相当于在这里不须要缓存太多的数据,并且做到一个流式计算,避免出现一次性耗费太多内存的状况。
对于比较复杂的一些 SQL,比方多表或者是更多的子查问,大家有趣味的话能够关注咱们前面的分享——SQL 引擎架构和引擎查问实战。