Mysql主从复制
在实际的生产环境中,如果对mysql数据库的读和写都在一台数据库服务器中操作,无论是在安全性、高可用性,还是高并发等各个方面都是不能满足实际需求的。因此,一般通过主从复制的方式来同步数据,再通过读写分离来提升数据库的并发负载能力。
1、是什么
MySQL 主从复制是指数据可以从一个MySQL数据库服务器主节点复制到一个或多个从节点。MySQL 默认采用异步复制方式,这样从节点不用一直访问主服务器来更新自己的数据,数据的更新可以在远程连接上进行,从节点可以复制 主数据库中的所有数据库或者特定的数据库,或者特定的表。
2、原理

主服务器角色的数据库服务器要开启bin-log(二进制日志)
- slave服务器先设置要连接的主库 change master……
- slave服务器上执行 start slave,开启主从复制开关。这时候从库会创建两个线程,一个 i/o线程(主要负责连接主数据库)。一个sql线程(主要负责读取relay log中的内容,解析成具体的操作并执行,最终保证主从数据的一致性)。slave服务器的i/o线程会通过在master上授权的复制用户权限请求连接master服务器,并请求从指定binlog日志文件的指定位置(日志文件名和位置就是在配置主从复制服务时执行change master命令时指定的)之后发送binlog日志内容。
- master服务器接收到来自slave服务器的io线程请求后,主库会生成一个log dump线程(用来给从库i/o线程传递bin-log内容给从库),master服务器根据slave服务器的io线程请求的信息读取指定bin-log日志文件指定位置之后的bin-log日志信息,然后返回给slave端的io线程(在读取bin-log中的操作时,此线程会对主节点上的bin-log加锁,当读取完成,甚至在发动给从节点之前,锁会被释放)。返回的信息中除了binlog日志内容外,还有本次返回日志内容后在master服务器端的新的binlog文件名以及在binlog中的下一个指定更新位置。
- 当slave服务器的io线程获取到来自master服务器发送日志内容及日志文件及位置点后,将binlog日志内容依次写入到slave端自身Realy log(中继日志)文件(mysql-bin.xxxxx)的最末端,并将新的Binlog文件名和位置记录到master-info文件中,以便下依次读取master端新binlog日志时能够告诉master服务器需要从新Binlog日志的哪个文件哪个位置开始请求新的binlog日志内容
- slave服务器端的sql线程会实时地检查本次relay log中新增加的日志内容,然后及时的把log文件中的内容解析成在master端曾执行的sql语句的内容,并在自身slave服务器上按语句的顺序执行这些sql语句。
注意事项
- master将操作语句记录到binlog日志中,然后授予slave远程连接的权限(master一定要开启bin-log二进制日志功能;通常为了数据安全考虑,slave也开启bin-log功能)。
- slave开启两个线程:IO线程和SQL线程。其中:IO线程负责读取master的binlog内容到中继日志relay log里;SQL线程负责从relay log日志里读出binlog内容,并更新到slave的数据库里,这样就能保证slave数据和master数据保持一致了。
- Mysql复制至少需要两个Mysql的服务,当然Mysql服务可以分布在不同的服务器上,也可以在一台服务器上启动多个服务。
- Mysql复制最好确保master和slave服务器上的Mysql版本相同(如果不能满足版本一致,那么要保证master主节点的版本低于slave从节点的版本)
- master和slave两节点间时间需同步
3、问题
3.1 主从数据不一致
在理想情况下,备库和主库的数据应该是完全一样的。但事实上备库可能发生错误并导致数据不一致。即使没有明显的错误,备库同样可能因为MySQL自身的特性导致数据不一致,例如MySQL的Bug感、网络中断、服务器崩溃,非正常关闭或者其他一些错误
我们可以使用percona-toolkit工具做校验,而该工具包含
- pt-table-checksum 负责检测MySQL主从数据一致性
- pt-table-sync负责挡住从数据不一致时修复数据,让他们保存数据的一致性
- pt-heartbeat 负责监控MySQL主从同步延迟
pt-table-checksum
在主(master)上通过执行校验的查询对复制的一致性进行检查,对比主从的校验值,从而产生结果。
实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
pt-table-checksum [options] [dsn]
# DSN指向的是主的地址
# 常用的参数
--nocheck-replication-filters :不检查复制过滤器,建议启用。后面可以用--databases来指定需要检查的数据库。
--no-check-binlog-format : 不检查复制的binlog模式,要是binlog模式是ROW,则会报错。
--replicate-check-only :只显示不同步的信息。
--replicate= :把checksum的信息写入到指定表中,建议直接写到被检查的数据库当中。
--databases= :指定需要被检查的数据库,多个则用逗号隔开。
--tables= :指定需要被检查的表,多个用逗号隔开
--host | h= :Master的地址
--user | u= :用户名
--passwork | p=:密码
--Post | P= :端口
pt-table-checksum --nocheck-replication-filters --replicate=check_data.checksums --no-check-binlog-format --databases=mytest --tables=t --user=snailsir --password=root
# 返回
Checking if all tables can be checksummed ...
Starting checksum ...
TS ERRORS DIFFS ROWS DIFF_ROWS CHUNKS SKIPPED TIME TABLE
11-11T23:27:10 0 1 3 1 1 0 0.064 mytest.t
# 参数解读
TS :完成检查的时间。
ERRORS :检查时候发生错误和警告的数量。
DIFFS :0表示一致,1表示不一致。当指定--no-replicate-check时,会一直为0,当指定--replicate-check-only会显示不同的信息。
ROWS :表的行数。
CHUNKS :被划分到表中的块的数目。
SKIPPED :由于错误或警告或过大,则跳过块的数目。
TIME :执行的时间。
TABLE :被检查的表名。
以上信息,我们只要关注diffs字段就可以,然后判断这个值是否不等于0;则可以判断是否一致
同时检测信息会被放到check_data库里的checksums表中
|
pt-table-sync
pt-table-sync进行数据的同步
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# 在主库执行
h: 从库ip
u:从库用户名
p:从库密码
P:从库端口
--databases: 从库库名
--print:打印需要同步的sql语句,这样就可以知道那些数据有问题
pt-table-sync --sync-to-master h=192.168.29.103,u=mytest,p=root,P=3306 --databases=mytest --print
# 返回
DELETE FROM `mytest`.`t` WHERE `id`='3' LIMIT 1 /*percona-toolkit src_db:mytest src_tbl:t src_dsn:P=3306,h=192.168.29.102,p=...,u=mytest dst_db:mytest dst_tbl:t dst_dsn:P=3306,h=192.168.29.103,p=...,u=mytest lock:1 transaction:1 changing_src:1 replicate:0 bidirectional:0 pid:76531 user:root host:localhost.localdomain*/;
#返回参数:
--replicate= :指定通过pt-table-checksum得到的表,这2个工具差不多都会一直用。
--databases= : 指定执行同步的数据库,多个用逗号隔开。
--tables= :指定执行同步的表,多个用逗号隔开。
--sync-to-master :指定一个DSN,即从的IP,他会通过show processlist或show slave status 去自动的找主。
h=127.0.0.1 :服务器地址,命令里有2个ip,第一次出现的是Master的地址,第2次是Slave的地址。
u=root :帐号。
p=123456 :密码。
--print :打印,但不执行命令。
--execute :执行命令。
|
我们也可以把这个编辑成脚本,定期通过centos定时器定期检查,脚本如下:
/home/pt-check-sync.sh
1
2
3
4
5
6
7
8
9
10
11
|
#!/usr/bin/env bash
NUM=`pt-table-checksum --nocheck-replication-filters --replicate=check_data.checksums --no-check-binlog-format --databases=mytest --tables=t --user=mytest --password=rot | awk 'NR>1{sum+=$3}END{print sum}'`
if [ $NUM -eq 0 ] ;then
echo "Data is ok!"
else
echo "Data is error!"
# pt-table-sync --sync-to-master h=192.168.29.103,u=mytest,p=rot,P=3306 --databases=mytest --print
pt-table-sync --sync-to-master h=192.168.29.103,u=mytest,p=rot,P=3306 --databases=mytest --execute
# pt-table-sync --sync-to-master h=192.168.29.104,u=mytest,p=rot,P=3306 --databases=mytest --print
pt-table-sync --sync-to-master h=192.168.29.104,u=mytest,p=rot,P=3306 --databases=mytest --execute
fi
|
每天24点执行一次
1
|
* 24 * * * /home/pt-check-sync.sh
|
3.2 主从延迟
产生原因
- 网络延迟
- mysql配置问题
- 服务器配置
因此:只能进行监控以及尽可能的减少从服务器的写入时间,本质:加快从写入数据时间
注意:
mysql的主从复制都是单线程的操作,主库对所有DDL和DML产生的日志写进binlog,由于binlog是顺序写,所以效率很高,slave的sql thread线程将主库的DDL和DML操作事件在slave中重放。DML和DDL的IO操作是随机的,不是顺序,所以成本要高很多,另一方面,由于sql thread也是单线程的,当主库的并发较高时,产生的DML数量超过slave的SQL thread所能处理的速度,或者当slave中有大型query语句产生了锁等待,那么延时就产生了。
pt-heartbeat 监测主从延迟
pt-heartbeat 的工作原理是通过使用时间戳方式在主库上更新特定表,然后再从库上读取呗更新的时间戳然后与本地系统时间对比来得出其延迟。
具体流程
- 在主上创建一张hearteat表,按照一定的时间频率更新该表的数据。监控操作运行后,heartbeat表能促使主从同步
- 连接到从库上检查复制的时间记录,和从库的当前系统时间进行比较,得出时间的差异。 注意在使用的方式就是需要在主库中创建这个表;
1
2
3
4
5
6
7
8
|
CREATE TABLE heartbeat (
ts VARCHAR (26) NOT NULL,
server_id INT UNSIGNED NOT NULL PRIMARY KEY,
file VARCHAR (255) DEFAULT NULL, -- SHOW MASTER STATUS
position bigint unsigned DEFAULT NULL, -- SHOW MASTER STATUS
relay_master_log_file varchar(255) DEFAULT NULL, -- SHOW SLAVE STATUS
exec_master_log_pos bigint unsigned DEFAULT NULL -- SHOW SLAVE STATUS
);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# 在主库上启动服务 1s检测一次
pt-heartbeat --user=root --ask-pass --create-table --database mytest --interval=1 --update --replace --daemonize
# 查看启动状态
ps -ef | grep pt-heartbeat
# 返回
root 76637 1 0 23:52 ? 00:00:00 perl /usr/bin/pt-heartbeat --user=root --ask-pass --create-table --database mytest --interval=1 --interval=1 --update --replace --daemonize
root 76643 76367 0 23:53 pts/2 00:00:00 grep --color=auto pt-heartbeat
# 检测延迟
pt-heartbeat --database mytest --table=heartbeat --monitor --user=root --password=root --master-server-id=1
# 返回结果
0.02s [ 0.00s, 0.00s, 0.00s ]
0.00s [ 0.00s, 0.00s, 0.00s ]
# 这其中 0.02s 表示延迟了 ,没有延迟是为0 而 [ 0.00s, 0.00s, 0.00s ] 则表示1m,5m,15m的平均值, 而这期中需要注意的是 --master-server-id 为主服务器的服务id就是在my.cnf中配置的 server_id的值
|
处理
一旦使用了主从必然是会有一定的延时问题,因此我们就需要考虑程序对于延迟的容忍度。 如果是0容忍的话建议还是不用主从了。
从库的延时问题最为重要的就是主库与从库之间连接的网络环境
,从库的写入和读这两个点 ,再有就是对于主从的架构的优化
硬件
从库的硬件要优于主库
mysql从库配置
网络环境跳过,,,从库的写入主要是指insert,update,delete的语句的执行速度这些语句的执行速度我们就需要考虑MySQL的执行SQL语句的一个特点 -》 对于每一个写的sql会默认开启事务并提交事务 ; 而事务是会影响到io的消耗的这和innodb_flush_log_at_trx_commit参数有关系。默认为1 我们可以尝试设置为0或2可以提高效率, 另一个就是sync_binlog
sync_binlog 配置说明:
sync_binlog”:这个参数是对于MySQL系统来说是至关重要的,他不仅影响到Binlog对MySQL所带来的性能损耗,而且还影响到MySQL中数据的完整性。对于“sync_binlog”参数的各种设置的说明如下: sync_binlog=0,当事务提交之后,MySQL不做fsync之类的磁盘同步指令刷新binlog_cache中的信息到磁盘,而让Filesystem自行决定什么时候来做同步,或者 cache满了之后才同步到磁盘。 sync_binlog=n,当每进行n次事务提交之后,MySQL将进行一次fsync之类的磁盘同步指令来将binlog_cache中的数据强制写入磁盘。
在MySQL中系统默认的设置是sync_binlog=0,也就是不做任何强制性的磁盘刷新指令,这时候的性能是最好的,但是风险也是最大的。因为一旦系统Crash,在binlog_cache中的所有binlog信息都会被丢失。而当设置为“1”的时候,是最安全但是性能损耗最大的设置。因为当设置为1的时候,即使系统Crash,也最多丢失binlog_cache中未完成的一个事务,对实际数据没有任何实质性影响。
从以往经验和相关测试来看,对于高并发事务的系统来说,“sync_binlog”设置为0和设置为1的系统写入性能差距可能高达5倍甚至更多。
innodb_flush_log_at_trx_commit 配置说明: 默认值1的意思是每一次事务提交或事务外的指令都需要把日志写入(flush)硬盘,这是很费时的。特别是使用电池供电缓存(Battery backed up cache)时。 设成2对于很多运用,特别是从MyISAM表转过来的是可以的,它的意思是不写入硬盘而是写入系统缓存。日志仍然会每秒flush到硬 盘,所以你一般不会丢失超 过1-2秒的更新。设成0会更快一点,但安全方面比较差,即使MySQL挂了也可能会丢失事务的数据。而值2只会在整个操作系统 挂了时才可能丢数据。
架构方面
- 可以考虑对于一些库进行单独分离。
- 服务的基础架构在业务和MySQL之间加入memcache或者redis的cache层
2.单个库读写分离,一主多从,主写从读,分散压力。这样从库压力比主库高,保护主库。
4.不同业务的mysql物理上放在不同机器,分散压力。
- 如果主库和从库服务器配置不一样,从库的差点,那么就可能导致延迟时间加长。这时候,换成相同的服务器配置服务器即可。
- 从库压力太大了。一般主从了,从库基本用来查询,比如可能运营或者开发者自己都在从库上进行一系列的 sql 操作。多配几个从库,分摊压力,一主多从(服务的基础架构在业务和mysql之间加入memcache或者redis的cache层。降低mysql的读压力)。
- 大事务。比如 delete 这种语句 不 limit 限制一下,如果数据量过大,导致主库运行时都花费了长时间,再同步到从库,这个时间间隔过长。
4、主从搭建
主服务器搭建过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
# 启动主
docker run --name master -d -p 3310:3306 -v /docker/mysql/master/conf:/etc/mysql/conf.d -v /docker/mysql/master/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7
# 修改配置
vim /docker/mysql/master/conf/my.cnf
[mysqld]
## 同一局域网内注意要唯一
server-id=100
## 开启二进制日志功能,可以随便取(关键)
log-bin=mysql-bin
#重启主服务器
docker restart master
# 进入容器
docker exec -it master /bin/bash
# 创建主从同步用户
create user 'slave'@'%' identified by '123456';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'slave'@'%';
flush privileges;
# 查看状态
show master status;
|

从服务器搭建过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
# 启动从1
docker run --name slave -d -p 3311:3306 -v /docker/mysql/slave/conf:/etc/mysql/conf.d -v /docker/mysql/slave/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7
# 修改配置
vim /docker/mysql/slave/conf/my.cnf
[mysqld]
## 设置server_id,注意要唯一
server-id=101
## 开启二进制日志功能,以备Slave作为其它Slave的Master时使用
log-bin=mysql-slave-bin
## relay_log配置中继日志
relay_log=mysql-relay-bin
# 重启slave01服务器
docker restart slave
# 进入容器
docker exec -it slave /bin/bash
# 执行同步sql语句,同步master账号信息
change master to
master_host='172.17.0.4',
master_user='slave',
master_password='123456',
master_log_file='log_bin.000013',
master_log_pos=154;
# 启动
start slave;
# 查看salve状态
show slave status;
# 如果报错
ERROR 1872 (HY000): Slave failed to initialize relay log info structure from the repository
# 执行
reset slave;
start slave;
# 查看同步状态
show slave status;
|

5、用途
- 实时备份
- 读写分离
- 高可用MHA
主从复制备份
暴力备份
- 停止mysql服务
- 压缩data目录
- 停止从mysql服务
- 压缩主库压缩的data目录
- 给压缩主库压缩的data目录权限
- 再重启从的mysql服务
逻辑备份
mysqldump
1
2
3
4
5
6
7
8
|
# 导出master_slave数据库为master_slave.sql文件到/home目录
mysqldump -h192.168.29.1 -usnailsir -p master_slave > /home/master_slave.sql
# 查看执行时间
time mysqldump -h192.168.29.1 -usnailsir -p master_slave > /home/master_slave.sql
# 导入数据
mysql -f -uroot -p master_slave < /home/master_slave.sql
|
mydumper
1
2
|
# 导出master_slave数据库为master_slave.sql文件到/home目录
time mydumper -h 192.168.29.1 -u snailsir -p -B master_slave -o /home/master_slave.sql
|
我们在进行冷备份的时候,要进行锁表,防止主库写入新的数据
热备份也是直接复制数据物理文件,和冷备份一样,但是热备份可以不停机直接复制,一般用于7*24小时不间断的重要核心业务
热备份工具:xtrabackup
1
2
3
4
5
6
7
8
9
|
xtrabackup --defaults-file=/etc/my.cnf --user=root --password=root --port=3306 --backup --target-dir=/home/master_slave
# 然后将备份的数据传输到从数据库中
scp -r /home/master_slave root@192.168.3.2:/home/master_slave
# 然后进入从数据库中进行恢复
xtrabackup --defaults-files=/etc/my.cnf --copy-back --target-dir=/home/master_slave
# 这样之前的mysql中的data目录会变成data2
# 新生成的数据库会占用data目录名,但是我们要修改下目录权限
chown -R mysql:mysql /usr/local/mysql/data
|