TDSQL-C PG 版整体架构
在介绍整体架构前,先说一下为什么咱们要做 TDSQL-C 这款产品。在传统数据库上,数据库的应用是存在一些问题,次要分为以下四个:
第一是资源利用率低,计算和存储在一台机器上,CPU 和磁盘应用不平衡,例如 CPU 用满,但磁盘很闲暇或者 CPU 很闲暇但磁盘又满了,这样就会导致资源利用率低。
第二是扩大能力有余,在单排上可能不能满足一些用户要求,无奈扩大。
第三是资源布局难,例如用户应用数据库,一开始无奈预估这个数据库须要多少次磁盘空间。
第四是备份比拟艰难,因为每一个实例数据是公有的,所以每个实例都须要独自进行备份。
TDSQL-C 的解决思路:
第一个问题是计算存储拆散,计算资源能够弹性调度。例如说可能给用户调配一个 4 核 8G 存储资源,一段时间当前会发现这个无奈满足他要求,那能够给它调配一个更高规格实例。比如说 16 核 32G 这样一个实例,这个是做计算资源升配。第二个问题是日志下沉以及异步回放,TDSQL-C 的日志是通过网络,从计算层下放到存储层。第三个问题是共享分布式存储,咱们 TDSQL-C 地区的所有实例,在底下是共享一个分布式存储,能够动静向一个实例里增加资源。最初一个是后盾继续备份,咱们的后盾有定期备份工作,将日志和数据备份到地上存储下面。
PG 实例,包含主实例和读实例,主的负责读写,只读是负责数据读取。在 PG 下有一个叫 CynosStore Agent 组件,它次要负责存储层进行通信,包含主的写日志、读页面,从的是读页面。再向下是存储服务或叫 CynosStore,采纳的是 RAPS 构造,一主两层。左边是集群治理服务,是对 CynosStore 里存储节点进行治理服务。包含故障迁徙是一个节点产生故障而后迁到其它节点性能。
另外一个当须要扩大资源时,也是由这个集群治理服务来实现。上面是对象存储,咱们会定期在对象存储备份日志和数据。这里波及几个外围架构:一个是日志下沉,计算节点产生的日志是通过网络,下沉到存储层。存储层是通过异步形式来回访日志,导出页面下来。另外是咱们会提供一个多版本读的能力,PG 下层可能会有多个 Buffer 会话去读这个数据,每个开始工夫不一样,可能会读到不同的版本。
介绍一下日志下沉、异步回放这部分。这里边波及几个概念:
第一个是数据原子批改,咱们叫 MTR。当中有很多状况是一个数对应一个数据库要批改多个页面,例如想对数字索引决裂或是一条 UP Date 语句,它可能要给多条元组,散布在不同的页面上,这些都须要保障是原子操作。第二是 CPL 概念,咱们 MTR 里批改了多条数据页面批改,最初一个产生日志咱们叫它 CPL,另外一个是叫 VDL。这 CPL 是存储层所有间断 CPL 里最大值,我管它叫做 VDL。随着数据库在运行中,这个 VDL 是在一直地向前推动。同时咱们的 PG 读也是拿到一个读点,就是一个个的 VDL。
第二个是日志异步写入。日志异步写入是由咱们 PG 过程来生成日志,写到咱们的日志 Buffer,日志 Buffer 是 PG 过程和咱们 CynosStore Agent 过程之间共享的。写到这个 Buffer 当前,由 Agent 过程给它异步发送到存储节点。存储节点是通过挂到日志链上,再异步合并到数据页面下来,整个过程都是异步的。
第三个是日志并行插入。下层 PG 能够有多个 Buffer 同时去写日志,是用一个并行的形式,不是串行的拷贝形式。
接下来看一下,TDSQL-C PG 版的主机优化:
第一点是不用依赖于传统 PG 的 CheckPoint 机制。大家都晓得传统 PG 有个 CheckPoint,定期把数据库中日志和对应批改的数据页都刷到本地磁盘上,这是一个比拟耗时操作。那在咱们的零碎里边因为是存在拆散,日志是异步通过存储节点来回放,所以就没有了 CheckPoint(07:43 英)的机制。
第二点是不须要在日志中记录全页。PG 上有一个概念,叫 Full Page Write,在 CheckPoint 之后,对每个数据页面的第一次批改,把整个数据页面内容写入到日志当中。这是为了避免断电状况下可能产生数据页面的半页问题,而在咱们这种架构下不须要这个,能够缩小很多日志。
第三点是疾速启动零碎。在启动时不须要复原 XLog、DLog 这些,能够很快将数据库启动起来提供服务。传统 PG 它是先须要复原大量 XLog 当前,达到统一点才能够对外提供服务。
最初一个是日志合并压缩。这个是在对一个数据页面做批改时,往往须要批改这个页面多个不同偏移,比如说从第 0 个偏移改 8 个字节,而后须要从第 30 个字节开始改 4 个字节,会波及到多个批改。咱们把这同一页面多个批改形象进去一个共享日志来缩小日志大小,进一步缩小 IO。
TDSQL-C PG 版主从架构
接下来介绍一下 TDSQL-C PG 版的主从架构。传统数据库 PG 主备模式是先把日志写到本地磁盘,再由主机的 Sender 过程把 XLog 读取进去通过网络发送到备机,备机有个 Receiver 过程接管到这部分日志写到本地磁盘,再由 PG 复原过程 Starup 读出来,把 XLog 对应批改利用到数据页面下来。然而它有几个毛病,第一个是在创立备机时,须要拷贝主机日志和数据这部分内容,这部分内容拷贝须要肯定工夫,例如说当实例比拟大几个 T 甚至几十个 T 时,须要很多工夫,另外一点是须要消耗存储资源,在备机切换成主机和启动过程中,它都是须要去复原 XLog,达到一致性状态之后能力对外提供服务,导致后果就会启动慢。
咱们采纳的一主多读模式有以下两种劣势:第一个是因为咱们搭建从句速度很快,所以横向扩大读能力会比传统 PG 好很多。咱们搭建从不须要思考数据,因为咱们的数据是共享的。第二个是因为咱们横向扩大能力强,所以从晋升主时也不须要来复原日志,在晋升数据库可用性这方面比传统 PG 好很多。
接下来介绍主从架构里边多个节点并复原日志的实现。这张图外面是一组三层构造,能够看到主从之间发送日志是在咱们 CynosStore Agent 这个组件里进行发送。从机是由 CynosStore Agent 组件来接管日志。接管到日志会把它先写到磁盘,再读上来写到共享日志 HAC 表里生成日志链,这个日志链的 Key 就是 Block ID。
这些日志大略分为两类:第一类是运行时一些信息,包含事物列表,还有锁,还有一些运行时快照这些信息。另外一类是对数据页批改产生的日志,包含 Heap 页面、索引页面这些。挂完链当前,这些链上的日志是由 PG 的后盾过程读取,而后将日志对应的批改利用到页面上。和 PG 不一样的在于,当咱们要利用的日志对应数据页面不在内存当中时,咱们跳过这个页面读取,就是日志复原是不须要从存储上读上来当前再复原到内存中。这一点是和传统 PG 不一样的中央。
TDSQL PG 版主从机优化
接下来介绍 TDSQL-C PG 版优化,这里波及到从句优化。从 Starup 过程去读 XLog,能够看到它不论页面是不是在 Buffer 中,它都是须要去存储中把对应数据页面读出来,把 XLog 利用下来在复原下一条。因为它的数据不是共享的而是公有的,如果不复原对应日志就会失落数据。
咱们和它区别一是咱们有多个利用日志过程来利用日志。二是当这个页面不在 Buffer 的时候,咱们能够跳过这部分日志,就不须要去读上来再回复。
接下来介绍一下后面提到的从机并行合并日志。这个图外面咱们画的是 ABCD 有这么四个数据块,每个块下面是括号里的值示意的是以后数据块日志利用到的地位,11、17、15、9,它们示意的就是以后数据块利用地位。假如当初又来了 12、18、16、19 对应日志,那就会有多个 Merge 过程来对这些没有相关性的数据块做并行的复原。例如第一个 Merge 过程会对 A 和 B 这两个数据块做复原。如果是另外一个 Merge 过程能够对 C 这个数据块做复原,它们之间是并行的。每复原完这样一些数据块后,咱们的 VDL 就能够向前推动,比如说从 17 到 19 还能够接着向前推。
接下来介绍从机优化是针对 DROP 表和 DROP 数据库优化。在数据库 PG 里,当你主机上 DROP 一个表或 DROP 一个 DB 时,从机须要做这么几件事:
第一是要删除,把这个表或者这个数据库在零碎表当中的原数据,像 PG-Class 当中或者是 PG-Attrdef,还有 PG-attribute 的这些零碎表当中的原数据,要先给它删掉。
第二是须要遍历一下 Shared Buffer,这些表或数据可能在从机上去读取过,它可能在 Buffer 里留下了一些页面,咱们须要把这些页面找到,让它生效掉。
第三是发送生效音讯,就是这个表或数据的生效音讯。一个时效音讯队列,告诉其它过程,而后是删除外存文件。
这里第二步是 Shared Buffer,这个操作比拟耗时。依照默认 PG 的一个 Buffer 是 8K 来算,那么 1G 的 Shared Buffer 就会有 13 万左右 Buffer,那么有 64G 大略会有 800 多万的 Buffer。被删一张表,在从机这边可能要遍历 800 万次 Buffer,生效一个页面,去淘汰一个页面。当到了这个表比拟多的时候,比如说抓回一个 Scheme,上面可能有几十张表时,那么这个遍历次数就更多了。
咱们这里做了一个优化,就是将它的第二步也就是遍历 Buffer,生效 Buffer 的操作,把它独自拿进去做为一个过程来做这件事。这个中央咱们叫 Log Process。它和 Starup 之间是通过共享内存队列形式来通信,也就是 Starup 过程。当须要生效 Shared Buffer 的时候,咱们会把对应的 DBID 或表 ID 放到队列中,Starup 会告诉 Log Process,Log Process 会去对接外面取到这个 DBID 或 TableID,再去遍历 Buffer,将对应 Buffer 生效掉。
另外一个优化和 DAG 表相干。PG 在 DAG 表时,会把这个表信息在内存当中保留这个表一些信息,当这个表在主机删除,再从机须要复原的时候,它会把这个表从它单项列表当中移除掉,也就是这个表第一次创立时会在从机放到一个列表里,当主机删除的时候,备机会把这个表信息从单列表导出来,把它进行删除。
当咱们表比拟多的时候,比如说有几千、几万表时,单向列表长度可能须要几千几万。当要删除这个表时,要从几千或几万个元素单向列表中去找到要删除的节点,找到它前向节点,最初把它指向要删除这个节点的后向节点。这个单向里查找是一个比拟耗时的操作。
这里用法比较简单是把单向链表改为双向链表。让要删除这个节点的后面指向它前面节点,它前面节点指向它后面的节点,就能够达到删除目标。有了这个优化,加上异步淘汰 Buffer 优化,在有肯定压力状况下,日志沉积能够从 100GB 升高到几十兆这个级别,几百兆就没有日志沉积了。
接下来是介绍一下传统备机和 TDSQL-C PG 版启动的一个。传统 PG 启动的时候复原到一致性的点能力对外提供服务。以后画的这个图里比如说最小复原点是 50,而复原当中又来了一个 CheckPoint,比如说 CheckPoint 里记录的是 1000,那么它在下一次启动时须要复原到 1000 点能力对外提供服务。
在 TDSQL-C PG 版里,从机启动时,是须要拿到一个长久化 VDL 就能够取得存储一致性状态,而这个 VDL 是能够从主机传过来的日志当中计算出来。这个速度比 PG 快很多。第二个是疾速 Replica 创立备机,就是不须要复制全量数据。
咱们的主从优化它解决的问题是防止 PG 在产生主从切换时可能会呈现双写,导致日志“分叉”。例如一个一主一丛的 PG 实例,当产生切主时,因为某些起因旧主并没有死掉,可能有些利用还是连在旧主下面,然而另外一些利用连到新主下面,会导致两边数据不统一,须要人工干预能力把数据库复原到统一状态。
TDSQL-C PG 版采纳的是 HA Fencing 机制。当每一个主实例在启动开始写数据时,之前会通过网络去咱们的 Meta 服务下面获取一个 Fencing 值,这个 Fencing 值是全局惟一递增的。计算层每次往存储层写日志时都会带着这个值。
当切到一个新主上时,新主又会去 Meta 服务上拿到一个新的值,例如旧主拿的是 100,旧主存储节点通信时都会用 100,假如这时候新主上来,它会拿到 101,而后它就用 101 和咱们的存储节点通信。这时假如旧主还通过网络,以 100 往存储上写数据,存储就会回绝这个数据的写入,从而达到了防止数据分叉目标。
将来瞻望
对将来的一些摸索,咱们可能会采纳一些新硬件,包含 RDMA。另外,当初是一种多层的架构,将来会尝试做多主架构、Serverless 无服务化这些来升高计算成本,可能还会做一些兼容性方面的工作,例如 Oracle 兼容性这些。