MySql复制总结
引言
说MySql复制技术为其高可用性奠基一点不为过,当然实现MySql高可用性有很多种实现方式,比如共享存储、使用分布式协议在引擎级实现的高可用性等等。但是原生MySql复制技术是实现MySql高可用性,保证数据库安全稳定运行的最基础也是最重要的技术,它简单、原生,与MySql无限兼容,随着最新版本的发布,其已经能全方位满足大多数业务场景下的使用。因此有必要总结,总结,在总结!!!
常见概念
异步复制(Asynchronous replication)
逻辑上
MySql默认的复制即是异步的,主库在执行完客户端提交的事务后会立即将结果返给给客户端,并不关心从库是否已经接收并处理,这样就会有一个问题,主如果crash掉了,此时主上已经提交的事务可能并没有传到从库上,如果此时,强行将从提升为主,可能导致新主上的数据不完整。
技术上
主库将事务 Binlog 事件写入到 Binlog 文件中,此时主库只会通知一下 Dump 线程发送这些新的 Binlog,然后主库就会继续处理提交操作,而此时不会保证这些 Binlog 传到任何一个从库节点上。
全同步复制(Fully synchronous replication)
逻辑上
指当主库执行完一个事务,所有的从库都执行了该事务才返回给客户端。因为需要等待所有从库执行完该事务才能返回,所以全同步复制的性能必然会收到严重的影响。
技术上
当主库提交事务之后,所有的从库节点必须收到、APPLY并且提交这些事务,然后主库线程才能继续做后续操作。但缺点是,主库完成一个事务的时间会被拉长,性能降低。
半同步复制(Semisynchronous replication,5.5)
逻辑上
是介于全同步复制与全异步复制之间的一种,主库只需要等待至少一个从库节点收到并且 Flush Binlog 到 Relay Log 文件即可,主库不需要等待所有从库给主库反馈。同时,这里只是一个收到的反馈,而不是已经完全完成并且提交的反馈,如此,节省了很多时间。
技术上
介于异步复制和全同步复制之间,主库在执行完客户端提交的事务后不是立刻返回给客户端,而是等待至少一个从库接收到并写到relay log中才返回给客户端。相对于异步复制,半同步复制提高了数据的安全性,同时它也造成了一定程度的延迟,这个延迟最少是一个TCP/IP往返的时间。所以,半同步复制最好在低延时的网络中使用。
问题
很显然,从上面的概念来看,早期的MySql版本或者说早期MySql复制技术存在两个很显眼的问题:
- 主从数据一致性
- 异步复制:显然异步复制不能保证在master发生宕机或者网路异常不能把binglog同步到slave时,切换主备会造成数据丢失。
- 全同步复制:如果我们在有多个slave的情况下,会严重影响master对客户端的响应速度,因此生产上基本很少用。
- 半同步复制:从5.5版本开始,可以使用半同步插件,能解决部分全同步复制影响客户端响应及数据一致性问题,至于为什么只能解决部分,会在增强半同步段落说明。
- 数据同步延迟
- 复制的另外一个影响使用的地方在于数据延迟,特别在网络不稳定、master处理大事务的时候表现特别突出。这是因为MySql最早的主备复制只有两个线程,IO 线程负责从主库接收 Binlog 日志,并保存在本地的 relay log 中,SQL线程负责解析和重放 relay log 中的 event。当主库并行写入压力较大时,备库 IO 线程一般不会产生延迟,因为写 relay log 是顺序写,但是 SQL 线程重放的速度经常跟不上主库写入的速度,会造成主备延迟。如果延迟过大,relay log 一直在备库堆积,还可能把磁盘占满。
MySql的解决之道
增强半同步复制
为了解决数据一致性问题,5.7 的半同步最关键的一个新增参数是 rpl_semi_sync_master_wait_point ,主从一致性加强,支持在事务commit前等待ACK。
AFTER_COMMIT
after commit是MySQL5.6半同步参数,区别于after sync,after sync是在接收ack确认以后主库在引擎层做提交,而after commit是先在引擎层做提交后等待ACK确认。因此,在写入数据后并且在从库确认之前,其他的客户端可以看到在这一事务。
故障分析:
1.binlog 未发送到从库:
事务B获取到事务A提交的内容, 此时宕机故障切换到slave,事务B获取到的内容却丢失了。事务A commit没有收到反馈信息(则需要业务判断了)。
2.binlog 已经发送给从库 :
事务B获取到事务A提交的内容,故障切换到salve ,B仍然获取到A提交的内容,没毛病。事务A commit没有收到反馈信息,若重新执行该事务,则相当于执行两次A事务(则需要业务判断了)。AFTER_SYNC (5.7的默认值)
master 将每个事务写入Binlog , 传递到slave 刷新到磁盘(relay log)。master等待slave 反馈接收到relay log的ack之后,再提交事务并且返回commit OK结果给客户端。 即使主库crash,所有在主库上已经提交的事务都能保证已经同步到slave的relay log中。
实际上,客户端发出commit请求后,在主库上写入binlog并推送给slave,slave接收到binlog并写入relaylog,发送ACK确认已经接收binlog后,master在引擎层commit,客户端接收commit完成,此时其他会话才可以看见已提交的数据。
故障分析:假设master在接收ACK确认时宕机,因为在引擎层并没有提交,HA切换到从库,因为binlog已经写入从库的relaylog,因此不会造成数据丢失,个人认为是目前比较完美的解决方式。
并行复制的演进
为了缓解数据延迟问题,很自然的想法是提高 SQL 线程重放的并行度,引入并行复制。
5.6 Schema 级别的并行复制
开启并行复制后,会启动多个 Worker 线程,原有的 SQL 线程变为 Coordinator 线程。
可以并行的事务分发给 Worker 线程执行;
不能并行的事务等待 Worker 线程全部结束后,再由 Coordinator 线程自己执行。
DDL 语句或者是跨 Schema 的语句不能并行执行。
这种并行复制的模式,对于多个 DB 同时更新才能有较高的并行度,但是更常见的情况是更新集中在同个一个 DB。
一个简单的改进,把 Schema 级别的并行复制改成 Table 级别,可以大幅度提高单库多表环境下的并行度。但是对于只有一个热点表的情况依然处理不了。
5.7 基于 Group Commit 的并行复制
Group Commit
引入 Group Commit之前,Binlog 和 InnoDB 日志是内部XA,为了保证 InnoDB 和 Binlog 提交顺序一致,实际是串行提交,执行序列如下:
- InnoDB prepare
- write/sync Binlog
- InnoDB commit
官方的 Group Commit 分为三个阶段,每个阶段有一个线程操作,三个阶段可以并发执行。 - flush stage:Binlog 从 cache 写入文件
- sync stage: 对 Binlog 做 fsync
- commit stage:引擎层 commit
这样 InnoDB prepare 成功的事务可以进入队列,每个阶段可以对队列事务统一做操作,提高了并行度。
写Binlog 和 InnoDB commit 都是按照队列中的顺序,可以保证 Binlog 和事务提交顺序一致。
Binlog 中记录了 sequence_number 和 last_commited,如上图,MySqlBinlog 解析日志可以看到这两个值。
sequence_number 是自增事务 ID,last_commited 代表上一个提交的事务 ID。
如果两个事务的 last_commited 相同,说明这两个事务是在同一个 Group 内提交的。
LOGICAL_CLOCK 并行复制
slave-parallel-type=LOGICAL_CLOCK : Commit-Parent-Based模式(同一组的事务[last-commit相同],没有锁冲突. 同一组,肯定没有冲突,否则没办法成为同一组)
slave-parallel-type=LOGICAL_CLOCK : Lock-Based模式(即便不是同一组的事务,只要事务之间没有锁冲突[prepare阶段],就可以并发。 不在同一组,只要N个事务prepare阶段可以重叠,说明没有锁冲突)
5.7 引入了变量 slave_parallel_type,可选值 DATABASE、LOGICAL_CLOCK,DATABASE 就是和 5.6 中相同,Schema 级别的并行复制,而 LOGICAL_CLOCK 是基于 Group Commit 的并行复制,相比 5.6 极大提高了并行度。
Group Commit 实现了主库事务的并行提交。很显然的,主库能同时进入prepare阶段的事务之间不会冲突,那么这些事务在备库回放时也不会冲突。
Group Commit 中,last_commited 相同的事务,可以在备库并行回放。
MySql bin log 里面维护了两个变量
Logical_clock max_committed_transaction:记录上次 Group commit 时最大的 sequence_number,即上述 MySqlBinlog 中的 last_committed
Logical_clock transaction_counter:sequence_number 来源,每次分配 sequence_number 时 transaction_counter 进行递增,即当前最大的 sequence_number
5.7 的并行复制还有一点点弊端,如果如果主库并行度低,那么备库回放时也很难并行。
为此,5.7 引入了两个参数:
- binlog_group_commit_sync_delay:等待延迟提交的时间,binlog提交后等待一段时间再 fsync。让每个 group 的事务更多,人为提高并行度。
- binlog_group_commit_sync_no_delay_count:等待提交的最大事务数,如果等待时间没到,而事务数达到了,就立即 fsync。达到期望的并行度后立即提交,尽量缩小等待延迟。
8.0 基于 WriteSet 的并行复制
5.7 为了提高备库回放的速度,需要在主库尽量提高并行度。
8.0解决了这个问题,即使主库在串行提交的事务,只有互相不冲突,在备库就可以并行回放。
8.0 引入了参数 binlog_transaction_dependency_tracking 来控制事务依赖模式,让备库根据 commit timestamps 或者 write sets 并行回放事务,有三个取值:
- COMMIT_ORDERE:使用 5.7 Group commit 的方式决定事务依赖
- WRITESET:使用 WriteSet 的方式决定判定事务直接的冲突,发现冲突则依赖冲突事务,否则按照 COMMIT_ORDERE 方式决定依赖
- WRITESET_SESSION:在 WRITESET 方式的基础上,保证同一个 session 内的事务不可并行
WRITESET 是一个 hash 数组,大小由参数 binlog_transaction_dependency_history_size 决定。
参数 transaction_write_set_extraction 决定 hash 算法,可选值:OFF、MURMUR32、XXHASH64,默认值 XXHASH64,如果
WriteSet 记录了事务的更新行信息,决定 commit_parent时,使用事务自己的 session WriteSet 和 history WriteSet 进行比对,找到最近的冲突行,设为 commit_parent。如果 WriteSet 找不到 commit_parent,则还是使用 COMMIT_ORDERE 决定 commit_parent
- 如何启用write-set并行复制
1 |
|
- 核心原理
1 |
|
- 要不要开启并行复制
1 |
|
如何让slave的并行复制和master的事务执行的顺序一致
5.7.19 之后,可以通过设置 slave_preserve_commit_order = 1
总结
随着 MySql 版本迭代,备库回放效率越来越高,为了保证主备同步时效性,可以尽量更新版本 MySql
同时,为了保证备库回放效率,应该根据业务模型适当设置复制相关参数。
比如 5.7 可以适当调大 binlog_group_commit_sync_delay 以提高主库并行度,同时设置 binlog_group_commit_sync_no_delay_count 在已满足并行度要求时主动提交,尽量减小延迟
在 8.0 中根据数据库配置高低设置 binlog_transaction_dependency_history_size,性能有富余的实例可以适当调大该参数,找到更小的 commit parent,提高备库回放并行度。内存和CPU紧张的实例最好避免在 WriteSet上消耗太多资源。binlog_transaction_dependency_history_size 过大,不光消耗内存,还会降低冲突查询的效率。
参考
MySql · 特性分析 · 8.0 WriteSet 并行复制
MySQL并行复制的深入浅出 | Focus on MySQL,Focus on Life
【MySql】5.7增强半同步AFTER SYNC&AFTER COMMIT - 简书
MySql并行复制的深入浅出 | Focus on MySql,Focus on Life
MySql系列(四)异步复制、全同步复制与半同步复制_数据库_xihuanyuye的博客-CSDN博客
【MySQL】5.7增强半同步AFTER SYNC&AFTER COMMIT - 简书