RDS AliSQL 面向 Binlog 的性能优化大揭密(上)— 极致 IO 优化

vicsue
发布于 2022-12-28 11:18
浏览
0收藏

RDS AliSQL简介

AliSQL是阿里云RDS团队深度优化的独立 MySQL 分支,除了社区版的所有功能外,AliSQL提供了类似于MySQL企业版的诸多功能,如企业级备份恢复、审计日志、线程池、Binlog in Redo 等。RDS MySQL使用AliSQL内核,为用户提供了MySQL所有的功能,同时提供了企业级的安全、备份、恢复、监控、性能优化、只读实例、Serverless等高级特性。

Binlog 是谓何物 

Binlog (Binary log)是 MySQL Server 层维护的一种二进制日志,以事务级别记录了对数据库的所有修改操作。

事务级别是指:一个事务的日志,是在事务提交时被写入 binlog 文件中的。具体来说,事务在执行过程中,会不断的生成 binlog events,暂存在 session 级别的 binlog cache 中;事务提交时,会一次将 binlog cache 中所有内容写到 binlog 文件中。

Binlog 的一致性保证

在 MySQL 中,binlog 是为数不多可以做到 “准” 的日志(另一个 “准” 的是 redo log),即保证日志中存在的修改,数据中一定存在,反之亦然。也正因为它可以做到 “准”,MySQL 才可以基于 binlog 做复制和备份。

Sync_binlog

Sync_binlog 是 binlog 相关的一个重要参数,它控制了 binlog 的刷盘方式。在了解这个参数的具体含义前,先要了解一下 Linux 系统的 page cache 机制。

Page Cache

我们知道计算机的存储介质笼统上说分三层,其读写速度从高到低分别是 cpu cache,memory 和磁盘。Linux 内核为了优化文件的读写速度,在磁盘之前加入了一层缓存,叫做 page cache。

Page cache 本质上是由操作系统直接管理的一块内存空间,应用程序写文件时,会先写到 page cache 上,然后操作系统择机进行刷盘。当然,这样的机制会带来的一个隐患,即当机器 crash 时,没有从 page cache 刷到磁盘的数据会丢失。很多应用程序不能容忍这样的数据丢失,因此操作系统也为应用程序提供了主动刷盘的接口。

Flush 和 Sync 的含义

在 binlog 中,写 binlog events 到 binlog 文件的 page cache 的行为被称为 flush,binlog 文件刷盘的行为被称为 sync。需注意,这个叫法是 binlog 语境中的习惯,redo log 中的叫法又有不同。

参数含义

Sync_binlog 参数控制了 binlog 刷盘的频率,当配置为 0 时,binlog 不主动进行刷盘;当配置为 n (n > 0) 时,binlog 每 n 个事务一起刷盘。

当 sync_binlog 设置为 1 时,每个事务提交时,都会主动 binlog 刷盘,这种配置下,不会出现已提交的事务的 binlog 丢失的现象,基于 binlog 的复制和备份都能保证可靠性。配合 innodb 中的参数innodb_flush_log_at_trx_commit = 1(每个事务 redo log 都主动刷盘),可以达到数据和日志完全一致的高可靠性,这种配置俗称双一,下文讨论过程中,我们都默认实例处于双一配置。

两阶段提交

在双一的配置下,MySQL 使用两阶段机制,保证 Binlog 与 innodb 的崩溃一致性(crash safe)。也就是说,无论实例在什么时间 crash,重启后通过 crash recovery,Binlog 与 innodb 都可以达到一致性状态。

RDS AliSQL 面向 Binlog 的性能优化大揭密(上)— 极致 IO 优化-鸿蒙开发者社区

上图是一个两阶段提交的示意图,所谓两阶段,就是将事务提交分为 prepare 和 commit 两个阶段,将事务分为了 active 和 prepared 两种状态。

Prepare 阶段

Prepare 阶段最主要的动作是将事务从 active 状态置为 prepared 状态,并将 undo 回滚段也置为 prepared。这个动作的意义是,标记事务所有操作都已经结束,在 crash recovery 过程中,处在 prepared 状态的事务是可以回滚也可以提交的(active 状态事务只能回滚不能提交)。有了这个状态,redo log 和 binlog 才能在 commit 阶段和 crash recovery 过程中相互协调。

需注意,这个阶段修改事务状态的动作是内存态的,只有记录 undo 回滚段的 redo log 刷盘,事务状态才算被持久化到磁盘。这是由于 innodb 的 WAL 机制导致的,有兴趣的同学可以去查阅相关资料学习。

在早期的 MySQL 上,prepare 阶段还会进行 redo log 的刷盘操作。但随着 binlog group commit 优化的诞生,刷盘操作的性能有了巨大提升,因此 redo log 的刷盘操作被移到了 commit 阶段的 binlog group commit 流程中以优化 IO 性能。这部分细节将在 binlog group commit 优化的章节中详细讲解。值得一提的是,这个改动和 AliSQL 的早期成员印风有很大关联,他在性能测试中首先发现了这个问题,并将 idea 和 code patch 一并贡献给了 MySQL 官方。对这段历史感兴趣的可以参照 http://bugs.mysql.com/bug.php?id=73202。

Commit 阶段

Commit 阶段除了事务提交的操作外,还包括 redo log 的刷盘,binlog 的写入和刷盘。这里对这两个日志文件的操作顺序很重要。

Binlog 刷盘,就意味着事务已经可以被传给从库,此时就算实例没有完成 commit 就 crash 了,crash recovery 过程中也需要能够提交此事务,这样才不会造成主从不一致。而事务在 crash recovery 过程中事务可提交,就需要事务的 prepared 状态已经被持久化到磁盘,也就是 redo log 已经刷盘。因此必须先将 redo log 刷盘,才可以写 binlog 文件并刷盘。因此,在提交阶段,以 binlog 刷盘完成为标志,刷盘完成前发生 crash,事务回滚,刷盘完成后发生 crash,事务提交。

在完成 binlog 刷盘后,事务就可以提交。提交时会把事务从 prepared 状态改为 commit 状态,这个操作是需要写 redo log 的,但是事务完成提交并不需要等待这个 redo log 刷盘。刚才说过,只要 binlog 刷盘完成,就可以认为这个事务已经持久化的提交了,因此不必再等待一次 redo log 刷盘,直接返回事务提交成功即可。

各时间点 Crash 分析

当实例发生 crash 时(我们这里假设一种最严重的 crash——机器故障导致重启,所有page cache全丢)

  • 如果 redo log 刷盘没有完成,事务的 prepared 状态还没有刷盘,还是 active 的状态,直接回滚即可。
  • 如果 redo log 刷盘完成,binlog 刷盘没有完成,重启时事务是 prepared 状态,在启动阶段这样的事务又叫未决事务,可以选择提交或回滚。通过扫描 binlog,发现事务没有完整的存在于 binlog 中,因此选择回滚;
  • 如果 binlog 完成刷盘,重启时事务是 prepared 状态,binlog 中也完整的记录了这个事务,必须选择提交。

IO 性能问题

上述机制中,为了保证 binlog 的完整和 crash safe,每个事务在提交时,都需要等待两次 IO 操作,一次 redo log,一次 binlog。这导致了极大的性能问题。

RDS AliSQL 面向 Binlog 的性能优化大揭密(上)— 极致 IO 优化-鸿蒙开发者社区

为了优化性能,MySQL 官方引入了 Binlog Group Commit 优化来合并 IO 操作。

Binlog Group Commit 优化

BGC (Binlog Group Commit)优化是 MySQL 官方做的,针对 binlog 提交阶段的一个性能优化,目的是合并 IO 操作。

合并 IO 操作

在文件系统中,IO 操作是成批进行的,在一般情况下,10 次 1KB 的 IO 要比 1 次 10KB 的 IO 慢得多,这就是合并 IO 操作的意义。这个思想在 MySQL server 和 innodb 的众多设计中都有体现。

Binlog Group Commit 流程

RDS AliSQL 面向 Binlog 的性能优化大揭密(上)— 极致 IO 优化-鸿蒙开发者社区

Group Commit 顾名思义,就是把若干事务组成一个组,一起提交。MySQL 将 Binlog 提交阶段分成 flush stage,sync stage 和 commit stage。

Flush stage

RDS AliSQL 面向 Binlog 的性能优化大揭密(上)— 极致 IO 优化-鸿蒙开发者社区

Flush stage 中包含 sync redo 和 flush binlog。进入提交阶段的事务,首先会等待进入 flush stage。为什么会等待呢?具体来说,正在 flush stage 中进行 sync redo 和 flush binlog 的 group,会持有一把 lock_log 锁,这样就阻塞住了新来的事务,它们需要等待当前 group 结束释放 lock_log 锁,才能进入 flush stage。

在等待的过程中,系统可以积攒很多等待进入 flush stage 的事务,把他们全部组成一个 group。Group 中的第一个事务作为 leader,flush stage 中所有的操作都由 leader 的线程完成。这样一来,原先多个事务进行多次 sync redo 的操作,就变成了一次 sync redo,最大限度合并了 IO 操作。

MySQL-80 上 redo log 引入了无锁化设计后,sync redo 的动作变为后台线程完成 。Flush stage 中主动进行 sync redo 的操作就变为了等待后台线程完成 sync redo。这种设计下,group commit 也非常有意义,因为等待后台线程是需要拿锁的,很多事务独立等待时,会造成严重的锁冲突。在这种情况下 group commit 实质上是合并了锁等待,避免了锁冲突。

Sync stage

Sync stage 中包含 sync binlog 的操作。完成 flush stage 的 flush group,在进入 sync stage 前还需要再次组队,由几个 flush group 组成一个 sync group,进一步合并 IO。

RDS AliSQL 面向 Binlog 的性能优化大揭密(上)— 极致 IO 优化-鸿蒙开发者社区

Sync stage 组队的机制和 flush stage 类似,由正在 sync binlog 的 sync group 持有一把 lock_sync 锁,准备进入 sync stage 的 flush group 在等待这把锁的过程中积攒,组成一个 sync group。当上一组 sync binlog 完成 lock_sync 被释放,由本组第一个 flush group 的 leader 作为新组的 leader,完成本组的 sync stage。

Commit stage

Commit stage 中包含事务的提交操作。Commit stage的目的并非合并 IO,而是为了让 binlog 中事务的顺序和事务真实提交的顺序一致。

在开启了 binlog_order_commits 或者 clone 过程中需要依赖提交顺序时,完成 sync stage 的事务会再次进入 commit stage;没有开始顺序提交的实例,commit stage会被跳过,每个事务自己进行事务提交。


Binlog in Redo 

 极致 IO 优化

BGC 优化通过分组的方式,可以将很多小的 IO 操作合并,但是事务提交需要等待的 IO 次数(sync redo 和 sync binlog 两次 IO)并没有改变。为了进一步优化 IO,AliSQL 引入了 Binlog in redo 优化,旨在将事务提交阶段的两次 IO 操作合并为一次 IO 操作。

而具体如何实施优化,以及如何验证优化效果,会在后续文章中进行讲述。

Binlog in redo 架构

RDS AliSQL 面向 Binlog 的性能优化大揭密(上)— 极致 IO 优化-鸿蒙开发者社区

前章曾提到过 Innodb 的 WAL 机制,innodb 写任何数据前,都需要先写下数据的 redo log,随后提交时,只需要同步等待 redo log 刷盘,数据后续异步的刷盘即可。即便出现了 crash,只要 redo log 完整,数据也可被准确无误的恢复。

Binlog in redo 顾名思义,将 binlog 写到 redo log 中。在这个架构下,binlog 对于 redo log 来说也是一份“数据”,受到 redo log 的保护;binlog 的刷盘由异步线程完成,事务提交过程中,无需等待 binlog 刷盘。如果发生 crash,根据 redo log 恢复binlog 文件即可。 这样一来,提交阶段需要等待的 IO 次数就变为了 1 次,能够大幅提升性能。

Crash Recovery 机制

RDS AliSQL 面向 Binlog 的性能优化大揭密(上)— 极致 IO 优化-鸿蒙开发者社区

Binlog in redo 架构中,当实例 crash 后,binlog 中的内容有可能丢失,因此需要在重启后的 recovery 过程中补齐。Server 层中实现了一个 Binlog applier 去补齐 binlog。

在 crash recovery 期间 apply redo 时,每次在 redo log 中读到 binlog event,就解析他的 binlog position,如果这个位置已经存在于 binlog文件中,就不需要回填,如果这个位置不在了,就需要回填一下。

性能优化效果

测试环境:32Core, 64G Ram, ESSD存储。

测试工具:sysbench

oltp_update_non_index

RDS AliSQL 面向 Binlog 的性能优化大揭密(上)— 极致 IO 优化-鸿蒙开发者社区

RDS AliSQL 面向 Binlog 的性能优化大揭密(上)— 极致 IO 优化-鸿蒙开发者社区

oltp_insert

RDS AliSQL 面向 Binlog 的性能优化大揭密(上)— 极致 IO 优化-鸿蒙开发者社区

RDS AliSQL 面向 Binlog 的性能优化大揭密(上)— 极致 IO 优化-鸿蒙开发者社区

oltp_write_only

RDS AliSQL 面向 Binlog 的性能优化大揭密(上)— 极致 IO 优化-鸿蒙开发者社区

RDS AliSQL 面向 Binlog 的性能优化大揭密(上)— 极致 IO 优化-鸿蒙开发者社区

结论

Binlog In Redo 功能在不损失可靠性的前提下,减少了 1 次IO。在不超过 256 并发的情况 下,Binlog In Redo 功能对性能的提升和延迟的降低都非常显著。但是对于大并发的场景,性能提升效果有限。这是由于大并发下,BGC 合并 IO 的作用更加明显,Binlog 提交期间的瓶颈来到了串行化的 Flush binlog。为了优化这一瓶颈,AliSQL 引入了 Binlog Parallel Flush 优化,最终使实例达到全业务场景的大幅性能提升。

欲知后事如何,请听下回分解。


本文转载自公众号:阿里云数据库

分类
标签
已于2022-12-28 11:18:45修改
收藏
回复
举报
回复
    相关推荐