文章目录
MySql主从(原理)
一、目的
- 高可用:主库不可用时,切换到从库提供服务;
- 扩展负载能力:主库写,从库提供读,对于读多写少的场景可以大幅度提高并发读能力;(需要注意主从延迟对实时性数据读取的影响)
- 数据备份:主从模式自动备份,而且备份从库可以降低对主库的影响;
PS:因为主从同步是异步的,因此对于实时性要求高的数据,读从库可能会有数据不一致的问题,因此实时性高的数据还是需要查主库,反之可以查从库
二、原理
2.1 主从同步线程
Master会创建一个binlog dump线程(也可以叫IO线程、slave连接到master的时候创建)
- IO线程。当slave连接到master时master会为slave开启binlog dump线程。当master 的 binlog发生变化的时候,binlog dump线程会通知slave(Push模式),并将相应的binlog内容发送给slave。(一个master 有多少个slave,就有多少个binlog dump线程)
slave上会创建2个线程(start slave的时候启动)
- I/O线程。slave 创建IO线程负责与 master连接,接收master发过来的binlog内容并将内容写入到本地的relay log。
- SQL线程。检测Relay Log中新增了内容后解析该 Log 文件中的内容并应有对应的语句到Slave数据库。
使用SHOW PROCESSLIST命令可以查看线程:
- Master
//主库,去掉了无关线程的显示
mysql> show processlist\G;
*************************** 2. row ***************************
Id: 16
User: root
Host: localhost
db: NULL
Command: Query
Time: 0
State: starting
Info: show processlist
*************************** 3. row ***************************
Id: 18
User: slave
Host: 172.17.0.3:49218
db: NULL
Command: Binlog Dump
Time: 10294
State: Master has sent all binlog to slave; waiting for more updates
Info: NULL
- Slave:
//从库,去掉了无关线程的显示
mysql> show processlist\G;
*************************** 1. row ***************************
Id: 4
User: root
Host: localhost
db: NULL
Command: Query
Time: 0
State: starting
Info: show processlist
*************************** 2. row ***************************
Id: 16
User: system user
Host:
db: NULL
Command: Connect
Time: 10402
State: Waiting for master to send event
Info: NULL
*************************** 3. row ***************************
Id: 17
User: system user
Host:
db: NULL
Command: Connect
Time: 10204
State: Slave has read all relay log; waiting for more updates
Info: NULL
- 注意:为了保证 slave crash 重启后够知道复制到了什么地方以便从库的io线程和sql线程仍能够知道从哪里继续复制,slave 还创建两个日志文件 master.info 和 relay-log.info 用来保存复制进度。master.info 里面会记录 master 的 binlog文件名称,IP,端口,账号密码,UUID等信息,relay-log.info 文件会记录relay log文件名、Relay_Log_Pos、Read_Master_Log_Pos等信息,如下:
mysql> show slave status\G;
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 172.17.0.2
Master_User: slave
Master_Port: 3306
Connect_Retry: 30
Master_Log_File: mysql-bin.000001
Read_Master_Log_Pos: 2425
Relay_Log_File: edu-mysql-relay-bin.000002
Relay_Log_Pos: 2091
Relay_Master_Log_File: mysql-bin.000001
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 2425
Relay_Log_Space: 2302
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 100
Master_UUID: 5722550e-2319-11ea-b15e-0242ac110002
Master_Info_File: /var/lib/mysql/master.info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
root@d8a4501c94b7:/var/lib/mysql# cat master.info
25
mysql-bin.000001
2425
172.17.0.2
slave
123456
3306
30
0
0
30.000
0
5722550e-2319-11ea-b15e-0242ac110002
86400
0
root@d8a4501c94b7:/var/lib/mysql# cat relay-log.info
7
./edu-mysql-relay-bin.000002
2091
mysql-bin.000001
2425
0
0
1
-
这里文件内部有些是空行;
-
主从同步示意图:
1.Slave的IO线程连接Master,请求获取指定日志文件的指定位置之后的binlog日志;
2.Master的 binlog dump 线程将指定的 binlog信息发给Slave,除日志信息外,还包括本次返回的信息在 Master端的Binary Log 的日志文件名称以及在Binary Log中的位置;
3.Slave的IO线程接收到信息后将日志内容写入RelayLog 文件(mysql-relay-bin.xxxxxx)的末端,并将读取到的Master端的bin-log的文件名和位置记录到master-info,以便下一次读取时能告诉Master从何处开始同步日志。
4.Slave的SQL线程检测到Relay Log新增了内容后解析Log文件生成对应的sql语句,并应用这些sql到数据库,保证主从数据一致性。
2.2 bin-log主从同步的三种形式
- binl-log中主从同步事件有3种记录形式:statement、row、mixed。推荐使用ROW模式,具体参考 10-MySql 日志-binlog
2.3 主从架构
一主一从:一主一从,最简单的主从架构
一主多从:扩展从节点,增加从节点的并发读取能力(从节点多,则Master上的Dump线程多,而且binlog是Master Push到Slave,过多从节点可能导致Master负载过大)
多级复制:解决从节点过多Master 的Dump线程负载高的问题
双主复制:两个MySql实例互为主从
2.4 循环复制
- 在双Matser 模式下,两个MySql实例互为主从,切换时不需要修改主从关系,但是有一个问题,一个A上的操作记录到binlog后,binlog发给B,B执行后也会产生binlog,这个binlog再同步给A,(log_slave_updates 参数控制主备模式下Slave 执行中继日志后是否产生binlog),此时A不能再执行这个语句,否则会一直循环下去。
- 通过server id解决该问题,不同实例server id必须不同,数据库实例日志中会记录server id,这样A生成的binlog在B执行值,B生成binlog后,在binlog里记录的server id还是A的server id,这样再到A之后,A就不会执行对应的binlog;
2.5 思考
- 为什么Slave是双线程?
早期Slave是单线程,但是容易引起延迟的问题。
- binlog的推送是拉模式还是推模式?为什么?
推模式,推模式更加及时,不过会带来问题,Master的Slave太多可能造成Master负载过高(由此有了多级复制)
三、问题和挑战
- Mysql 主从同步有两个问题,一个是数据安全,一个是同步延迟;这两个问题都源于主从同步的异步性,Master完成一个事物时不能保证这个事物的日志应用到了Slave,由此有可能引起主库执行完后机器故障导致数据丢失,延迟问题在于主库写入的数据有可能无法在从库立刻读取到,这个时差超出可忍受的范围;
3.1 主从同步延迟?
- 主库执行完事物后写入binglog时刻记为t1,这份binlog需要传输给Slave,B收到之后记为t2,然后Slave执行完这个事物记为t3,局域网内很快一般t2-t1比较小,但是t3-t2可能会有延迟,在Slave 通过 show slave status 的 seconds_behind_master参数可以查看主从同步的延迟时间(t3 - t1),这里slave 已经扣除了主从系统的时间差异,看到的值就是主从延迟时间;这里分析主从延迟就要来自于Slave消费中继日志的速度比主库生产binlog的速度慢;
- 主从延迟的关键在于Master的操作是并行的Slave是串行的,比如Master上面有20条针对不同数据的修改,假设有索引,这些锁是互不干扰的(行锁),但是在Slave虽然锁不干扰,但是20个得一条一条的串行执行,由此在某些场景下
可能导致主从延迟,比如TPS比较高超出了一个Slave线程所能承受的范围,另外如果是一些读写分离的场景,可能slave上的某些查询导致锁表,导致SQL线程写入慢;
Master并发 -- Slave串行;
Slave的读可能造成锁的争用引起SQL线程写入慢;
mysql> show slave status\G; 中的 Seconds_Behind_Master 可以查看主从同步的延迟;
1.3.1 延迟原因
- 原因1:主从机器性能不一致:Slava机器性能差
- 原因2:从库有大量费时的读操作;
- 原因3:大事物;比如大事物操作在主库执行了10min,但是在主库上,因为是行锁,这个事物加锁的行不影响主库上同时并发的其他操作,但是备库是单线程,这10min备库执行中级日志时候不能做其他的事情,一个是并行,一个是串行,这是最大的区别;大事物场景比如大量删除历史数据,大表的DDL
1.3.2 应对方法
- 原因1方案:主从可能发生切换,因此建议主从对称部署
- 原因2方案:一主多从,多个从节点分摊读压力,或者通过binlog输出到外部系统,利用专门的计算组件分析计算
- 原因3方案:避免大事物,使用多线程复制,分库(将主库拆分为多个库,降低每个库的写并发就减少了几倍,此时主从延迟可以减少。)
- 另外为提高Slave的写入性能,某些时候可以考虑将从库的刷盘策略调整,主库通常配置sync_binlog=1 和 innodb_flush_log_at_trx_commit = 1,从库则可以考虑
配置为0;(这个方法在安全性高的时候不可取)
3.2 主从同步数据安全?
- MySQL半同步模式:半同步复制,用于解决数据安全的问题
- 半同步模式要求bing-log提交后至少传输到一个从库,通过这样的方式保证数据安全,显然对性能有一定的影响,比如在网络异常,从库卡死的时候;
- 半同步复制在主库写入 binlog日志之后,会强制将binlog立即将同步到从库,从库将日志写入自己本地的 relay log 之后,接着会返回一个 ack 给主库,主库接收到至少一个从库的 ack 之后才会认为写操作完成了。(类似于Kafka分区的机制,MongoDB副本集的写入模式 – 性能与安全的权衡)
四、并行复制
-
并行复制可以按照更细的粒度让多个线程来应用中继日志(单个SQL线程 -> 一组线程);
-
单线程复制模式下SQL线程读取中继日志,多线程复制模式下,分为 coordinator 线程和 worker线程,coordinator 和之前的SQL线程类似 负责读取中继日志并分发事物,worker 线程负责接受coordinator分发的事物并应用到数据库,worker 数量由参数 slave_parallel_workers 控制;
-
另外:coordinator分发事物的时候需要保证:同一行数据的多个事物需要分发给一个worker执行(不同线程顺序不能保证),一个事物的多个步骤需要分发给一个worker;
4.1、并行复制策略
4.1.1 按表分发
- 按表分发事物,不同的事物操作不同的表,各表负载比较均衡的时候效果很好,但是这种策略不能解决热点表问题;
- 原理:不同的worker用hash表保存自己需要执行的事物和事物需要修改的表的关系,coordinator 分配事物时,如果待分配事物 T 不与任意一个worker冲突,那就分配给最空闲的worker,如果与一个worker 冲突,就分配给这个worker ,如果与1个以上的worker 冲突,coordinator 就进入等待状态,直到冲突的worker 降低为一个;
4.1.2 按行分发
- 按行分发策略粒度更细,只要不是修改同一行,则可以通过worker并行执行,要求binlog必须是ROW模式
- 采用模式和按表分发类似,不过hash的键值对粒度更细,并且要求:binlog必须是ROW模式、表必须有主键、不能有外键,也会消耗更多计算资源
4.2、MySql并行复制
4.2.1 5.6版本
- MySql 5.6 的并行复制策略粒度是库级别,原理和按表分发模式类似,不过粒度更大(一个库只有一个线程)。不过如果只有一个数据库,相当于单线程模型,比较适用于多数据库且数据库压力比较均衡的场景
4.2.2 5.7版本
- 5.7并行复制支持单个库多线程,通过 slave-parallel-type 配置来切换模式,DATABASE代表库模式,和5.6一样,LOGICAL_CLOCK则支持单个库多个线程,线程数由配置slave_parallel_workers决定;
--单线程模式:
show processlist; -- 查看是单线程复制
show variables like 'slave_parallel_type'; -- 默认DATABASE
show variables like 'slave_parallel_workers'; -- 默认0
--配置多线程模式:
stop slave; -- 先停止Slave
set global slave_parallel_type='logical_clock'; -- 设置为 LOGICAL_CLOCK
set global slave_parallel_workers=4; -- 设置单库并行复制的线程数量
start slave; -- 启动 Slave
show processlist; -- 查看是多线程复制
- 注意线程数量需要结合具体场景配置(业务/设备核心等);