1 同步原理

1643092662548

slave 从 master 节点复制数据主要有 4 个步骤

  • 1,master 对所有 DDL 和 DML 产生的日志写入 binlog
  • 2, Slave 的 IO Thread 线程从主库中 bin log 中读取取日志。类似 tailf 命令
  • 3, Slave 的 IO Thread 线程把读取的日志放到 relay-bin 文件里,如果不放到文件里,SQL Thread 线程重放过慢的会导致丢数据
  • 4, Slave 的 SQL Thread 线程将主库的 DDL 和 DML 操作事件在 slave 中重放

1,2,3 步骤都是顺序读写,在不考虑网络延迟的情况下,slave 不会产生延迟.但是第 4 步 DML 和 DDL 的 IO 操作是随即的,不是顺序的.不考虑硬件,主从复制延迟主要在这里

1.1 随机读写与顺序读写区别

如下是内存块,每个块最大存 4k

1k 2k
4k 3k
3k

我如果删除 1k 这个块的数据再加个 2k 的块的数据

如果是顺序读写

1k 的数据删除掉之后就丢掉,新加数据直接在后面的块添加数据

2k
4k 3k
3k 2k

如果是随机读写

1k 的数据删除掉之后改成 2k 的数据

2k 2k
4k 3k
3k

2 主从复制产生原因

硬件原因

  • Master 负载
  • Slave 负载
  • 网络延迟
  • 机器配置(cpu、内存、硬盘)

软件原因

  • SQL Thread 是随机读写并且单线程,master 并发写入的话会导致 SQL Thread 来不及重放导致 slave 产生延迟,解决方案就是改成多线程

改进为多线程

注意点:

  • 不能造成更新覆盖.这就需要更新同一行的两个事务,必须分发到同一个 worker 中
  • 同一个事务不能拆开,必须放到同一个 worker 中

1643096254165

SQL Thread Corrdinator 负责给 SQL Thread Worker 分配重写任务

MySQL5.6 及以上版本支持多线程,但是 5.7 版本才实现真正的多线程,区别如下

  • 5.6: I/O thread 同步并发线程是以库级别并行的,也就是说两个库可以并行两个线程,三个库可以并行三个线程,但是注意一个库不要开启并行,影响性能.而且就算开启也会存在 slave 同步延迟
  • 5.7: I/O thread 同步并发线程可以做到按行级别并行,线程数量一般与 CPU 数量一样,可以做到 slave 节点无延迟,slave_parallel_workers=4,slave_parallel_type=LOGICAL_CLOCK,这两个参数一定要加上

3 mysql5.7 如何分配 SQL Thread 任务

一句话: 一个组提交的事务都是可以并行回放 ,因为这些事务都已进入到事务的 prepare 阶段,则说明事务之间没有任何冲突(否则就不可能提交)。

3.1 数据更新流程

1643098008461

  • 执行器先从引擎找到数据,如果在内存中直接返回,如果不再内存中,查询后返回
  • 执行器拿到数据后先修改数据,然后调用引擎接口重新写入数据. 如果这步 mysql 服务宕机,因为 redo 里没有 prepare 状态,会放弃这部分数据
  • 引擎数据更新到内存,同事写数据到 redo 中,此时处于 prepare 阶段,并通知执行器执行完成,随时可以操作. 如果这步 MySQL 宕机,重启后先查看 redolog 的 prepare 状态的数据在 binlog 里有没有,如果有就提交,没有丢弃
  • 执行器生产这个操作的 binlog.如果这步 MySQL 宕机,重启后先查看 binlog 日志对比 redolog,只有在他俩一致的情况提交,否则丢弃
  • 执行器调用引擎的事务提交接口,引擎把刚刚写完的 redo 改成 commit 状态,更新完成

通过两阶段提交保证数据准确性,要么没有,要么有就是最全的

3.2 GTID

这里我有些薄弱,先简单写写后期补上

1643098359682

当事务提交时,它们将在单个操作中写入到二进制日志中。如果多个事务能同时提交成功,那么它们意味着没有冲突,因此可以在 Slave 上并行执行,所以通过在主库上的二进制日志中添加组提交信息。所有已经处于 prepare 阶段的事务,都是可以并行提交的。这些当然也可以在从库中并行提交,因为处理这个阶段的事务都是没有冲突的。在一个组里提交的事务,一定不会修改同一行。这是一种新的并行复制思路,完全摆脱了原来一直致力于为了防止冲突而做的分发算法,等待策略等复杂的而又效率底下的工作。

并且不开启 GTID 的话 MySQL 会自动生成匿名的 GTID,完全不需要担心 GTID 限制

而 MySQL5.7 版本就是按照组提交分发 SQL Thread 任务的

4 MTS 调优

1
2
3
4
5
6
7
8
9
10
slave-parallel-type=LOGICAL_CLOCK # 基于组提交的并行复制
slave-parallel-workers=16 # 并行sql进程数,要大于1
master_info_repository=TABLE # 主节点信息写表里,增加安全性,依赖数据库的事务可以,保证存储的点和数据库真正执行的点一致并且并行复制开启后更新master.info这个文件非常频繁
relay_log_info_repository=TABLE # slave同步的位置信息写表里,与上面同理
relay_log_recovery=ON # 数据库启动后立即启动自动relay log恢复
slave_preserve_commit_order=yes #确保,在slave上事务的提交顺序与relay log中一致。当开启后,slave_parallel_type只能是LOGICAL_CLOCK,如果你有使用级联复制,那LOGICAL_CLOCK可能会使离master越远的slave并行性越差。
global slave_pending_jobs_size_max=100M # 执行事件所需的内存大小,一定要大于主库设置的max_allowed_packet。
#binlog_group_commit_sync_delay=0 # 等待多少时间后才进行组提交,慎用,容易锁表
#binlog_group_commit_sync_no_delay_count=0 # 延时时的最大事务数

5 监控

复制的监控依旧可以通过 SHOW SLAVE STATUS\G,但是 MySQL 5.7 在 performance_schema 架构下多了这些表,用户可以更细力度的进行监控:

1
2
3
4
5
6
7
8
9
10
11
12
13
show tables like 'replication%';
+---------------------------------------------+
| Tables_in_performance_schema (replication%) |
+---------------------------------------------+
| replication_applier_configuration |
| replication_applier_status |
| replication_applier_status_by_coordinator |
| replication_applier_status_by_worker |
| replication_connection_configuration |
| replication_connection_status |
| replication_group_member_stats |
| replication_group_members |
+---------------------------------------------+

总结:MTS+二阶段提交,配合主从切换是服务不可用时间,基本上不会出现 slave 与 master 数据不一致问题

但是有个遗留问题:如果 master 频繁修改一行,其他数据修改不频繁,性能可能比但进程复制性能要差