• 进入"运维那点事"后,希望您第一件事就是阅读“关于”栏目,仔细阅读“关于Ctrl+c问题”,不希望误会!

GH-OST:原子切换原理

MySQL Tools 彭东稳 7年前 (2018-06-11) 22842次浏览 已收录 0个评论
Github 开源的 GH-OST 工具也已经有几年了,用过 GH-OST 工具的就会知道,其最后也需要有一个 cut-over 的动作,就是原表跟新表的切换。作者提供了一个原子可靠的切换方式,并且写了三篇文章来阐述 cut-over 操作过程,其中还对比了 Facebook OSC 切换算法。

这里主要说第三篇文章:解决非原子表切换问题,使其成为原子表切换

我们提供的解决方案仅基于两个连接(之前是三个,采用乐观方式)。这里我们假设 GH-OST 工具的连接将是 C10,C20;正常的应用程序连接是 C1..C9,C11..C19,C21..C29。

  • 连接 C1..C9:正常 DML 操作 tbl 表
  • 连接 C10:CREATE TABLE _tbl_del (id int primary key) COMMENT=’magic-be-here’
  • 连接 C10:LOCK TABLES tbl WRITE, _tbl_del WRITE
  • 连接 C11..C19:新进来的请求,或许有 DML 和 SELECT 都会因 LOCK 而等待
  • 连接 C20:RENAME TABLE tbl TO _tbl_del, _tbl_gho TO tbl,会因 LOCK 而等待
  • 连接 C21..C29:新进来的请求在 tbl 上发出查询,但由于 LOCK 和由于 RENAME 而在队列中等待而被阻塞
  • 连接 C10:检查 C20 的 RENAME 是否正在执行(在 show processlist 中查找被阻止的 RENAME)
  • 连接C10:DROP TABLE _tbl_del,什么也没有发生,tbl 仍然锁定,所有其他连接仍然被阻止
  • 连接C10:UNLOCK TABLES

这里注意了!此时存在 C1..C9、C11..C19、C21..C29、C20 都在等待锁,无固定先后顺序。此时,RENAME 是第一个执行的,ghost 表被交换代替 tbl,然后 C1..C9、C11..C19、C21..C29 都可以运行在新的 tbl 表上。

注意点:

  1. 提前创建 _tbl_del 表是为了防止 tbl 表过早交换,导致数据丢失。
  2. 在同一个会话下执行了 write lock 后是可以进行 dml 操作和 drop 表的,这个 lock 本身的机制。
  3. 无论 DML 和 RENAME 谁先执行,被阻塞后 RENAME 总是优先于 DML 语句被执行,这个很重要。

在 gh-ost issue #82,有哥们提出一个问题就是怎么保证 RENAME 总是会优先于 DML 被执行,因为这个很关键。如果不能保证 RENAME 优先于 DML 被执行,就有数据丢失的风险。作者回答说他并没有保证,这个确实需要依赖 MySQL 内部行为,并且这种行为已经在 MySQL 中存在十年之久,并且与 MySQL 工程师讨论过这个问题。

整个 cut-over 方案的出现主要还是因为 MySQL 的局限性,你无法在同一个会话中 LOCK TABLES 后再去 RENAME TABLE(从 MySQL 8.0.13 版本开始支持同一个会话中 LOCK TABLES 后执行 RENAME TABLE),其他操作是可行的,比如 C10 还兼顾应用积压的 binlog 日志回访操作,这点是很重要的。

如果执行失败会发生什么?

  • 如果 C10 的 CREATE 执行错误,GH-OST 不会继续往下执行了。
  • 如果 C10 的 LOCK 语句出现错误,GH-OST 不会继续,表未锁定,应用程序继续正常运行。
  • 如果 C10 在 C20 执行 RENAME 前挂掉了:

A. C10 锁被释放,查询 C1..C9,C11..C19 立即在 tbl 上运行。

B. C20 的 RENAME 立即失败,因为 _tbl_del 表存在。

C. 整个操作都失败了,但没有什么可怕的事情发生,有些查询被阻止了一段时间,我们需要重试。

  • 如果 C10 在 C20 执行 RENAME 被阻塞时挂掉了:大部分与上述类似,锁释放,则 C20 执行 RENAME 失败(因为 _tbl_del 表存在),则所有查询恢复正常操作。
  • 如果 C20 在 C10 DROP 表之前挂掉了,GH-OST 会捕获该错误并让 C10 按计划进行:DROP,UNLOCK。并不会有什么可怕的事情发生,一些查询被阻止了一段时间,我们需要重试。
  • 如果 C20 在 C10 DROP 表之后但在解锁之前挂掉了,则与上面情况相同。C10 线程挂掉后自然锁就释放了,然后 C20 的 RENAME 会在 DML 之前操作。
  • 如果 C10 和 C20 都挂掉了,没问题:LOCK 被清除,RENAME 锁被清除。 C1..C9,C11..C19,C21..C29 可以在 tbl 上自由运行。

无论发生什么事,在操作结束时,我们都会寻找 ghost 表。它还在,然后我们知道操作失败了。它不在,然后它被重命名为 tbl,并且该操作以原子方式工作。

关于失败的一个注意事项是清理 _tbl_del,也许只是让它存在下去,避免重新创建它;或者如果你喜欢,你可以删除它。

对应用程序的影响?

应用程序连接保证被阻止,直到交换 ghost 表或直到操作失败。在前者中,他们继续在新表上进行操作。在后者中,他们继续在原表上进行操作。

对复制的影响?

复制从库只能看到 RENAME,二进制日志中没有 LOCK。因此,复制会看到一个原子两表交换,没有表中断。

为什么需要锁定源表?

我们知道,RENAME 操作是 in-place 的,无需重建表并允许并发的 DML 操作。但因为 GH-OST 是通过异步方式应用二进制日志内容,有可能二进制日志有一些积压。因此,我们锁定源表,以确保我们完整应用完二进制日志相关的改动。

RENAME TABLES under LOCK TABLES

值得注意的是,从 MySQL 8.0.13 版本开始,支持了在 LOCK TABLES 之上的 RENAME TABLE 操作,专门为 GH-OST 做了一些设计支持。see: https://mysqlserverteam.com/the-mysql-8-0-13-maintenance-release-is-generally-available/

简单翻译过来就是,允许在 LOCK TABLES 之上执行 RENAME TABLE (WL#9826)操作,由 Dmitry Lenev 实现了原子 RENAME TABLE。这项特性已经请求 Shlomi Noach 在 GH-OST 工具的最后 cut-over 阶段使用。此类工具使用现有的旧表与用户并行构建一个新的,经过更改的表。当新的表数据是最新的时候,需要做两个表的原子交换。现在可使用 LOCK TABLES tbl WRITE 来实现(停止更新旧表),然后同一个会话执行 RENAME TABLE tbl TO _tbl_del, _tbl_gho TO tbl(交换时 _tbl_gho 是最新的)。

NOTE

关于表锁定看 “MySQL表锁定(Table Locking)


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (0)
[资助本站您就扫码 谢谢]
分享 (0)

您必须 登录 才能发表评论!