关于数据库:PolarDBX-全局-Binlog-解读之性能篇下

多级多路归并

在上篇文章中,咱们通过一系列的测试,针对全局binlog的同步能力,得出了几个外围论断 BPS能够达到500M+/s EPS能够达到220w+/s * TPS能够达到35w+/s

测试实例的的规格为:8CN + 8DN + 2CDC 单CN节点规格:32核128GB 单DN节点规格:32核128GB * 单CDC节点规格:16核32GB

此处咱们将DN节点的数量由8个调整为16个,进行复测,看看性能是否仍然能够达到如上规范。以TPS为例,应用上篇中的48张表的sysbench数据导入进行测试

sysbench --config-file='sysb.conf' --create-table-options='dbpartition by hash(`id`) tbpartition by hash(id) tbpartitions 8'  --tables='48' --threads='48' --table-size='10000000' oltp_point_select prepare

测试论断

TPS只能达到19w+/s的程度,链路呈现比拟高的提早,如下图所示:

并且此时多路归并线程曾经满负荷运行,如下图所示:

并且此时多路归并线程曾经满负荷运行,如下图所示:

这是因为零碎默认只开启了单级归并,如下图所示:

随着DN数量的增多,参加归并排序的队列长度也会随之增大,性能则相应呈现衰减(另附多路归并外围代码:LogEventMerger.java),解决办法是开启多级归并,通过“分治”和“并发”来解决性能瓶颈,如下所示:

开启多级归并后,吞吐能力复原至失常程度,如下所示:

如上是多级归并的逻辑示意图,反映到理论的运行时拓扑,多级归并能够是线程级的,如下所示:

也能够是过程级的,如下所示:

前者用来解决较小规模集群(<=64DN)的性能瓶颈,后者用来解决更大规模集群(>64DN)的性能瓶颈。当DN数量不太多时(如32个),优先思考通过晋升单个节点的配置,采纳线程级别的多级归并来晋升性能,防止不必要的网络传输开销;当DN数量十分多时(如256个),很难靠单个节点承当计算、内存或网络资源的压力,此时能够通过过程级别的多级归并来晋升性能。当然,多级归并也不是“银弹”,全局Binlog会有一个Global Merge Point,当DN数量足够多时,依然会有单点瓶颈问题(尤其是网络瓶颈),能够通过多流Binlog进行解决,咱们的后续文章会对PolarDB-X的多流Binlog进行介绍。

上面表格是oltp_insert场景下的测试数据,分隔行以上的局部是单级归并可撑持的压力范畴,当QPS高于20w之后,会呈现显著的提早,开启多级归并后,QPS达到35w时,延迟时间依然放弃在1s以内

流水线&并行

全局Binlog数据流生产线,采纳了相似SEDA(Staged Event-Driven Architecture)的架构,共划分为6个Stage,相邻的Stage之间以及Stage外部组件之间,通过队列进行串联,每个Stage外部通过异步或多线程并行技术对数据处理进行减速。

Extract Stage Extract Stage用于实现一级排序,波及binlog解析、数据整形、元数据保护、ddl解决等,每个DN对应一个解决线程,单个DN最大可反对的EPS吞吐为25w/s,采纳的性能优化伎俩次要在内存治理和缓存治理层面,见下文的内存应用优化章节。

Merge Stage Merge Stage用于实现全局排序,保障全局Binlog中数据操作的线性统一,采纳的性能优化伎俩次要是多级归并,上文曾经有详细描述,此处不再赘述。

Collect Stage Collect Stage用于实现事务的合并,将分布式事务的扩散到各个DN的binlog数据进行排序和合并,采纳的性能优化伎俩次要有: 通过RingBuffer,进行并行处理,大大晋升合并速度 反对“预序列化”,某个事务合并实现之后,如果事务大小小于设定阈值(如果太大,预序列化可能会占用大量内存),会间接构建Transmit Message并序列化,为Transmit Stage加重压力(Transmit Stage序列化操作是单线程解决)

Transmit Stage Transmit Stage用于实现Task和Dumper之间的网络传输,应用Grpc构建Data Stream,采纳的性能优化伎俩次要有: Batch模式,多个事务的event封装为一个数据包,晋升吞吐率 Single模式,自动检测大事务,单个事务独立封装数据包,防止内存溢出 异步解决,耗时的序列化和反序列化操作放到独立线程 动静反压管制,防止内存溢出。

Write Stage Write Stage处于数据流的末端,用来构建终态的全局Binlog文件,次要采纳了3种优化伎俩: 并行构建 应用RingBuffer构建缓冲区,将构建binlog event的操作(创立event、更新event内容、计算CRC32校验值等)并行化解决,解决单线程瓶颈。 间接内存 采纳间接内存进行IO操作,减小用户态和内核态上下文切换 * Write Cache 引入写缓存,并保障缓存大小为page页的整数倍,升高IO频率和晋升page命中率。

Backup Stage Backup Stage用于实现全局Binlog文件的备份,反对Dumper之间的主备复制,和到中心化存储(如OSS)的冷备份,次要的性能优化伎俩是多线程上传和下载。

内存应用优化

全局Binlog零碎是一个数据密集型的零碎,对内存资源的治理是一个外围关键问题,上面介绍全局Binlog在内存优化层面的优化伎俩。

事务数据长久化 排序是全局Binlog零碎最外围的性能,在对数据进行解决时,须要将某个事务的binlog数据全副接管完之后,能力进行排序操作,当遇到大事务或者超长事务空洞时,如果没有数据的长久化机制,很容易把内存撑爆,导致full gc或内存溢出,影响链路的失常运行。对此零碎设计了反对内存和磁盘混合存储的Hybrid KV Store,用来解决内存不足的问题,数据链路的几个外围Stage深度依赖该Store,如下所示

KV Store的次要特点有: 反对大事务swap到磁盘,动静监测事务大小,超过指定阈值后,该事务数据swap到磁盘 反对大事件swap到磁盘,遇到超过指定阈值的binlog event,间接保留到磁盘 反对动静监测内存使用率,超过指定阈值后,主动将存量数据和新增数据swap到磁盘 全磁盘存储相比纯内存存储,性能大略有20%的升高。

元数据长久化 元数据是全局Binlog零碎的外围命根子,在库表十分多的状况下,元数据会耗费大量内存,能够多达10几GB,影响数据链路的失常运行。举例来说,48w张数据表占用了靠近8G的内存,如下所示:

零碎提供了针对元数据的长久化机制,开启长久化能力之后,元数据会被序列化并保留至RocksDB,并反对在内存和磁盘之间的动静Swap,波及元数据长久化的参数为: meta.persist.basePath : 配置元数据长久化目录 meta.persist.schemaObject.switch : 是否开启元数据长久化。

接下面例子,当开启元数据长久化之后,内存占用大幅升高到只有240M,如下所示:

优化内存调配 尽管KV Store提供了swap机制,保障了内存不够用时数据能够转储到磁盘,但毕竟会对性能造成影响,最优的策略还是尽量减少内存的占用,零碎提供的次要优化伎俩有: 不同stage对内存的要求不一样,正当管制队列缓冲区的大小,躲避不必要的数据沉积 躲避不必要的数据复制,如针对ByteString的应用 ByteString.copyFrom办法能够替换为UnsafeByteOperations.unsafeWrap办法,免去字节数组copy操作 ByteString.toByteArray办法能够替换为DirectByteOutput.unsafeFetch办法,免去字节数组copy操作

总结

本篇从技术原理的角度,对全局Binlog的一些外围的性能优化伎俩做了简要的介绍,通过SEDA流水线架构实现了异步和并行处理,通过多级归并解决了集群规模扩张时的性能衰减问题,通过一系列的内存优化伎俩保障了数据链路的高效运行,这些优化措施,保障了全局Binlog在TPCC 150w tpmC的压力下,提早仍能够管制在1s以内(参见:性能白皮书)

当然,全局Binlog在架构上也有人造的劣势,其在应答超大规模集群时存在单点瓶颈,PolarDB-X的多流Binlog通过就义事务的完整性解决了单点问题,在应答超大规模集群时,可能保障性能的线性晋升,敬请关注咱们的系列文章。

原文链接

本文为阿里云原创内容,未经容许不得转载。