RDS AliSQL 面向 Binlog 的性能优化大揭密(下)——强效瓶颈消
一、Binlog Parallel flush 优化
优化背景
Binlog Commit 瓶颈分析
当我们在MySQL官方代码上做sysbench时,可以看到如上图的耗时占比。在低并发下,redo log和binlog的IO操作耗时占大头,然而在高并发下,耗时占比的大头是 flush binlog操作,IO操作只占到 35% 左右。
这是因为MySQL的binlog group commit机制合并的是事务间的 IO,在并发数很少的时候,只能合并几个事务的 IO 操作,在并发数很多的时候,可以合并几百个事务的 IO 操作,差距非常明显。
从耗时占比可以看出,在高并发下,主要的性能瓶颈来自于flush binlog的操作。
MySQL 的 Binlog Flush 机制
MySQL的binlog flush主要包含上述步骤,总的来说可以分为两种操作。
- 第2步和4步是一些解析和处理的动作,我们可以将其称为:事务准备binlog的操作;
- 第3步和5步是写文件动作(只是写到page cache,并非真 IO),可以称为:事务写binlog的操作。
接下来我们从耗时和可并发性这两个维度来研究这两类操作。
耗时
事务准备binlog的操作在整个flush流程中耗时占比很大,其中GTID event对象的生成,binlog cache的解析和填补,都是繁重的CPU工作;
事务写binlog的操作,仅仅是从MySQL内存到page cache的一个内存拷贝操作,速度很快,耗时很短。
可并发性
事务准备 binlog 的操作是可以并发执行的,每个事务准备自己的 binlog,不会有任何的冲突,因为 GTID 和逻辑时钟还有事务在 binlog 中的位置早已在第一步分配好;
事务写 binlog 的操作是不可并发的,binlog 是一个事务型日志,这意味着在 binlog 文件中,一个事务的 binlog events 必须连续的放在一起。因此,写 binlog 文件时必须串行的,一个接一个的写。
不幸的是,MySQL 代码实现时,将上述两种操作混在了一起,导致整个 flush 阶段的两种操作,全部串行执行。这导致在高并发下,flush binlog 成为瓶颈。
我们的优化目标是,将 flush binlog 中准备 binlog 的部分拆出来,让每个事务并行执行,以优化性能。
Binlog Flush 的并行化改造
为了将第一类操作改造的可并行,引入一段预先分配好的内存空间叫做 binlog buffer。Flush binlog 阶段,每个事务串行在 binlog buffer 中分配好空间后,就可以并行的准备自身的 binlog 内容,并写入 buffer 中。随后,再由后台线程将 binlog buffer 按顺序写进 binlog 文件。
并行 Flush Binlog 的流程
在 binlog parallel flush 架构下,flush binlog 的操作被分为了三个部分,分别是串行部分,并行部分和异步线程部分。
串行部分
- 分配 buffer 中的空间;
- 分配 Gtid 和 逻辑时钟。
并行部分
- 生成 GTID event 并写到 Binlog buffer;
- 解析 binlog cache 中的 binlog events,填充 postion 和 checksum,并写入 binlog buffer 中。
异步线程部分
- 由异步的 writer 线程,负责将 binlog buffer 中准备好的部分写入 binlog 文件;
- 由异步的 Syncer 线程,负责将 binlog 文件持久化。
合并 Redo Log IO
前面我们对于 binlog flush 的并行优化,大大提升了事务的并发度,这对于 flush binlog 来说是大大缓解了瓶颈。但是对于 redo log 来说,并发执行把原先由 binlog group commit 合并在一起的的 redo IO 又分开了,会导致了较严重的 log write up to 热点。
为此,我们继续保留 MySQL 原有的 binlog group commit 优化,在组内由 leader 串行的执行每个事务的 flush 动作,在组与组之间并发执行 flush。
Binlog Parallel Flush 架构
上图是 binlog parallel flush 的最终架构。进入提交阶段的事务会被分成多个组,每个组有一个 Leader,Leader 负责执行本组所有事务的提交操作,包括写 binlog buffer,sync redo 和等待异步线程的操作。根据 sync_binlog 参数的取值,leader 会选择等待 writer 或 syncer 线程。多个事务组之间,不同组的 leader 可以并发的执行 flush 动作。
性能测试
上图中红色曲线是 MySQL 的性能表现,橙色曲线是 AliSQL 使用 binlog parallel flush 后的性能表现。可以看到,在使用双一配置的高可靠场景,使用 parallel flush 可将峰值性能提升 56%;在使用 (0, 2) 的高性能场景,即 sync_binlog = 0,innodb_flush_log_at_trx_commit = 2 的场景下,峰值性能提升幅度可以达到 61%。
二、Binlog in redo V2 优化
上篇提到的 binlog in redo 优化是针对双一场景的 IO 优化,下篇提到的 parallel flush 优化可以应用于双一和(0,2)两种配置。也就是说,在双一场景下,两种优化都可以发挥作用,因此在双一场景下,我们同时使用两种优化思路,完成了 binlog in redo V2 优化。
Binlog in redo V2 架构
上图是 binlog in redo 优化与 parallel flush 优化结合后的架构图。进入提交阶段的事务会被分成多个组,每个组有一个 Leader,Leader 负责执行本组所有事务的提交操作,包括写 binlog buffer,写事务的 binlog 到 redo log,持久化 redo log 的操作。
随后,事务组结束,每个事务并发的进行自己的 innodb commit。由于 binlog 已经有 redo log 来保护,因此事务提交不需等待 sync 线程完成,原理上来讲也不需等待 writer 线程完成,不会有 crash safe 问题。但是许多用户会订阅 binlog,如果事务已经返回成功,但是 binlog 中还暂时读不到这个事务,可能会影响下游的逻辑判断。
因此,我们将事务返回成功前是否等待 writer 线程,作为一个用户可配置的参数,选择等待则相关逻辑与优化前不会有任何变化,不等待性能更好。
Crash Recovery 机制
Binlog in redo V2 架构中,当实例 crash 后,binlog 中的内容有可能丢失,因此需要在重启后的 recovery 过程中补齐。
与 V1 不同,在 V2 中 binlog flush 阶段是并行的,这导致 binlog 在 redo log 中可能是乱序的。因此,我们引入一个 recovery buffer,恢复过程中先将 binlog 写到 buffer 中,再将 buffer 写入 binlog 文件中。这样就可以解决乱序的问题。
性能测试
Binlog in redo V2 能够全方面的优化双一配置下实例的性能。可以看到,在各种并发度下,实例的性能都有显著的提升。
总结
通过上篇讲述的合并 IO 和本篇讲述 binlog 并行写这两种思路,我们对 binlog 提交阶段做了全方位的优化。AliSQL对比MySQL有了全方位,全场景的性能领先。
以上是在 sysbench 的两种不同的业务脚本下,MySQL 和使用 binlog commit 优化的 AliSQL 的性能曲线对比。可以看到,无论是高可靠的(1,1)配置,还是高性能的(0,2)配置,AliSQL 都有着显著的性能提升。
如果您使用 (1, 1) 的高数据可靠性配置,推荐直接使用 Binlog in redo V2 优化,能够适应各种业务压力,带来六成左右的写性能提升;如果您使用 (0, 2) 的高性能推荐使用 Binlog parallel flush 优化,能够讲写性能峰值再提升流程左右。
通过本系列提到的优化手段,AliSQL 在各业务场景,各种数据可靠度,各种并发度下,都取得显著的性能提升。我们能够在同样的资源下,成倍的增大 qps,可以显著降低用户的使用成本。本系列中的所有优化,都会在 RDS MySQL 8.0 的最新版本中上线,欢迎大家使用。
本文转载自公众号:阿里云数据库