https://github.com/github/gh-ost
2016年8月份,shlomi-noach在GitHub Engineering发文宣布gh-ost开源。gh-ost是什么?一个不依赖触发器实现的在线表结构变更工具.
对于数据库运维人员来说,MySQL的大表表结构变更一直都是个麻烦事,为了尽量不影响业务,业内常用的解决方案无外乎三种,一是利用Percona的pt-online-schema-change,Facebook的OSC等三方工具,二是在备库修改通过切换实现滚动变更,三则是升级MySQL到5.6/5.7通过官方Online DDL实现部分变更。然而,引入触发器带来的锁竞争问题,主备切换带来的附加成本以及Online DDL的局限性都不让DBA省心。
gh-ost的设计号称无触发器,可监控,可动态调整暂停等,更重要的是切换方案的优秀设计。下面就介绍下其实现原理和cut-over(新旧表切换)的详细过程。
原理:
gh-ost不依赖于触发器,是因为他是通过模拟从库,在row binlog中获取增量变更,再异步应用到ghost表的。
两种功能模式:
1.连接主库直接修改
直连主库
主库上创建ghost表
新表(ghost表)上直接alter修改表结构
迁移原表数据到新表
拉取解析binlog事件,应用到新表
cut-over阶段,用新表替换掉原表
2.连接从库间接应用到主库
连接从库
校验完后,在主库创建新表
迁移原表数据到新表
模拟从库的从库,拉取解析增量binlog应用到主库
cut-over阶段,用新表替换掉原表
两者不同的点就在于,通过连接从库来进行变更,对主库的性能影响最小
变更流程:
以直连主库修改为例,详细介绍gh-ost做了哪些操作:
1.模式:
根据参数配置可选三种变更模式
除了直连主库和连接从库以外,还有连接从库做变更测试
2.校验:
测试db是否可连通,并且验证database是否存在
确认连接实例是否正确
权限验证 show / gh-ost / grants for current_user()
binlog验证,包括row格式验证和修改binlog格式后的重启replicate
原表存储引擎,外键,触发器检查,行数预估等
3.初始化:
初始化stream的连接,添加binlog的监听
初始化applier连接,创建ghosttable和changelogtable
判断是否符合迁移条件,写入结果到tablesInPlace channel
4.迁移:
图片名称
迁移过程中,row copy和binlog apply是同时进行,其中原则是binlog apply的优先级一定大于row copy操作的优先级。
5.状态展示:
Copy: 9451000/10000060 94.5%; Applied: 31; Backlog: 0/100; Time: 8m26s(total), 8m26s(copy); streamer: mysql-bin.000040:68321839; ETA: 29s
6.cut-over:
尝试lock原表
成功后,进行rename原子性操作,被block住
unlock原表,rename完成切换
后续中间表清理工作
迁移和切换的细节实现:
关于gh-ost的实现,这里只挑了rowcopy和binlog apply的顺序问题和rename过程做了详细解析。
数据迁移过程
在数据迁移的过程中,数据变量有三个,暂且分为,A:来自原表的rowcopy,B:binlog的apply,C:对原表的dml操作。
C操作会记录binglog从而触发B操作,所以B操作一定在C操作的后面,因此一般情况下,会有ACB,CBA两种组合,同时特殊情况如binlog apply延迟,则会有CAB这种组合。
分析三种组合之前要先了解gh-ost在sql改写方面是如何映射的:
RowCopy 原表操作 新表操作
select insert ignore into
BinlogApply 原表操作 新表操作
insert replace into
update update 新表(全行更新)
delete delete
在上述原则的基础上,我们再来逐个分析不同顺序组合的影响:
1.insert 操作
binlog是最权威的,gh-ost的原则是以binlog优先,所以无论任何顺序下,数据都是和binlog保持一致,如果rowcopy在后,会insert ignore,如果binlog apply在后会replace into掉。
2.update/delete 操作
一般情况下:
ACB组合,即对已经rowcopy过的数据,出现对原表的update/delete操作。这时候会全部通过binlog apply执行,注意binlog apply的update是对某一条记录的全部列覆盖更新,所以不会有累加的问题。
CBA组合,即对尚未迁移的数据,出现对原表的update/delete操作。这时候对新表的binlog apply会是空操作,具体数据由rowcopy迁移。
特殊情况下:
CAB组合,即先对原表更新完以后,rowcopy在binlog apply之前把数据迁移了过去,而在binlog event过来以后,会再次应用,这里有问题?其实结合gh-ost的binlog aplly的sql映射规则,insert操作会被replace重新替换掉,update 会更新对应记录全部行,delete 会是空操作。最终数据还是一致的状态。
cut-over过程:
在pt-osc或者online ddl中,最后的rename操作一般是耗时比较短,但如果表结构变更过程中,有大查询进来,那么在rename操作的时候,会触发MDL锁的等待,如果在高峰期,这就是个严重的问题。所以gh-ost是怎么做的呢?
gh-ost利用了MySQL的一个特性,就是原子性的rename请求,在所有被blocked的请求中,优先级永远是最高的。gh-ost基于此设计了该方案:一个连接对原表加锁,另启一个连接尝试rename操作,此时会被阻塞住,当释放lock的时候,rename会首先被执行,其他被阻塞的请求会继续应用到新表。
migrator.go:iterateChunks() 函数来确定何时开始cut-over
具体切换流程如下:
START
会话A
CREATE table tbl_old
防止rename过早执行
LOCK TABLES tbl WRITE, tbl_old WRITE
通过lock_wait_timeout设置为2s控制超时,超时失败会重试次数为配置default-retries,默认60次
新的请求进来,关于原表的请求被blocked
RENAME TABLE tbl TO tbl_old, ghost TO tbl , 同样被blocked
新的请求进来,关于原表的请求被blocked
检查是否有blocked 的RENAME请求,通过show processlist
会话A: DROP TABLE tbl_old
会话A: UNLOCK TABLES
RENAME SUCCESS
END
不同阶段失败后如何处理:
如果第一步失败,退出程序
如果会话A建表成功,加锁失败,退出程序,未加锁
rename请求来的时候,会话A死掉,lock会自动释放,同时因为tbl_old的存在rename也会失败,所有请求恢复正常
rename被blocked的时候,会话A死掉,lock会自动释放,同样因为tbl_old的存在,rename会失败,所有请求恢复正常
rename死掉,gh-ost会捕获不到rename,会话A继续运行,释放lock,所有请求恢复正常
作为一个DBA,大表的DDL的变更大部分都是使用Percona的pt-online-schema-change,本文说明下另一种工具gh-ost的使用:不依赖于触发器,是因为他是通过模拟从库,在row binlog中获取增量变更,再异步应用到ghost表的。在使用gh-ost之前,可以先看GitHub 开源的 MySQL 在线更改 Schema 工具【转】文章或则官网了解其特性和原理。本文只对使用进行说明。
说明:
1)下载安装:https://github.com/github/gh-ost/tags
2)参数说明:gh-ost –help
View Code
3)使用说明:条件是操作的MySQL上需要的binlog模式是ROW。如果在一个从上测试也必须是ROW模式,还要开启log_slave_updates。根据上面的参数说明按照需求进行调整。
环境:主库:192.168.163.131;从库:192.168.163.130
DDL过程:
复制代码
① 检查有没有外键和触发器。
② 检查表的主键信息。
③ 检查是否主库或从库,是否开启log_slave_updates,以及binlog信息
④ 检查gho和del结尾的临时表是否存在
⑤ 创建ghc结尾的表,存数据迁移的信息,以及binlog信息等
—以上校验阶段
⑥ 初始化stream的连接,添加binlog的监听
—以下迁移阶段
⑥ 创建gho结尾的临时表,执行DDL在gho结尾的临时表上
⑦ 开启事务,按照主键id把源表数据写入到gho结尾的表上,再提交,以及binlog apply。
—以下cut-over阶段
⑧ lock源表,rename 表:rename 源表 to 源_del表,gho表 to 源表。
⑨ 清理ghc表。
复制代码
gh-ost –user=”root” –password=”root” –host=192.168.163.131 –database=”test” –table=”t1” –alter=”ADD COLUMN cc2 varchar(10),add column cc3 int not null default 0 comment ‘test’ “ –allow-on-master –execute
有2个选择,一是按照1直接在主上执行同步到从上,另一个连接到从库,在主库做迁移(只要保证从库的binlog为ROW即可,主库不需要保证):
gh-ost –user=”root” –password=”root” –host=192.168.163.130 –database=”test” –table=”t” –initially-drop-old-table –alter=”ADD COLUMN y1 varchar(10),add column y2 int not null default 0 comment ‘test’ “ –execute
此时的操作大致是:
行数据在主库上读写
读取从库的二进制日志,将变更应用到主库
在从库收集表格式,字段&索引,行数等信息
在从库上读取内部的变更事件(如心跳事件)
在主库切换表
在执行DDL中,从库会执行一次stop/start slave,要是确定从的binlog是ROW的话可以添加参数:–assume-rbr。如果从库的binlog不是ROW,可以用参数–switch-to-rbr来转换成ROW,此时需要注意的是执行完毕之后,binlog模式不会被转换成原来的值。–assume-rbr和–switch-to-rbr参数不能一起使用。
gh-ost –user=”root” –password=”root” –host=192.168.163.130 –database=”test” –table=”t” –alter=”ADD COLUMN abc1 varchar(10),add column abc2 int not null default 0 comment ‘test’ “ –test-on-replica –switch-to-rbr –execute
参数–test-on-replica:在从库上测试gh-ost,包括在从库上数据迁移(migration),数据迁移完成后stop slave,原表和ghost表立刻交换而后立刻交换回来。继续保持stop slave,使你可以对比两张表。如果不想stop slave,则可以再添加参数:–test-on-replica-skip-replica-stop
上面三种是gh-ost操作模式,上面的操作中,到最后不会清理临时表,需要手动清理,再下次执行之前果然临时表还存在,则会执行失败,可以通过参数进行删除:
复制代码
–initially-drop-ghost-table:gh-ost操作之前,检查并删除已经存在的ghost表。该参数不建议使用,请手动处理原来存在的ghost表。默认不启用该参数,gh-ost直接退出操作。
–initially-drop-old-table:gh-ost操作之前,检查并删除已经存在的旧表。该参数不建议使用,请手动处理原来存在的ghost表。默认不启用该参数,gh-ost直接退出操作。
–initially-drop-socket-file:gh-ost强制删除已经存在的socket文件。该参数不建议使用,可能会删除一个正在运行的gh-ost程序,导致DDL失败。
–ok-to-drop-table:gh-ost操作结束后,删除旧表,默认状态是不删除旧表,会存在_tablename_del表。
复制代码
还有其他的一些参数,比如:–exact-rowcount、–max-lag-millis、–max-load等等,可以看上面的说明,具体大部分常用的参数命令如下:
gh-osc –user= –password= –host= –database= –table= –max-load=Threads_running=30, –chunk-size=1000 –serve-socket-file=/tmp/gh-ost.test.sock –exact-rowcount –allow-on-master/–test-on-replica –initially-drop-ghost-table/–initially-drop-old-table/–initially-drop-socket-file –max-lag-millis= –max-load=’Threads_running=100,Threads_connected=500’ –ok-to-drop-table
4)额外说明:终止、暂停、限速
gh-ost –user=”root” –password=”root” –host=192.168.163.131 –database=”test” –table=”t1” –alter=”ADD COLUMN o2 varchar(10),add column o1 int not null default 0 comment ‘test’ “ –exact-rowcount –serve-socket-file=/tmp/gh-ost.t1.sock –panic-flag-file=/tmp/gh-ost.panic.t1.flag –postpone-cut-over-flag-file=/tmp/ghost.postpone.t1.flag –allow-on-master –execute
① 标示文件终止运行:–panic-flag-file
创建文件终止运行,例子中创建/tmp/gh-ost.panic.t1.flag文件,终止正在运行的gh-ost,临时文件清理需要手动进行。
② 表示文件禁止cut-over进行,即禁止表名切换,数据复制正常进行。–postpone-cut-over-flag-file
创建文件延迟cut-over进行,即推迟切换操作。例子中创建/tmp/ghost.postpone.t1.flag文件,gh-ost 会完成行复制,但并不会切换表,它会持续的将原表的数据更新操作同步到临时表中。
③ 使用socket监听请求,操作者可以在命令运行后更改相应的参数。–serve-socket-file,–serve-tcp-port(默认关闭)
创建socket文件进行监听,通过接口进行参数调整,当执行操作的过程中发现负载、延迟上升了,不得不终止操作,重新配置参数,如 chunk-size,然后重新执行操作命令,可以通过scoket接口进行动态调整。如:
暂停操作:
#暂停
echo throttle | socat - /tmp/gh-ost.test.t1.sock
#恢复
echo no-throttle | socat - /tmp/gh-ost.test.t1.sock
修改限速参数:
echo chunk-size=100 | socat - /tmp/gh-ost.t1.sock |
echo max-lag-millis=200 | socat - /tmp/gh-ost.t1.sock |
echo max-load=Thread_running=3 | socat - /tmp/gh-ost.t1.sock
4)和pt-online-schema-change对比测试
表没有写入并且参数为默认的情况下,二者DDL操作时间差不多,毕竟都是copy row操作。
表有大量写入(sysbench)的情况下,因为pt-osc是多线程处理的,很快就能执行完成,而gh-ost是模拟“从”单线程应用的,极端的情况下,DDL操作非常困难的执行完毕。
结论:虽然pt-osc不需要触发器,对于主库的压力和性能影响也小很多,但是针对高并发的场景进行DDL效率还是比pt-osc低,所以还是需要在业务低峰的时候处理。相关的测试可以看gh-ost和pt-osc性能对比。
5)封装脚本:
环境:M:192.168.163.131(ROW),S:192.168.163.130/132
封装脚本:gh-ost.py
View Code
运行:
View Code
总结:
gh-ost 放弃了触发器,使用 binlog 来同步。gh-ost 作为一个伪装的备库,可以从主库/备库上拉取 binlog,过滤之后重新应用到主库上去,相当于主库上的增量操作通过 binlog 又应用回主库本身,不过是应用在幽灵表上。
gh-ost 首先连接到主库上,根据 alter 语句创建幽灵表,然后作为一个”备库“连接到其中一个真正的备库上,一边在主库上拷贝已有的数据到幽灵表,一边从备库上拉取增量数据的 binlog,然后不断的把 binlog 应用回主库。图中 cut-over 是最后一步,锁住主库的源表,等待 binlog 应用完毕,然后替换 gh-ost 表为源表。gh-ost 在执行中,会在原本的 binlog event 里面增加以下 hint 和心跳包,用来控制整个流程的进度,检测状态等。这种架构带来诸多好处,例如:
整个流程异步执行,对于源表的增量数据操作没有额外的开销,高峰期变更业务对性能影响小。
降低写压力,触发器操作都在一个事务内,gh-ost 应用 binlog 是另外一个连接在做。
可停止,binlog 有位点记录,如果变更过程发现主库性能受影响,可以立刻停止拉binlog,停止应用 binlog,稳定之后继续应用。
可测试,gh-ost 提供了测试功能,可以连接到一个备库上直接做 Online DDL,在备库上观察变更结果是否正确,再对主库操作,心里更有底。
MySQL在线更改schema的工具很多,如Percona的pt-online-schema-change、 Facebook的 OSC 和 LHM 等,但这些都是基于触发器(Trigger)的,今天咱们介绍的 gh-ost 号称是不需要触发器(Triggerless)支持的在线更改表结构的工具。
图片描述
本文先介绍一下当前业界已经存在的这些工具的使用场景和原理,然后再详细介绍 gh-ost 的工作原理和特性。
今天我们开源了GitHub内部使用的一款 不需要触发器支持的 MySQL 在线更改表结构的工具 gh-ost
开发 gh-ost 是为了应付GitHub在生产环境中面临的持续的、不断变化的在线修改表结构的需求。gh-ost 通过提供低影响、可控、可审计和操作友好的解决方案改变了现有的在线迁移表工具的工作模式。
MySQL表迁移及结构更改操作是业界众所周知的问题,2009年以来已经可以通过在线(不停服务)变更的工具来解决。迅速增长,快速迭代的产品往往需要频繁的需改数据库的结构。增加/更改/删除/ 字段和索引等等,这些操作在MySQL中默认都会锁表,影响线上的服务。 向这种数据库结构层面的变更我们每天都会面临多次,当然这种操作不应该影响用户的正常服务。
在开始介绍 gh-ost 工具之前,咱们先来看一下当前现有的这些工具的解决方案。
在线修改表结构,已存在的场景
如今,在线修改表结构可以通过下面的三种方式来完成:
在从库上修改表结构,操作会在其他的从库上生效,将结构变更了的从库设置为主库
使用 MySQL InnoDB 存储引擎提供的在线DDL特性
使用在线修改表结构的工具。现在最流行的是 pt-online-schema-change 和 Facebook 的 OSC;当然还有 LHM 和比较原始的 oak-online-alter-table 工具。
其他的还包括 Galera 集群的Schema滚动更新,以及一些其他的非InnoDB的存储引擎等待,在 GitHub 我们使用通用的 主-从 架构 和 InnoDB 存储引擎。
为什么我们决定开始一个新的解决方案,而不是使用上面的提到的这些呢?现有的每种解决方案都有其局限性,下文会对这些方式的普遍问题简单的说明一下,但会对基于触发器的在线变更工具的问题进行详细说明。
基于主从复制的迁移方式需要很多的前置工作,如:大量的主机,较长的传输时间,复杂的管理等等。变更操作需要在一个指定的从库上或者基于sub-tree的主从结构中执行。需要的情况也比较多,如:主机宕机、主机从早先的备份中恢复数据、新主机加入到集群等等,所有这些情况都有可能对我们的操作造成影响。最要命的是可能这些操作一天要进行很多次,如果使用这种方法我们操作人员每天的效率是非常高的(译者注:现如今很少有人用这种方式了吧)
MySQL针对Innodb存储引擎的在线DDL操作在开始之前都需要一个短时间排它锁(exclusive)来准备环境,所以alter命令发出后,会首先等待该表上的其它操作完成,在alter命令之后的请求会出现等待waiting meta data lock。同样在ddl结束之前,也要等待alter期间所有的事务完成,也会堵塞一小段时间,这对于繁忙的数据库服务来说危险系数是非常高的。另外DDL操作不能中断,如果中途kill掉,会造成长时间的事务回滚,还有可能造成元数据的损坏。它操作起来并不那么的Nice,不能限流和暂停,在大负载的环境中甚至会影响正常的业务。
我们用了很多年的 pt-online-schema-change 工具。然而随着我们不断增长的业务和流量,我们遇到了很多的问题,我们必须考虑在操作中的哪些 危险操作 (译者注:pt工具集的文档中经常会有一些危险提示)。某些操作必须避开高峰时段来进行,否则MySQL可能就挂了。所有现存的在线表结构修改的工具都是利用了MySQL的触发器来执行的,这种方式有一些潜藏的问题。
基于触发器的在线修改有哪些问题呢?
所有在线表结构修改工具的操作方式都类似:创建与原表结构一致的临时表,该临时表已经是按要求修改后的表结构了,缓慢增量的从原表中复制数据,同时记录原表的更改(所有的 INSERT, DELETE, UPDATE 操作) 并应用到临时表。当工具确认表数据已经同步完成,它会进行替换工作,将临时表更名为原表。
pt-online-schema-change, LHM 和 oak-online-alter-table 这些工具都使用同步的方式,当原表有变更操作时利用一些事务的间隙时间将这些变化同步到临时表。Facebook 的工具使用异步的方式将变更写入到changelog表中,然后重复的将changelog表的变更应用到临时表。所有的这些工具都使用触发器来识别原表的变更操作。
当表中的每一行数据有 INSERT, DELETE, UPDATE 操作时都会调用存储的触发器。一个触发器可能在一个事务空间中包含一系列查询操作。这样就会造成一个原子操作不单会在原表执行,还会调用相应的触发器执行多个操作。
在基于触发器迁移实践中,遇到了如下的问题:
触发器是以解释型代码的方式保存的。MySQL 不会预编译这些代码。 会在每次的事务空间中被调用,它们被添加到被操作的表的每个查询行为之前的分析和解释器中。
锁表:触发器在原始表查询中共享相同的事务空间,而这些查询在这张表中会有竞争锁,触发器在另外一张表会独占竞争锁。在这种极端情况下,同步方式的锁争夺直接关系到主库的并发写性能。以我们的经验来说,在生产环境中当竞争锁接近或者结束时,数据库可能会由于竞争锁而被阻塞住。触发锁的另一个方面是创建或销毁时所需要的元数据锁。我们曾经遇到过在繁忙的表中当表结构修改完成后,删除触发器可能需要数秒到分钟的时间。
不可信:当主库的负载上升时,我们希望降速或者暂停操作,但基于触发器的操作并不能这么做。虽然它可以暂停行复制操作,但却不能暂停出触发器,如果删除触发器可能会造成数据丢失,因此触发器需要在整个操作过程中都要存在。在我们比较繁忙的服务器中就遇到过由于触发器占用CPU资源而将主库拖死的例子。
并发迁移:我们或者其他的人可能比较关注多个同时修改表结构(不同的表)的场景。鉴于上述触发器的开销,我们没有兴趣同时对多个表进行在线修改操作,我们也不确定是否有人在生产环境中这样做过。
测试:我们修改表结构可能只是为了测试,或者评估其负载开销。基于触发器的表结构修改操作只能通过基于语句复制的方式来进行模拟实验,离真实的主库操作还有一定的距离,不能真实的反映实际情况。
gh-ost
gh-ost GitHub 的在线 Schema 修改工具,下面工作原理图:
图片描述
gh-ost 具有如下特性:
无触发器
轻量级
可暂停
可动态控制
可审计
可测试
值得信赖
无触发器
gh-ost 没有使用触发器。它通过分析binlog日志的形式来监听表中的数据变更。因此它的工作模式是异步的,只有当原始表的更改被提交后才会将变更同步到临时表(ghost table)
gh-ost 要求binlog是RBR格式 ( 基于行的复制);然而也不是说你就不能在基于SBR(基于语句的复制)日志格式的主库上执行在线变更操作。实际上是可以的。gh-ost 可以将从库的 SBR日志转换为RBR日志,只需要重新配置就可以了。
轻量级
由于没有使用触发器,因此在操作的过程中对主库的影响是最小的。当然在操作的过程中也不用担心并发和锁的问题。 变更操作都是以流的形式顺序的写到binlog文件中,gh-ost只是读取他们并应用到gh-ost表中。实际上,gh-ost 通过读取binlog的写事件来进行顺序的行复制操作。因此,主库只会有一个单独连接顺序的将数据写入到临时表(ghost table)。这和ETL操作有很大的不同。
可暂停
所有的写操作都是由gh-ost控制的,并且以异步的方式读取binlog,当限速的时候,gh-ost可以暂停向主库写入数据,限速意味着不会在主库进行复制,也不会有行更新。当限速时gh-ost会创建一个内部的跟踪(tracking)表,以最小的系统开销向这个表中写入心跳事件
gh-ost 支持多种方式的限速:
负载: 为熟悉 pt-online-schema-change 工具的用户提供了类似的功能,可以设置MySQL中的状态阈值,如 Threads_running=30
复制延迟: gh-ost 内置了心跳机制,可以指定不同的从库,从而对主从的复制延迟时间进行监控,如果达到了设定的延迟阈值程序会自动进入限速模式。
查询: 用户可以可以设置一个限流SQL,比如 SELECT HOUR(NOW()) BETWEEN 8 and 17 这样就可以动态的设置限流时间。
标示文件: 可以通过创建一个标示文件来让程序限速,当删除文件后可以恢复正常操作。
用户命令: 可以动态的连接到 gh-ost (下文会提到) 通过网络连接的方式实现限速。
可动态控制
现在的工具,当执行操作的过程中发现负载上升了,DBA不得不终止操作,重新配置参数,如 chunk-size,然后重新执行操作命令,我们发现这种方式效率非常低。
gh-ost 可以通过 unix socket 文件或者TCP端口(可配置)的方式来监听请求,操作者可以在命令运行后更改相应的参数,参考下面的例子:
echo throttle | socat - /tmp/gh-ost.sock 打开限速,同样的,可以使用 no-throttle 来关闭限流。 |
改变执行参数: chunk-size=1500, max-lag-millis=2000, max-load=Thread_running=30 这些参数都可以在运行时变更。
可审计
同样的,使用上文提到的程序接口可以获取 gh-ost 的状态。gh-ost 可以报告当前的进度,主要参数的配置以及当前服务器的标示等等。这些信息都可以通过网络接口取到,相对于传统的tail日志的方式要灵活很多。
可测试
因为日志文件和主库负载关系不大,因此在从库上执行修改表结构的操作可以更真实的体现出这些操作锁产生的实际影响。(虽然不是十分理想,后续我们会做优化工作)。
gh-ost 內建支持测试功能,通过使用 –test-on-replica 的参数来指定: 它可以在从库上进行变更操作,在操作结束时gh-ost 将会停止复制,交换表,反向交换表,保留2个表并保持同步,停止复制。可以在空闲时候测试和比较两个表的数据情况。
这是我们在GitHub的生产环境中的测试:我们生产环境中有多个从库;部分从库并不是为用户提供服务的,而是用来对所有表运行的连续覆盖迁移测试。我们生产环境中的表,小的可能没有数据,大的会达到数百GB,我们只是做个标记,并不会正在的修改表结构(engine=innodb)。当每一个迁移结束后会停止复制,我们会对原表和临时表的数据进行完整的checksum确保他们的数据一致性。然后我们会恢复复制,再去操作下一张表。我们的生产环境的从库中已经通过 gh-ost 成功的操作了很多表。
值得信赖
上文提到说了这么多,都是为了提高大家对 gh-ost 的信任程度。毕竟在业界它还是一个新手,类似的工具已经存在了很多年了。
在第一次试手之前我们建议用户先在从库上测试,校验数据的一致性。我们已经在从库上成功的进行了数以千计的迁移操作。
如果在主库上使用 gh-ost 用户可以实时观察主库的负载情况,如果发现负载变化很大,可以通过上文提到的多种形式进行限速,直到负载恢复正常,然后再通过命令微调参数,这样可以动态的控制操作风险。
如果迁移操作开始后预完成计时间(ETA)显示要到夜里2点才能完成,结束时候需要切换表,你是不是要留下来盯着?你可以通过标记文件让gh-ost推迟切换操作。gh-ost 会完成行复制,但并不会切换表,它会持续的将原表的数据更新操作同步到临时表中。你第二天来到办公室,删除标记文件或者通过接口 echo unpostpone 告诉gh-ost开始切换表。我们不想让我们的软件把使用者绑住,它应该是为我们拜托束缚。
说到 ETA, –exact-rowcount 参数你可能会喜欢。相对于一条漫长的 SELECT COUNT(*) 语句,gh-ost 会预估出迁移操作所需要花费的时间,还会根据当前迁移的工作状况更新预估时间。虽然ETA的时间随时更改,但进度百分比的显示是准确的。
gh-ost 操作模式
gh-ost 可以同时连接多个服务器,为了获取二进制的数据流,它会作为一个从库,将数据从一个库复制到另外一个。它有各种不同的操作模式,这取决于你的设置,配置,和要运行迁移环境。
图片描述
a. 连接到从库,在主库做迁移
这是 gh-ost 默认的工作方式。gh-ost 将会检查从库状态,找到集群结构中的主库并连接,接下来进行迁移操作:
行数据在主库上读写
读取从库的二进制日志,将变更应用到主库
在从库收集表格式,字段&索引,行数等信息
在从库上读取内部的变更事件(如心跳事件)
在主库切换表
如果你的主库的日志格式是 SBR,工具也可以正常工作。但从库必须启用二级制日志(log_bin, log_slave_updates) 并且设置 binlog_format=ROW ( gh-ost 是读取从库的二级制文件)。
如果直接在主库上操作,当然也需要二进制日志格式是RBR。
b. 连接到主库
如果你没有从库,或者不想使用从库,你可以直接在主库上操作。gh-ost 将会直接在主库上进行所有操作。你需要持续关注复制延迟问题。
你的主库的二进制日志必须是 RBR 格式。
在这个模式中你必须指定 –allow-on-master 参数
c. 在从库迁移/测试
该模式会在从库执行迁移操作。gh-ost 会简单的连接到主库,此后所有的操作都在从库执行,不会对主库进行任何的改动。整个操作过程中,gh-ost 将控制速度保证从库可以及时的进行数据同步
–migrate-on-replica 表示 gh-ost 会直接在从库上进行迁移操作。即使在复制运行阶段也可以进行表的切换操作。
–test-on-replica 表示 迁移操作只是为了测试在切换之前复制会停止,然后会进行切换操作,然后在切换回来,你的原始表最终还是原始表。两个表都会保存下来,复制操作是停止的。你可以对这两个表进行一致性检查等测试操作。
gh-ost at GitHub
我们已经在所有线上所有的数据库在线操作中使用了gh-ost ,我们每天都需要使用它,根据数据库修改需求,可能每天要运行多次。凭借其审计和控制功能我们已经将它集成到了ChatOps流程中。我们的工程师可以清醒的了解到迁移操作的进度,而且可以灵活的控制其行为。
开源
gh-ost 在MIT的许可下发布到了开源社区。
虽然gh-ost在使用中很稳定,我们还在不断的完善和改进。我们将其开源也欢迎社会各界的朋友能够参与和贡献。随后我们会发布 贡献和建议的页面。
我们会积极的维护 gh-ost 项目,同时希望广大的用户可以尝试和测试这个工具,我们做了很大努力使之更值得信赖。
gh-ost 不使用触发器,它跟踪二进制日志文件,在对原始表的修改提交之后,用异步方式把这修改内容应用到临时表中去。
gh-ost 希望二进制文件使用基于行的日志格式,但这并不表示如果主库上使用的是基于语句的日志格式,就不能用它来在线修改表定义了。事实上,我们常用的方式是用一个从库把日志的语句模式转成行模式,再从这个从库上去读日志。搭一个这样的从库并不复杂。
轻量级
因为不需要使用触发器,gh-ost 把修改表定义的负载和正常的业务负载解耦开了。它不需要考虑被修改的表上的并发操作和竞争等,这些在二进制日志中都被序列化了,gh-ost 只操作临时表,完全与原始表不相干。事实上,gh-ost 也把行拷贝的写操作与二进制日志的写操作序列化了,这样,对主库来说只是有一条连接在顺序的向临时表中不断写入数据,这样的行为与常见的 ETL 相当不同。
可暂停
因为所有写操作都是 gh-ost 生成的,而读取二进制文件本身就是一个异步操作,所以在暂停时,gh-ost 是完全可以把所有对主库的写操作全都暂停的。暂停就意味着对主库没有写入和更新。不过 gh-ost 也有一张内部状态跟踪表,即使在暂停状态下也会向那张表中不断写入心跳信息,写入量可以忽略不计。
gh-ost 提供了比简单的暂停更多的功能,除了暂停之外还可以做:
负载:与 pt-online-schema-change 相近的一个功能,用户可以设置 MySQL 指标的阈值,比如设置 Threads_running=30。
复制延迟:gh-ost 内置了心跳功能来检查复制延迟。用户可以指定查看哪个从库的延迟,gh-ost 默认是直接查看它连上的那个从库。
命令:用户可以写一些命令,根据输出结果来决定要不要开始操作。比如:SELECT HOUR(NOW()) BETWEEN 8 and 17.
上述所有指标即使在修改表定义的过程中也可以动态修改。
标志位文件:生成一个标志位文件,gh-ost 就会立刻暂停。删除文件,gh-ost 又会恢复工作。
用户命令:通过网络连上 gh-ost,通过命令让它暂停。
动态可控
如果别的工具在修改过程中产生了比较高的负载,DBA 只好把它停掉再修改配置,比如把一次拷贝的数据量改小些,然后再从头开始修改过程。这样的反复操作代价非常大。
gh-ost 通过监听 TCP 或者 unix socket 文件来获取命令。即使有正在进行中的修改工作,用户也可以向 gh-ost 发出命令修改配置,比如可以这样做:
echo throttle | socat - /tmp/gh-ost.sock:这是暂停命令。也可以输入 no-throttle
修改运行参数,gh-ost 可以接受这样的修改方式来改变它的行为:chunk-size=1500, max-lag-millis=2000, max-load=Thread_running=30
可审计
用上面所说的相同接口也可以查看 gh-ost 的状态,查看当前任务进度、主要配置参数、相关 MySQL 实例的情况等。这些信息通过网络发送命令就可以得到,因此就给了运维人员极大的灵活性,如果是使用别的工具的话一般只能是通过共享屏幕或者不断跟踪日志文件最新内容。
可测试
读取二进制文件内容的操作完全不会增加主库的负载,在从库上做修改表结构的操作也和在主库上做是非常相象的(当然并不完全一样,但主要来说还是差不多的)。
gh-ost 自带了–test-on-replica 选项来支持测试功能,它允许你在从库上运行起修改表结构操作,在操作结束时会暂停主从复制,让两张表都处于同步、就绪状态,然后切换表、再切换回来。这样就可以让用户从容不迫地对两张表进行检查和对比。
我们在 GitHub 是这样在生产环境测试 gh-ost 的:我们有许多个指定的生产从库,在上面不提供服务,只是周而复始地不断地把所有表定义都改来改去。对于我们生产环境地每一张表,小到空表,大到几百 GB,都会通过修改存储引擎的方式来进行修改(engine=innodb),这样并不会真正修改表结构。在每一次这样的修改操作最后我们都会停掉主从复制,再把原始表和临时表的全量数据都各做一次校验和,然后比较两个校验和,要求它们是一致的。然后我们恢复主从复制,再继续测试下一张表。我们生产环境的每一张表都这样用 gh-ost 在从库上做过好多次修改测试。
可靠的
所有上述讲到的和没讲到的内容,都是为了让你对 gh-ost 的能力建立信任。毕竟,大家在做这件事的时候已经使用类似工具做了好多年,而 gh-ost 只是一个新工具。
我们在从库上对 gh-ost 进行测试,在去主库上做第一次真正改动之前我们在从库上成功地试了几千次。所以,请你也在从库上开始测试,验证数据是完好无损的,然后再把它用到生产环境。我们希望你可以放手去试。
当你执行了 gh-ost 之后,也许你会看见主库的负载变高了,那你可以发出暂停命令。用 echo throttle 命令生成一个文件,看看主库的负载会不会又变得正常。试一下这些命令,你就可以知道你可以怎样控制它的行为,你的心里就会安定许多。
你发起了一次修改操作,然后估计完成时间是凌晨 2 点钟,可是你又非常关心最后的切换操作,非常想看着它切换,这可怎么办?只需要一个标志位文件就可以告诉 gh-ost 推迟切换了,这样 gh-ost 会只做完拷贝数据的操作,但不会切换表。它还会仍然继续同步数据,保持临时表的数据处于同步状态。等第二天早上你回到办公室之后,删除标志位文件或者向 gh-ost 发送命令 echo unpostpone,它就会做切换了。我们不希望软件强迫我们看着它做事情,它应该把我们解放出来,让人去做人该做的事。
谈到估计完成时间,–exact-rowcount 选项非常有用。在最开始时要在目标表上做个代价比较大的 SELECT COUNT(*)操作查出具体要拷多少行数据,gh-ost 就会对它要做多少工作有了一个比较准确的估计。接下来在拷贝的过程中,它会不断地尝试更新这个估计值。因为预计完成的时间点总是会不断变化,所以已经完成的百分比就反而比较精确。如果你也曾经有过非常痛苦的经历,看着已经完成 99%了可是剩下的一点操作却继续了一个小时也没完,你就会非常喜欢我们提供的这个功能。
gh-ost 工作模式【要着重看】
gh-ost 工作时可以连上多个 MySQL 实例,同时也把自己以从库的方式连上其中一个实例来获取二进制日志事件。根据你的配置、数据库集群架构和你想在哪里执行修改操作,可以有许多种不同的工作模式。gh-ost:在线DDL修改MySQL表结构工具
模式一、连上从库,在主库上修改
这是 gh-ost 默认的工作模式,它会查看从库情况,找到集群的主库并且连接上去。修改操作的具体步骤是:
在主库上读写行数据;
在从库上读取二进制日志事件,将变更应用到主库上;
在从库上查看表格式、字段、主键、总行数等;
在从库上读取 gh-ost 内部事件日志(比如心跳);
在主库上完成表切换;
如果主库的二进制日志格式是 Statement,就可以使用这种模式。但从库就必须配成启用二进制日志(log_bin, log_slave_updates),还要设成 Row 格式(binlog_format=ROW),实际上 gh-ost 会在从库上帮你做这些设置。
事实上,即使把从库改成 Row 格式,这仍然是对主库侵入最少的工作模式。
模式二、直接在主库上修改
如果没有从库,或者不想在从库上操作,那直接用主库也是可以的。gh-ost 就会在主库上直接做所有的操作。仍然可以在上面查看主从复制延迟。
主库必须产生 Row 格式的二进制日志;
启动 gh-ost 时必须用–allow-on-master 选项来开启这种模式;
模式三、在从库上修改和测试
这种模式会在从库上做修改。gh-ost 仍然会连上主库,但所有操作都是在从库上做的,不会对主库产生任何影响。在操作过程中,gh-ost 也会不时地暂停,以便从库的数据可以保持最新。
–migrate-on-replica 选项让 gh-ost 直接在从库上修改表。最终的切换过程也是在从库正常复制的状态下完成的。
–test-on-replica 表明操作只是为了测试目的。在进行最终的切换操作之前,复制会被停止。原始表和临时表会相互切换,再切换回来,最终相当于原始表没被动过。主从复制暂停的状态下,你可以检查和对比这两张表中的数据。
三种模式各有优缺点,但我只关心缺点:先说模式一的缺点,模式一会在从 DB 上面读取 binlog,可能造成数据库主从数据不一致,原因因为是主库的 binlog 没有完全在从库执行。所以个人感觉模式一有丢失数据的风险。
模式二任何操作都会在主库操作,或多或少会对主库负载造成影响,但是可以通过调整一些参数降低和时刻关注这些影响,所以个人推荐使用模式二。
至于模式三是偏向测试用的,这里不做过多介绍,但是模式三里有一个细节,cut-over 阶段有会 stop slave 一个操作,其实这个操作风险特别高,有时 stop slave 时间会很长,务必会对线上数据库使用造成影响,所以如果使用模式三做测试也要在线下数据库。
gh-ost 在 GitHub 的应用
现在 GitHub 生产环境的表修改操作全都是用 gh-ost 完成的。每天只要有需求来了,就将它运行起来,有时候一天会做好多次。因为它有审计和控制功能,所以我们还可以把它和我们的 Chatops 系统整合起来。技术人员可以对它的工作进度有非常清晰的了解,因此可以控制它的行为。在生产环境中各种指标和事件都被收集起来,让大家可以以图形化的方式看到操作情况。
============ 分割线之间内容摘自网络 ============
下面分享 gh-ost 的测试使用:
1、下载程序
一直都非常喜欢 go 语言编写的程序,原因很单纯,大部分情况都是一个二进制就能解决问题了,无任何依赖,简直是 IT 界的一股清流!
从 github 发布地址下载最新的 binary 包:https://github.com/github/gh-ost/releases
解压后就一个 gh-ost 二进制文件,再次献上我坚实的膝盖。
2、常用参数
–max-load
执行过程中,gh-ost 会时刻关注负载情况,负载阀值是使用者自己定义,比如数据库的最大连接数,如果超过阀值,gh-ost 不会退出,会等待到负载在阀值以下继续执行。
–critical-load
这个指的是 gh-ost 退出阀值,当负载超过这个阀值,gh-ost 会停止并退出
–chunk-size
迁移过程是一步步分批次完成的,这个参数是指事务每次提交的行数,默认是 1000。
–max-lag-millis
会监控从库的主从延迟情况,如果延迟秒数超过这个阀值,迁移不会退出,等待延迟秒数低于这个阀值继续迁移。
–throttle-control-replicas
和–max-lag-millis 参数相结合,这个参数指定主从延迟的数据库实例。
–switch-to-rbr
当 binlog 日志格式不是 row 时,自动转换日志格式
–initially-drop-ghost-table
gh-ost 执行前会创建两张 xx_ghc 和 xx_gho 表,如果这两张表存在,且加上了这个参数,那么会自动删除原 gh 表,从新创建,否则退出。xx_gho 表相当于老表的全量备份,xx_ghc 表数据是数据更改日志,理解成增量备份。
–initially-drop-socket-file
gh-ost 执行时会创建 socket 文件,退出时不会删除,下次执行 gh-ost 时会报错,加上这个参数会删除老的 socket 文件,重新创建。
–ok-to-drop-table
go-ost 执行完以后是否删除老表,加上此参数会自动删除老表。
–host
数据库实例地址。
–port
数据库实例端口。
–user
数据库实例用户名。
–password
数据库实例密码。
–database
数据库名称。
–table
表名。
–verbose
执行过程输出日志。
–alter
操作语句。
–cut-over
自动执行 rename 操作。
–debug
输出详细日志。
–panic-flag-file
这个文件被创建,迁移操作会被立即终止退出。
–execute
如果确定执行,加上这个参数。
–allow-on-master
整个迁移所有操作在主库上执行,也就是上文介绍的第二种方案:在主库执行。
–throttle-flag-file
此文件存在时操作暂停,删除文件操作会继续。
3、测试过程
本次在单实例 DB 上执行,采用的连接主库的方案,不能存在任何和主从有关系的参数,比如:
–max-lag-millis
–throttle-control-replicas
–switch-to-rbr
gh-ost和pt-osc性能对比
基于MySQL row格式的复制现在趋于主流,因此可以使用此格式的binlog来跟踪改变而不是触发器。与percona toolkit的pt-online-schema-online相比,gh-ost做法更为干净,更安全。由于gh-ost不使用触发器,可能会产生更低的开销并且工作更快。
声明:这些基准对应于一个特定结构和硬件配置的表上的一个特定的ALTER TABLE。 我没有设置一套广泛的测试。
Benchmark Setup Details:
● pt-online-schema-change from Percona Toolkit 3.0.3
● gh-ost 1.0.36
● Percona Server 5.7.18 on Ubuntu 16.04 LTS
● Hardware: 28CPU cores/56 Threads. 128GB Memory. Samsung 960 Pro 512GB
● Sysbench 1.0.7
通过如下命令生成测试表:
sysbench –threads=40 –rate=0 –report-interval=1 –percentile=99 –events=0 –time=0 –db-ps-mode=auto –mysql-user=sbtest –mysql-password=sbtest /usr/share/sysbench/oltp_read_write.lua –table_size=10000000 prepare
1
表大小为3GB
设置如下参数以满足“full ACID”:
sync_binlog=1
innodb_flush_log_at_trx_commit=1
innodb_doublewrite=1
使用pt-online-schema-online更改表结构:
time pt-online-schema-change –execute –alter “ADD COLUMN c1 INT” D=sbtest,t=sbtest1
1
使用gh-ost更改表结构:
time ./gh-ost –user=”sbtest” –password=”sbtest” –host=localhost –allow-on-master –database=”sbtest” –table=”sbtest1” –alter=”ADD COLUMN c1 INT” –execute
1
测试细节:
对于每个测试,丢弃旧的sysbench表被并准备好一个新表。 在每次测量了所有情况下的alter table完成时间,以及alter所产生的开销(换句话说,通过通过工具运行alter table可以减少多少峰值吞吐量)。 在三种不同的情况下进行alter table测试:
● When nothing else was running (“Idle Load”) 空负载
● When the system handled about 2% of load it can handle at full capacity (“Light Background Load”)系统有2%左右的负载(低负载)
● When the system handled about 40% of the possible load, with sysbench injected about 25% of the transactions/sec the system could handle at full load (“Heavy Background Load”)系统处理40%左右的负载(高负载)
Idle Load(空负载)
Idle Load(空负载)
对于空闲负载测试,pt-online-schema-change完成几乎比gh-ost快一倍。可以看到gh-ost的大部分CPU使用情况在MySQL服务器端。 也许差异与用于执行非阻塞alter table的SQL有关。
Light Background Load(低负载)
通过运行下面的sysbench命令生成了Light Background Load。 它对应于大约4%的负载,因为系统可以在满负荷的情况下处理这种并发的大约2500个事务/秒。 调整–rate值以缩放系统。
time sysbench –threads=40 –rate=100 –report-interval=1 –percentile=99 –events=0 –time=0 –db-ps-mode=auto –mysql-user=sbtest –mysql-password=sbtest /usr/share/sysbench/oltp_read_write.lua –table_size=10000000 run
1
1、连上从库,在主库上修改
这是gh-ost默认的工作模式,它会查看从库情况,找到集群的主库并且连接上去,对主库侵入最少,大体步骤是:
在主库上创建_xxx_gho、_xxx_ghc,并修改_xxx_gho表结构;
从slave上读取二进制日志事件,将变更应用到主库上的_xxx_gho表;
在主库上读源表的数据写入_xxx_gho表中:insert into igore….select;
在主库上完成表切换;
2、直接主库修改
在主库上创建_xxx_gho、_xxx_ghc,并修改_xxx_gho表结构;
从主库上读取二进制日志事件,将变更应用到主库上的_xxx_gho表;
在主库上读源表的数据写入_xxx_gho表中:insert into igore….select;
在主库上完成表切换;
3、在从库上修改和测试
这种模式会在从库上做修改。gh-ost仍然会连上主库,但所有操作都是在从库上做的,不会对主库产生任何影响。在操作过程中,gh-ost也会不时地暂停,以便从库的数据可以保持最新。
–migrate-on-replica选项让gh-ost直接在从库上修改表。最终的切换过程也是在从库正常复制的状态下完成的。
–test-on-replica表明操作只是为了测试目的。在进行最终的切换操作之前,复制会被停止。原始表和临时表会相互切换,再切换回来,最终相当于原始表没被动过。主从复制暂停的状态下,你可以检查和对比这两张表中的数据。
二、原理
1、检查校验环境
测试db是否可连通,并且验证database是否存在
确认连接实例是否正确
权限验证 show grants for current_user()
binlog验证,包括row格式验证和修改binlog格式后的重启replicate
原表存储引擎,外键,触发器检查,行数预估等
2、创建binlog streamer连接到主库或者从库,添加binlog的监听
3、创建log表_xxx_ghc和ghost表_xxx_gho并修改ghost表结构到最新
4、开始迁移数据:row copy和binlog apply同时进行
1)最小值:select id
from darren.
t4 order by id
asc limit 1;
2) 最大值:select id
from darren.
t4 order by id
desc limit 1;
3) 计算第一个chunk: select id
from darren
.t4
where id
>= _binary’1’ and id
<= _binary’58594’ order by id
asc limit 1 offset 999
最后一个chunk如果不足1000,那么上面sql查询为空,这时运行:
select id
from (
select id
from darren
.t4
where id
> _binary’58000’ and id
<= _binary’58594’ order by id
asc limit 1000
) select_osc_chunk
order by id
desc limit 1;
4)循环插入数据:insert ignore into darren
._t4_gho
(id
, name
, c1
)
(select id
, name
, c1
from darren
.t4
force index (PRIMARY
)
where id
>= _binary’1’ and id
<= _binary’1000’ lock in share mode
)
4.1、rowcopy数据和应用binlog顺序不同是否产生数据冲突
数据迁移过程中sql映射关系:
rowcopy和binlog应用各种排列组合:
数据迁移过程,涉及三个操作:A:对原表进行rowcopy;B:应用程序的DML;C:应用binlog到新表,因为DML操作才会记录binglog,所以C操作一定在B操作的后面,共有如下几种组合:
1.insert 操作
binlog是最权威的,gh-ost的原则是以binlog优先,所以无论任何顺序下,数据都是和binlog保持一致,如果rowcopy在后,会insert ignore,如果binlog apply在后会replace into掉。
2.update/delete 操作
对已经rowcopy过的数据,出现对原表的update/delete操作。这时候会全部通过binlog apply执行,注意binlog apply的update是对某一条记录的全部列覆盖更新,所以不会有累加的问题。
对尚未迁移的数据,出现对原表的update/delete操作。这时候对新表的binlog apply会是空操作,具体数据由rowcopy迁移。
特殊情况下:
先对原表更新完以后,rowcopy在binlog apply之前把数据迁移了过去,而在binlog event过来以后,会再次应用,这里有问题?其实结合gh-ost的binlog apply之前把数据迁移了过去,
而在binlog的sql映射规则,insert操作会被replace重新替换掉,update 会更新对应记录全部行,delete 会是空操作。最终数据还是一致的状态。
4.2、binlog同步数据何时结束?
copy完数据向_xxx_ghc写入status:AllEventsUpToLockProcessed:1533533052229905040,当binlogsyncer过滤到该值表示所有event都已应用
5、copy完成后进行原子性cut-over阶段
5.1) C10:
创建magic表_xxx_del,目的为了防止过快的进行rename操作和意外情况rename
对源表和magic表_xxx_del加write锁
5.2) C11…C19: 新的请求进来,关于原表的请求被blocked
5.3) C20: 执行:rename table t4
to _t4_del
,_t4_gho
to t4
;这时被阻塞,timeout:3s。(这一步只有binlog event应用完成后)
5.4) 检查是否有blocked 的RENAME请求,通过show processlist
5.5) C10:
删除magic表(只有show processlist里存在被block的rename才进行)
释放琐
不同阶段失败后如何处理:
如果5.1失败,退出程序,比如建表成功,加锁失败,退出程序,未加锁
rename请求来的时候,会话C10死掉,lock会自动释放,同时因为_xxx_del的存在rename也会失败,所有请求恢复正常
rename被blocked的时候,会话C10死掉,lock会自动释放,同样因为_xxx_del的存在,rename会失败,所有请求恢复正常
C20死掉,gh-ost会捕获不到rename,会话C10继续运行,释放lock,所有请求恢复正常
6、清理战场
7.1) 关闭binlogsyncer连接
7.2) 删除源表和_t4_ghc表
一。原理和所用说明
原理
1.创建一个和要执行 alter 操作的表一样的新的空表结构(是alter之前的结构)
2.在新表执行alter table 语句
3.在原表中创建触发器3个触发器分别对应insert,update,delete操作
4.以一定块大小从原表拷贝数据到临时表,拷贝过程中通过原表上的触发器在原表进行的写操作都会更新到新建的临时表
5.Rename 原表到old表中,在把临时表Rename为原表
6.如果有参考该表的外键,根据alter-foreign-keys-method参数的值,检测外键相关的表,做相应设置的处理
7.默认最后将旧原表删除
1.在变更的服务器上 创建 ghost table( _tbname_gho like tbname)
2.更改 _tbname_gho 结构为新表结构
3.作为mysql的slave连接mysql server,并记录新增binlog event
4.交替执行: 应用新增events到 ghost table 和 复制老表的记录到 ghost table
5.table重命名(ghost table 替代 老表)
其中有2种常用用法:
1.连接从库,变更主库 - 默认方式,slave需要开启log-slave-update
2.连接主库,变更主库 - 必须ROW格式,带上参数–allow-on-master”
使用限制
1.原表必须要有主键或者唯一索引(不含NULL)
2.原表上不能有触发器存在
3.使用前需保证有足够的磁盘容量,因为复制原表需要一倍的空间
4.在阿里RDS 上使用需要增加参数no-version-check
1.原表必须要有主键或者唯一索引(不含NULL)
2.不支持外键
3.不支持触发器
4.不支持虚拟列
5.不支持 5.7 point类型的列
重要参数说明
–max-load,默认threads_running=25,可以指定多个指标来限速,每个chunk拷贝完会检查,超过阀值会暂停复制。如果不指定该参数,工具会检查当前运行值并增加20%
–critical-load,默认为threads_running=50,如果不指定,则工具检查当前运行值并当运行到200%是退出工具运行
–max-lag,默认1s,如果发现延迟大于该值,则暂停复制数据。
–check-interval,配合max-lag使用,检查从库超过延时后,该工具睡眠多久
–recursion-method,指定从库的发现机制,processlist,dsn,none 等
–chunk-time,默认0.5秒,拷贝数据行的时候为了保证0.5秒内拷贝完一个chunk,动态调整下一次chunk-size的大小
–[no]check-replication-filters,如果工具检查到服务器选项中有任何复制相关的筛选,工具会报错退出,默认为yes
–chunk-size,指定块大小,默认1000行。
–max-load=Threads_running=25 表面如果在执行gh-ost的过程中出现Threads_running=25则暂停gh-ost的执行
–critical-load=Threads_running=60 表明执行过程中出现Threads_running达到60则终止gh-ost的执行
–chunk-size=1000 设置每次从原表copy到 ghost table的行数
–ok-to-drop-table 执行完之后删除原表
–allow-on-master 直连主库执行
优点 1.执行速度快,业界使用比较广泛,较稳定
1.读binlog可以放在从库执行,减少主库的压力
2.不需要创建触发器,对原表没有改动
风险点
1.需要创建触发器,对原表有改动
2.涉及主键的更改需要review
1.当系统负载极高时,gh-ost有可能无法跟上binlog日志的处理(未测试过该场景)
2.限制比较多,见上文
3.涉及主键的更改需要review
运行命令实例 pt-online-schema-change –user=db_monitor –password=xxx –host=127.0.0.1 –port=xxx –alter “add COLUMN c2 varchar (120) not null default ‘’” D=sbtest,t=sbtest1 –no-check-replication-filters –alter-foreign-keys-method=auto –recursion-method=none –print –execute ./gh-ost –assume-master-host=ip:port –master-user=db_monitor –master-password=xxx –user=db_monitor –password=yyy –host=10.xxx –port=port –alter=”ADD COLUMN c2 varchar(120)” –database=sbtest –table=”sbtest1” -execute –initially-drop-old-table –initially-drop-socket-file –initially-drop-ghost-table
二,性能测试对比
测试场景
16core CPU,2G buffer pool的测试实例,5.5的MySQL版本异步主从,2kw行记录,4.8GB 测试表大小
测试结果(不限速),复制延时用zabbix 监控seconds behind master 的值
结果展示
三, 最后说一下GH-OST的 cut over
gh-ost利用了MySQL的一个特性,就是原子性的rename请求,在所有被blocked的请求中,优先级永远是最高的。
gh-ost基于此设计了该方案:一个连接对原表加锁,另启一个连接尝试rename操作,此时会被阻塞住,当释放lock的时候,rename会首先被执行,其他被阻塞的请求会继续应用到新表。