MySQL与Redis主从复制

MySQL主备数据一致性

整体流程

第一阶段
在备库上通过 change master 命令,设置主库的 IP、端口、用户名、密码,以及要从哪个位置开始请求 binlog,这个位置包含文件名和日志偏移量。
第二阶段
在备库上执行 start slave 命令,这时候备库会启动两个线程,io_thread 和 sql_thread。其中 io_thread 负责与主库建立连接。主库校验完用户名、密码后,开始按照备库传过来的位置,从本地读取 binlog发给从库。
第三阶段
备库拿到 binlog 后写到本地文件,称为中转日志(relay log)。sql_thread 读取中转日志,解析出日志里的命令,并执行。

主备流程图.png

Binlog

mysql提供三种日志格式statement、row、mixed 。

CREATE TABLE `t` ( `id` int(11) NOT NULL, 
`a` int(11) DEFAULT NULL, 
`t_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 
PRIMARY KEY (`id`), 
KEY `a` (`a`), 
KEY `t_modified`(`t_modified`)) ENGINE=InnoDB;
insert into t values(1,1,'2018-11-13');
insert into t values(2,2,'2018-11-12');
insert into t values(3,3,'2018-11-11');
insert into t values(4,4,'2018-11-10');
insert into t values(5,5,'2018-11-09');
delete from t  where a>=4 and t_modified<='2018-11-10' limit 1;

当 binlog_format=statement 时,binlog 里面记录的就是原始SQL语句。
如果 delete 语句使用的是索引 a,那么会根据索引 a 找到第一个满足条件的行,也就是说删除的是 a=4 这一行;
如果使用的是索引 t_modified,那么删除的就是 t_modified='2018-11-09’也就是 a=5 这一行。
当 binlog_format=row是,binlog 里面记录的就是字段前后的详细信息就不会出现此类问题。

为什么会有 mixed 这种 binlog 格式的存在场景?
因为有些 statement 格式的 binlog 可能会导致主备不一致,所以要使用 row 格式。但 row 格式的缺点是,很占空间。比如你用一个 delete 语句删掉 10 万行数据,用 statement 的话就是一个 SQL 语句被记录到 binlog 中,占用几十个字节的空间。但如果用 row 格式的 binlog,就要把这 10 万条记录都写到 binlog 中。这样不仅会占用更大的空间,同时写 binlog 也要耗费 IO 资源,影响执行速度。所以MySQL 就取了个折中方案,也就是有了 mixed 格式的 binlog。
mixed 格式的意思是,MySQL自己会判断这条 SQL 语句是否可能引起主备不一致,如果有可能就用 row 格式,否则就用 statement 格式。mixed 格式可以利用 statment 格式的优点,同时又避免了数据不一致的风险。

row模式的优点

1、delete 语句,row 格式的 binlog 也会把被删掉的行的整行信息保存起来。所以,如果你在执行完一条 delete 语句以后,发现删错数据了可以直接把 binlog 中记录的 delete 语句转成 insert,把被错删的数据插入回去就可以恢复了。
2、insert 语句,row 格式下insert 语句的 binlog 里会记录所有的字段信息,这些信息可以用来精确定位刚刚被插入的那一行。这时,你直接把 insert 语句转成 delete 语句,删除掉这被误插入的一行数据就可以了。
3、update 语句binlog 里面会记录修改前整行的数据和修改后的整行数据。所以如果你误执行了 update 语句的话,只需要把这个 event 前后的两行信息对调一下,再去数据库里面执行,就能恢复这个更新操作了。

mixed模式

如果用 mysqlbinlog 解析出日志,然后把里面的 statement 语句直接拷贝出来执行。此方法是不妥当的,因为有些语句的执行结果是依赖于上下文命令的,直接执行的结果很可能是错误的。所以用 binlog 来恢复数据的标准做法是,用 mysqlbinlog 工具解析出来,然后把解析结果整个发给 MySQL 执行。

mysqlbinlog master.000001  --start-position=2738 --stop-position=2973 | mysql -h127.0.0.1 -P13000 -u$user -p$pwd;

Redis主备数据一致性

Redis 具有高可靠性
一是数据尽量少丢失,二是服务尽量少中断。
AOF 和 RDB 保证了前者,而对于后者,Redis 的做法就是增加副本冗余量,将一份数据同时保存在多个实例上。即使有一个实例出现了故障,需要过一段时间才能恢复,其他实例也可以对外提供服务,不会影响业务使用。

Redis主从配置

Version>5.0 replicaof master_ip port
Version<5.0 slaveof master_ip port

主从库第一次同步的流程.png

第一阶段
主从库间建立连接、协商同步的过程,主要是为全量复制做准备。在这一步,从库和主库建立起连接,并告诉主库即将进行同步,主库确认回复后,主从库间就可以开始同步了。从库给主库发送 psync 命令,表示要进行数据同步,主库根据这个命令的参数 来启动复制。psync 命令包含了主库的 runID 和复制进度 offset 两个参数。
runID,是每个 Redis 实例启动时都会自动生成的一个随机 ID,用来唯一标记这个实 例。当从库和主库第一次复制时,因为不知道主库的 runID,所以将 runID 设为“?”。offset,此时设为 -1,表示第一次复制。
主库收到 psync 命令后,会用 FULLRESYNC 响应命令带上两个参数:主库 runID 和主库 目前的复制进度 offset,返回给从库。从库收到响应后,会记录下这两个参数。FULLRESYNC 响应表示第一次复制采用的全量复制,也就是说, 主库会把当前所有的数据都复制给从库。

第二阶段
主库将所有数据同步给从库,从库收到数据后,在本地完成数据加载。这个过程依赖于内存快照生成的 RDB 文件。主库执行 bgsave 命令,生成 RDB 文件,接着将文件发给从库。从库接收到 RDB 文件后,会先清空当前数据库,然后加载 RDB 文件。这是因为从库在通过 replicaof 命令开始和主库同步前,可能保存了其他数据。为了避免之前数据的影响,从库需要先把 当前数据库清空。
在主库将数据同步给从库的过程中,主库不会被阻塞,仍然可以正常接收请求。否则, Redis 的服务就被中断了。但是,这些请求中的写操作并没有记录到刚刚生成的 RDB 文件 中。为了保证主从库的数据一致性,主库会在内存中用专门的 replication buffer,记录 RDB 文件生成后收到的所有写操作。

第三个阶段
主库会把第二阶段执行过程中新收到的写命令,再发送给从 库。具体的操作是,当主库完成 RDB 文件发送后,就会把此时 replication buffer 中的修 改操作发给从库,从库再重新执行这些操作。这样一来,主从库就实现同步了。

增量同步

在 Redis 2.8 之前,如果主从库在命令传播时出现了网络闪断,那么,从库就会和主库重 新进行一次全量复制,开销非常大。从 Redis 2.8 开始,网络断了之后,主从库会采用增量复制的方式继续同步。全量复制是同步所有数据,而增量复制只会把主从库网络断连期间主库收到的命令,同步给从库。
当主从库断连后,主库会把断连期间收到的写操作命令,写入 replication buffer,同时也 会把这些操作命令也写入 repl_backlog_buffer 这个缓冲区。
repl_backlog_buffer 是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己已经读到的位置。刚开始的时候,主库和从库的写读位置在一起,这算是它们的起始位置。随着主库不断接 收新的写操作,它在缓冲区中的写位置会逐步偏离起始位置,我们通常用偏移量来衡量这 个偏移距离的大小,对主库来说,对应的偏移量就是 master_repl_offset。主库接收的新 写操作越多,这个值就会越大。
同样,从库在复制完写操作命令后,它在缓冲区中的读位置也开始逐步偏移刚才的起始位 置,此时,从库已复制的偏移量 slave_repl_offset 也在不断增加。正常情况下,这两个偏移量基本相等。


repl_backlog_buffer的使用.png

如果从库的读取速 度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库 间的数据不一致。
一般而言,我们可以调整 repl_backlog_size 这个参 数。这个参数和所需的缓冲空间大小有关。缓冲空间的计算公式是:缓冲空间大小 = 主库 写入命令速度 * 操作大小 - 主从库间网络传输命令速度 * 操作大小。在实际应用中,考虑到可能存在一些突发的请求压力,我们通常需要把这个缓冲空间扩大一倍,即 repl_backlog_size = 缓冲空间大小 * 2,这也就是 repl_backlog_size 的最终值

你可能感兴趣的:(MySQL与Redis主从复制)