写在前面的话

对于企业而言,在互联网这一块其实最重要的是数据。保证数据的安全性,稳定性是作为运维人的基本工作职责。于是为了数据安全性,引进了数据备份,bin log 等。但这并不意味着有这些就足够了。试想一下,假设我们的数据库服务器宕机了,原因可能是机器炸了,硬件故障,而数据量又特别大。如果新建数据库再恢复可能需要半天乃至于一天的时间。那么在这段时间,所有服务的都是无法使用的。这样一天下来,很难想象对于公司造成的损失到底有多大,特别是上市公司,股票可能暴跌。那么有没有一种方法,能够帮助我们在遇到这种事故的时候实现快速切换?保证服务不中断?有的。这节就主要来说说最简单最基础的方式,主从复制。

关于主从复制

对于主从复制,需要知道一些它的基本原理:

1. 主从复制的服务器包含两个角色:Master 和 Slave。

2. 主从复制是基于二进制日志(bin log)的,这意味主节点(Master)必须开启二进制日志。

3. 从库(Slave)通过特定的线程获取到主库(Master)的二进制日志,并在从库中执行,从而实现和主库数据保持一致。

由此可以得出,出从更多的解决是主库一键故障,系统故障,而不是例如删库这样的操作,因为删库操作会在从库也执行。

主从复制的前提:

1. 两台 MySQL,配置不同的 server_id。

2. 主库开启了 bin log。

3. 拥有用于主从复制的专有用户。

4. 从库数据是主库数据的某个时间点的。

搭建最基础的主从复制

1. 主库中开启 bin log,配置不同 server_id:

主库 /etc/my.cnf:

server_id=111
log_bin=/data/logs/mysql/binlog/mysql-bin
binlog_format=row

从库 /etc/my.cnf:

server_id=112
log_bin=/data/logs/mysql/binlog/mysql-bin
binlog_format=row

2. 主库新建同步用户,并备份主库:

创建同步授权用户:

grant replication slave on *.* to 'repl'@'192.168.100.112' identified by '';

同步用户只需要授权:replication slave 权限即可

备份主库:

mysqldump -uroot -p -S /data/logs/mysql/mysql.sock -E -R -A --triggers --master-data=2 --single-transaction --set-gtid-purged=OFF >/tmp/data.sql

查看备份中的指针(Position),并记录:

head -30 /tmp/data.sql

结果:

关键:MASTER_LOG_FILE='mysql-bin.000002',MASTER_LOG_POS=971592

3. 从库中导入备份:

source /tmp/data.sql

4. 从库中配置主库连接信息:

help change master to;

查看用法:

根据这个帮助文档中示例就行修改,然后执行:

CHANGE MASTER TO
MASTER_HOST='192.168.100.111',
MASTER_USER='repl',
MASTER_PASSWORD='',
MASTER_PORT=3306,
MASTER_LOG_FILE='mysql-bin.000002',
MASTER_LOG_POS=971592;

5. 从库中启动 slave:

start slave;
show slave status\G

查看结果:

主要看这两个 Yes,表示开启主从复制成功。

6. 主库测试:在主库新家数据库在从库查看结果:

create database testdb3;

从库查看:

至此,最基本的主从复制完成!

主从复制分析

在主从复制搭建完成后,可以对其进行分析:

线程方面:

主库:dump 线程

从库:IO 线程,SQL 线程

文件方面:

主库:mysql-bin.00000x(二进制日志文件)

从库:

demo-node2-relay-bin.000001(中继日志)

master.info(主库信息)

relay-log.info(中继日志应用情况)

主从复制原理:

整个主从复制过程:

1. 从库执行 CHANGE MASTER TO 以后,关于主库的 IP,端口,同步账户等信息就被保存到了 master.info 文件中。

2. 从库执行 START SLAVE; 之后,从库开启两个线程:IO 和 SQL。

3. IO 线程读取 master.info 的信息连接主库,主库创建 DUMP 线程来响应从库。

4. IO 线程根据 master.info 中的指针(Position)和 bin log 文件向 DUMP 请求最新的日志。

5. DUMP 线程根据收到的 IO 线程的指针信息查看 bin log,如果有更新,则传递给从库 IO 线程。

6. IO 线程将收到的日志保存在 TCP/IP 缓存中,并向主库 DUMP 线程应答,至此主库的工作完成。

7. IO 线程将缓存中的数据存储到 relay-log 日志文件中,并更新 master.info 中的指针。至此,IO 线程工作完成。

8. SQL 线程读取 relay-log.info,获取到上次的起点,然后将日志中新增的在从库执行,完成后更新 relay-log.info。

至此,整个同步过程完成,需要知道的是,每次主库产生新数据都会发信号给 DUMP 线程,IO 线程再度发起请求。

查看主库:

查看从库:

Slave 状态说明:

show slave status\G

1. 主库相关的信息:

Master_Host: 192.168.100.111
Master_User: repl
Master_Port: 3306
Master_Log_File: mysql-bin.000002
Read_Master_Log_Pos: 971760

2. 中继日志信息:

Relay_Log_File: demo-node2-relay-bin.000002
Relay_Log_Pos: 488

3. 从库线程:

Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:

4. 过滤复制相关:

Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:

5. 从库延时:(因系统原因导致)

Seconds_Behind_Master: 0

6. 人为设置的主从延时:

SQL_Delay: 0
SQL_Remaining_Delay: NULL

7. GTID 复制相关:

Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0

一般故障总结

在主从配置的时候,一般如果从库无法连接上主库的原因大致有以下几个:

1. 用户名,密码,端口,地址错误导致无法连接。

2. 主库达到连接数量上限,主库繁忙导致无法连接。

3. 防火墙,Selinux,网络问题导致无法连接。

这类问题有一个统一的排查方法,就是使用同步用户在从库上面直接远程测试连接主库看能否连接上。

主从同步失败故障原因:

1. 主库未开启二进制或者从库配置的二进制指针错误导致同步失败。

2. relay-log 缺失或者损坏,导致 SQL 线程无法执行。

3. 主从数据库版本差异,导致某些语法在另外一个库上面不同,从而无法执行。

4. 直接修改了从库,导致和主库已经不一致,再去同步操作某些可能不存在的数据出错。

这类问题的终极处理办法都是推荐重新创建主从环境,并确保两边的配置。

为了避免误操作从库导致主从同步失败,可以将从库配置为只读,当时是针对普通用户:

show variables like '%read_only%';

结果:

当然对于同步中出现同步某个同步失败的情况,可以跳过该事务:(生产环境坚决不允许,不然可能出现大坑

方法1:

stop slave;
-- 将同步指针向下移动一个,如果多次不同步,可以重复操作。
set global sql_slave_skip_counter = 1;
start slave;

方法2:配置文件中添加(虽然可以解决,但是坚决不允许

slave-skip-errors = 1032,1062,1007

错误代码说明:

1007:对象已存在

1032:无法执行DML

1062:主键冲突,或约束冲突

主从同步延时故障分析

在同步过程中,可能会出现主库执行的 SQL 在从库结果一段时间后才会执行的情况,针对于该类问题,可以将原因归咎于以下几个方面:

1. 外在因素:

外在因素有很多,可能是网络传输性能差,服务器性能差,参数版本问题等。

2. 主库因素:

二进制写入不及时,二进制日志写入策略为延时写入(sync_binlog),因为从库是根据二进制日志同步的。

在 5.6 之前版本,DUMP 线程是串行传输二进制日志。主库事务大,可以并行执行,但是从库只能串行(类似多线程和单线程)。

解决办法为开启 GTID 功能,实现 GC(group commit),并行传输日志给从库。对于大事务建议拆分成小事务。

3. 从库因素:

在传统的主从同步中,只有一个 SQL 线程,所以日志到从库还是串行执行,所以慢。

解决办法:5.6 版本开启 GTID 后,从库就能实现 SQL 线程的多线程执行,但只能针对不同库并发。

5.7 以后的版本开启 GTID 以后针对逻辑时钟实现 SQL 多线程,这才是正在多线程(MTS)。另外就是事务不应该太大。

总而言之,版本 5.7 以上,开启 GTID 能够解决很多问题。

人为实现主从延时

在某些特殊时候,并不一定需要主库执行之后从库立即执行。之前说过,主从能够解决因为系统或者硬件故障引起的主库挂掉的问题。但是无法解决类似 drop database 这类问题。因为从库也会跟着 drop,所以这个时候就有了主从延时发挥作用了。

假设这样一个场景,配置了主从延时 3 小时,如果主库执行了 drop database,那么在这 3 个小时内我们有 99% 的可能性发现问题。所以此时我们只需要去从库 stop slave,从库就不会执行 drop database 操作,而我们恢复的数据也就变成最近 3 个小时以内的即可。大大减少了我们恢复的时间。

具体配置方式:

从库执行:

stop slave;
CHANGE MASTER TO MASTER_DELAY=300;
start slave;

在主库上面删除测试库可以在从库查看:

show slave status\G

结果:

上面是配置的延时时间,下面是距离执行还需要的时间,我们配置的 300 秒。

延时从库故障恢复模拟

准备工作:

特别提示:生产环境出现问题,先备份,再操作。

一个主从环境,为了便于观察,配置主从延时为 3000 秒。

在主库上面执行一系列的操作,并删库,期间还有其它写入:

create database testdb3 charset utf8;
use testdb3;
create table t1(id int(4),name char(10));
insert into t1 values(1, "张三");
insert into t1 values(2, "李四");
commit;
insert into t1 values(3, "王五");
commit;
drop database testdb3;
create database testdb4;
use testdb4;
create table t1(id int(4),name char(10));
insert into t1 values(1, "AAA");
insert into t1 values(2, "BBB");
commit;

从库查看:

show slave status\G

还没有执行:

此时我们主库已经删库了,并且 DUMP 线程已经将这些步骤都传输给了从库,从库只是等待时间执行而已。

同时我们需要记录当前的 relay-log 执行到的指针:

1. 关闭主库,避免再度有新数据写入,然后停止同步,这样从库的 SQL 线程就不会再去执行 relay-log:

stop slave;

2. 处理 relay-log:因为 relay-log 说到底还是 binlog 的一种,所以个之前的恢复方式一样:

mysqlbinlog --no-defaults --base64-output=decode-rows -vvv "demo-node2-relay-bin.000002" >/tmp/relay.sql

先将所有的 relay-log 导出为我们看的懂的 SQL 文件,这样便于我们寻找 drop 命令。

通过 vim 打开,可以找到对于的指针:

所以,本次截取的 relay-log 中的 SQL 我们期望的是 320 - 最后,但是要剔除 1558:

mysqlbinlog --no-defaults --start-position=320 "/data/data/mysql/demo-node2-relay-bin.000002" >/tmp/relay-1.sql

再次 vim 新的 SQL 文件,然后删除掉 drop 命令所在的 GTID 整个事务:

3. 在从库执行这个 SQL:

为了避免 GTID 问题,可以先执行重置,因为最终我们会将该库数据作为准确数据。

reset master;

然后导入 SQL:

source /tmp/relay-1.sql

完成后查看:

此时数据已经有了,并且还需要清理从库身份:

reset slave all;

现在去查看 relay-log 中的内容就已经没有了,被清除干净:

show relaylog events in 'demo-node2-relay-bin.000002';

结果:

这样最终实现了在从库恢复了所有数据,这个时候再把从库当作主库重新搭建主从也好,还是将数据导出再导入主库重新搭建主从也好。都不至于删库跑路了。

半同步复制(了解即可)

在之前介绍主从同步原理的时候,DUMP 线程将二进制日志发送给 IO 线程,IO 线程将日志存在缓存中然后恢复给主库一个 ACK 信号。那么主库的工作就完成了。

然而这个过程中有一个问题,就是主库其实是根本不知道从库有没有执行成功的。或许从库正准备执行,然后就宕机了呢?

于是,为了提高主从数据的一致性,便有了半同步复制。

工作原理:

1. 主库提交事务,发送信号给 DUMP 线程,DUMP 线程发信号给从库 IO 线程。

2. IO 线程向主库请求新的二进制日志。

3. DUMP 线程发送二进制日志给 IO 线程,IO 线程接受后,当日志被写到 relay-log 后,返回给主库 ACK_reciver 线程一个信号。

4. ACK_reciver 线程收到信号后告知主库同步成功。

5. 如果 ACK 线程超多预设的时间还没有收到信号,则切换回原始的异步复制。

这里配置就不再做阐述,该同步存在一定的问题,现在很少使用。

过滤复制

在某些特殊情况下,可能存在一些特殊的需求,比如只需要同步某一单独的库,而其它不需要同步,这时候便需要用到过滤。

看一个简单的示例架构,一个项目,有三个数据库,但是该项目主要还是查询居多,于是做了读写分类,从库读,主库写:

在查看从库状态的时候,有几个参数:

Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:

这就是同步过滤的关键,而在这 6 个参数中,用的最多的是:

Replicate_Do_DB:数据库过滤白名单

Replicate_Ignore_DB:数据库过滤黑名单

实现库的过滤方式有两种:

1. 主库配置:

Binlog_Do_DB:
Binlog_Ignore_DB:

也就是指定哪些库能够记录 binlog,因为同步就是基于 binlog 的。但是这种方法及其不推荐,这样数据丢了无法没有 binlog 的就无法恢复。

2. 从库配置:

Replicate_Do_DB:
Replicate_Ignore_DB:

配置需要同步的库,获取配置不同步的库。

过滤复制示例

准备工作:搭建完成的基本主从复制环境

1. 修改从库配置文件,添加需要同步的数据库:

replicate_do_db=testdb1
replicate_do_db=testdb2

有多少个就添加多少个,不能用逗号隔开。完成后重启数据库查看:

show slave status\G

结果:

2. 修改主库测试同步效果:

use testdb1;
drop table t3;
use testdb2;
drop table t1;
use testdb4;
drop table t1;

查看从库:

此时同步的库已经同步了,没同步的不变。

GTID 复制

准备工作:准备两台虚拟机,都安装了数据库。一台已经有数据,另外一台初始化完成的状态。

可以删掉其他库,然后执行:

stop slave;
reset slave all;
reset master;

1. 修改主库和从库的配置文件,增加 GTID 配置:

# 开启 GTID
gtid-mode=on
# 强制 GTID 一致性
enforce-gtid-consistency=true
# Slave 更新写入日志
log-slave-updates=1

2. 备份主库导入从库:

mysqldump -uroot -p -S /data/logs/mysql/mysql.sock -E -R -A --triggers --master-data=2 --single-transaction >/tmp/data.sql

值得注意的是,千万不能有 --set-gtid-pureged=OFF 参数,因为我们需要 GTID。

从库导入:

source /tmp/data.sql

3. 由于同步用户已经存在,所以直接配置同步,启动同步:

CHANGE MASTER TO
MASTER_HOST='192.168.100.111',
MASTER_USER='repl',
MASTER_PASSWORD='',
MASTER_PORT=3306,
MASTER_AUTO_POSITION=1;

相比于传统的主从同步需要指定指针,GTID 由于在备份中已经存在,所以直接 AUTO。

start slave;

查看结果:

如果同步失败,会在下面配置的地方显示错误的 GTID 编号:

注意:解决主从同步失败最好的办法就是重新构建主从环境,而不是想办法绕开错误的点。

GTID 复制的优点:

1. 无论你多少从,它们的 GTID 都是一致的,这样有助于以后跨主机恢复。

2. 备份导入后不需要再度指定指针,直接 auto 即可。

3. 复制过程中,从库不在需要 master.info 文件,直接读取 relay-log 的 GTID。

4. 备份中会包含 GTID 的值,所以请求 binlog 就直接根据该值查询下一个事务,不会重复执行了。

小结

主从复制在实际生产中应用非常广,如果你的数据库连最简单的主从都没有,无异于在走钢丝。最终我们推荐主从的基础模式都想 GTID 复制靠拢,能够大大的提升性能。

MySQL for OPS 07:主从复制的更多相关文章

  1. Mysql实现企业级数据库主从复制架构实战

    场景 公司规模已经形成,用户数据已成为公司的核心命脉,一次老王一不小心把数据库文件删除,通过mysqldump备份策略恢复用了两个小时,在这两小时中,公司业务中断,损失100万,老王做出深刻反省,公司 ...

  2. 项目实战7—Mysql实现企业级数据库主从复制架构实战

    Mysql实现企业级数据库主从复制架构实战 环境背景:公司规模已经形成,用户数据已成为公司的核心命脉,一次老王一不小心把数据库文件删除,通过mysqldump备份策略恢复用了两个小时,在这两小时中,公 ...

  3. Window环境下配置MySQL 5.6的主从复制

    原文:Window环境下配置MySQL 5.6的主从复制 1.环境准备 Windows 7 64位 MySQL 5.6 主库:192.168.103.207 从库:192.168.103.208 2. ...

  4. MySQL实战 | 06/07 简单说说MySQL中的锁

    原文链接:MySQL实战 | 06/07 简单说说MySQL中的锁 本文思维导图:https://mubu.com/doc/AOa-5t-IsG 锁是计算机协调多个进程或纯线程并发访问某一资源的机制. ...

  5. Mysql多实例安装+主从复制+读写分离 -学习笔记

    Mysql多实例安装+主从复制+读写分离 -学习笔记 .embody{ padding:10px 10px 10px; margin:0 -20px; border-bottom:solid 1px ...

  6. MySQL/MariaDB数据库的主从复制

     MySQL/MariaDB数据库的主从复制  作者:尹正杰  版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.MySQL复制概述 1>.传统扩展方式 垂直扩展(也叫向上扩展,Sacle ...

  7. 搭建 MySQL 5.7.19 主从复制,以及复制实现细节分析

    主从复制可以使MySQL数据库主服务器的主数据库,复制到一个或多个MySQL从服务器从数据库,默认情况下,复制异步; 根据配置,可以复制数据库中的所有数据库,选定的数据库或甚至选定的表. Mysql ...

  8. mysql -- mysql基于ssl的主从复制

    mysql基于ssl的主从复制由于mysql在复制过程中是明文的,所以就大大降低了安全性,因此需要借助于ssl加密来增加其复制的安全性. 主服务器node1:172.16.200.1从服务器node2 ...

  9. docker+mysql 构建数据库的主从复制

    docker+mysql 构建数据库的主从复制 在最近的项目中,决定将项目改造成数据库读写分离的架构,后续会有博文详细讲述我的开发改造,本文主要记录我是如何一步步的构建数据库的主从复制. 为什么使用d ...

随机推荐

  1. NGINX 配置清单

    以下内容来自 SimulatedGREG/nginx-cheatsheet. 通用设置 端口 listen server { # standard HTTP protocol listen 80; # ...

  2. concurrent.futures模块简单介绍(线程池,进程池)

    一.基类Executor Executor类是ThreadPoolExecutor 和ProcessPoolExecutor 的基类.它为我们提供了如下方法: submit(fn, *args, ** ...

  3. charAt()检测回文

    package seday01; /** * char charAt(int index) 返回指定位置对应的字符 * @author xingsir */public class CharAtDem ...

  4. python数据挖掘之数据探索第一篇

    目录 数据质量分析   当我们得到数据后,接下来就是要考虑样本数据集的数据和质量是否满足建模的要求?是否出现不想要的数据?能不能直接看出一些规律或趋势?每个因素之间的关系是什么?   通过检验数据集的 ...

  5. 【转载】Gradle for Android 第六篇( 测试)

    由于现阶段Android开发趋于敏捷开发,再加上国内大大小小的互联网公司都在做app,导致很多这会是一个系列,所以如果你看完这篇文章,请看下列文章: 开发人员对单元测试没有基本的概念,但是本篇博文不会 ...

  6. 【微信小程序】App.js生命周期

    1.小程序的生命周期-App.js App() 必须在 app.js 中注册,且不能注册多个.所以App()方法在一个小程序中有且仅有一个. App({ onLaunch: function () { ...

  7. emmet的用法

    emmet 是一个提高前端开发效率的一个工具.emmet允许在html.xml.和css等文档中输入缩写,然后按tab键自动展开为完整的代码片段. 一.Sublime Text 3 安装插件Emmet ...

  8. 用python绘画一些简单图片

    python画笑脸 程序源代码 import turtle #画脸 t = turtle.Pen() t.speed(15) #t.circle(150) #t.color('orange') t.f ...

  9. 6.JavaCC官方入门指南-例1

    例1:整数加法运算   在这个例子中,我们将判断如下输入的式子是否是一个合法的加法运算: 99 + 42 + 0 + 15   并且在输入上面式子的时候,数字与加号之间的任何位置,都是可以有空格或者换 ...

  10. 02-webpack的基本配置-运行webpack

    1安装webPack的方式 第一次全局安装 npm i webpack -g 第一次安装了之后以后就不需要在安装了 在项目根录中运行 npm i webpack --save-dev 安装到项目依赖中 ...