Mysql 是如何保证主从一致的?
主从一致性的原理
以 A
结构为例子:S
- A 的更新流程
A在接受一个来自客户端的更新请求之后,首先在undolog 内存中写入,然后存入硬盘,在redolog恢复日志 prepare 阶段完成之后,写入binlog
,最后再commit
整个prepare
,完成 A这边的一套完整的执行内部事务的更新逻辑。 - B的同步流程
B
和A
之间维持了一个长链接,在B上,我们会设置A的账号信息,以及日志位置和偏移量,同步时,我们会主动执行一次start slave
命令,启动io_thread
和sql_thread
两个线程 。io_thread
与A建立请求,主库 A 接受请求,然后检验,把A的binlog 发送给B 。B终于拿到A 的binlog ,写到本地日志,relay log
。sql_thread
处理B上的relay log
,解析binlog。
值得注意的是,在 Mysql5.6 之后,Mysql开始支持多线程复制,这减少了主备延迟的问题。
binlog格式
binlog
的格式分为3种,一种是 statement
,一种是 row
,一种是 mixed
。
# mysql 查看 binlog format
mysql> show variables like '%binlog_format%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | MIXED |
+---------------+-------+
1 row in set (0.00 sec)
# mysql 的 binlog 文件
mysql > show binlog events in 'macco-mysql-bin.000055';
...
| macco-mysql-bin.000055 | 7430387 | Query | 100 | 7430476 | BEGIN |
# use db 再进行语句执行;保证无论当前的工作线程在哪个库里,都可以正确执行操作语句。
| macco-mysql-bin.000055 | 7430476 | Query | 100 | 7430632 | use `productdb`; update Product set LikeCount = '21' where ProductNO = 'Product000001582' |
| macco-mysql-bin.000055 | 7430632 | Xid | 100 | 7430663 | COMMIT /* xid=2093802689 */
# xid 就是 redo log 和 binlog 共同的数据字段 |
| macco-mysql-bin.000055 | 7430663 | Query | 100 | 7430752 | BEGIN |
| macco-mysql-bin.000055 | 7430752 | Query | 100 | 7430908 | use `productdb`; update Product set LikeCount = '10' where ProductNO = 'Product000001593' |
| macco-mysql-bin.000055 | 7430908 | Xid | 100 | 7430939 | COMMIT /* xid=2093802697 */ |
| macco-mysql-bin.000055 | 7430939 | Query | 100 | 7431028 | BEGIN | macco-mysql-bin.000055 | 7430939 | Query | 100 | 7431028 | BEGIN
...
...
这里简单描述下三种binlog 合适的问题所在:
以一句简单的 delete
语句来说:
statement
格式的 binlog 会记录 原来的delete 语句row
格式会记录 具体删除的行的id 和其他信息mixed
格式 会在判断之后,选择性的记录上面两种
比如 delete limit1
,有多个where
条件,那么就会产生选择索引导致的行选择问题,这种删除语句,mixed
情况就会记录成 row
格式的 binlog
,如果是简单的不会产生歧义的 sql
语句,就会记录成 statement
格式的binlog
。
row 能够保证主从完全一致,但是性能较低,mixed 存在一定的风险,需要配合 read-commited 隔离级别。
# 将binlog 2738-2973 写入数据库
mysqlbinlog master.000001 --start-position=2738 --stop-position=2973 | mysql -h127.0.0.1 -P13000 -u$user -p$pwd;
双1 和 binlog 、redolog 的完整性
binlog 写入机制
当一个事务执行,先把日志写到 binlog cache
,事务提交的时候,其实是redo log
prepare/commit
的时候,再把 binlog cache
写到 binlog
文件中,清空 binlog cache
。
如果当 binlog
的 size
大于 binlog_cache_size
的时候,就要从内存中写入到磁盘上。
从内存binlog cache 写入磁盘分为两个动作, 即binlog
写入 page cache
还是 持久化到磁盘
分别在 write 和 fsync 先后进行。控制这两个动作的发生的是一个叫 sync_binlog
的参数,
- 如果参数为
0
,那么只write ; - 如果参数为
1
,那么只 fsync ; - 如果参数为
N(N>1)
那么,write到了N才会 fsync ;
一般这个值被设置为 100-1000
,既可以较快的处理,减少IO次数,也可以一定程度的防止实际业务中丢失数据的可能性(除非主机掉电)。
redo log 写入机制
redo log
同样是有三种存储状态和存储媒介:
redo log buffer
:Mysql进程内存
- 写磁盘
write
: 文件系统-page cache
- 持久化磁盘
fsync
:硬盘
控制参数 innodb_flush_log_at_trx_commit
:
- 0 : 事务提交,写入
bnlog
的时机,redo log
是留在redo log buffer
中; - 1 :事务提交,
redo log
持久化到磁盘 - 2 : 事务提交,
redo log
写入page cache
Redo log
持久化到磁盘的几个场景:
- InnoDb 引擎每秒会把 redo log buffer 的日志,写到文件系统的page cache ,再 fsync
redo log size >= redo_log_buffer_size
,会写盘page cache
innodb_flush_log_at_trx_commit
1 会并行的把redo log fsync
双1
:sync_binlog
和 innodb_flush_log_at_trx_commit
都设置为1 。通过上面的介绍,我们 已经知道,在一个事务提交之前,会进行两次刷盘,一次刷盘(fsync)是binlog ,一次是 redo log 的 prepare 阶段
主从延迟
- 主备机器性能不一致,往往备库会比主库机器配置差
Solve: 对称部署 - 备库上随意的无压力控制的操作,影响同步操作
Solve:一主多从、统计类查询交给Hadoop 、Elastic等系统处理,从库更多解决的是A高可用,高并发的问题,并不适合在Mysql底层完全解决。 - 大事务运行,从
主从一致性的原理
主从同步流程看到,一个事务在主库执行完成才会写入binlog ,传递给Slave ,Slave写入relay log
,最后写入从库,如果这个事务有10分钟,从库至少延迟10分钟
解决/优化主从延迟
我觉得这里说优化更恰当。
- 可以把Slave 的 双1 关闭,
innodb_flush_log_at_trx_commit
和sync_binlog
,这样binlog 不会 fsync - 第二种思路就是减少从库查询负载,有两种办法,0:增加Slave服务器 1:Slavel只作为备份
可以看到Slave 的优化和高并发的情况是存在一些冲突的,一个是减少查询,一个是增加查询,个人觉得Slave在请求并发很高的时候,可以考虑转向,分布式缓存,例如 redis 等,这肯定是比单纯数据库能扛住更大的压力的。
还没有评论,来说两句吧...