MySQL 主从复制原理和主从复制(延迟 / 不一致)的解决方案

摘要:mysql 的复制,mysql 的复制是构建大规模,高性能应用和 mysql 水平扩展的基础。mysql 复制有很多优势,比如读写分离,负载均衡,高可用性和故障切换等等。可以通过一个或多个备库的进行不同的组合方式来进行数据同步。mysql 主从复制延迟也是最大的缺点。

什么是 MySQL 复制

● mysql 复制是构建大规模,高性能应用的基础。比如最简单的,使用主从复制实现 mysql 的水平扩展。

● mysql 支持的两种复制方式,行复制(mysql >= 5.1)和基于语句的复制(也称为逻辑复制 mysql 3.23 版本就存在)。

● mysql 复制大部分是向后兼容的,新版本的 mysql 服务可以作为老版本的备库,反之不行,但是最好两个数据库版本一致。


主从复制优缺点

优点:构建分布式集群的基础,可以利用主从复制实现读写分离,mysql 服务之间的负载均衡,数据备份,提高 mysql 数据库的高可用性和故障切换。

缺点:主从复制的延迟,延迟时间要根据提交事务大小和网络延迟等多种元素决定。一主多从的复制架构会造成一些浪费,因为他会创建多个重复的 binlog dump 线程。还有就是主从复制不一致的问题。


主从复制的配置

基本上可以分为创建复制账、配置主库和备库、启动复制三步。


主从复制工作原理

主从复制,其实简单理解就是备库复制主库的数据。整个过程就是备库使用主库的一个授权账号来连接主库,然后通过拉取主库的 binlog 日志进行同步。主要原理涉及到三个线程:

1. 备库一个 i/o 线程。

当备库发出 start slave 时,从库会通过在主库上授权的复制用户创建一个 I/O 线程连接主库,并让主库发送指定的 binlog 日志文件和指定位置的日志内容。当备库收到主库的日志记录后,会将日志记录放到中继日志中。并将新的 binlog 文件名和位置记录到 master-info 文件中,以便下次读取主库的指定 binlog 日志文件和指定位置。

2. 主库的一个 binlog dump 线程,如果是一主多从的配置,主库会为每一个备库都创建一个 binlog dump 线程。

主库收到备库的请求时,会先验证权限是否允许连接,然后会创建一个特殊的二进制转储 binlog dump 线程,这个线程会读取主库的二进制日志中的日志信息,然后返回给从库的 I/O 线程,返回的信息除了 binlog 的日志内容外,还有本次更新的 binlog 日志名和更新位置。它不会对日志进行轮询,如果该线程追赶上主库,会进入睡眠状态,直到主库发送信号通知有新的日志产生才会被唤醒。

3. 备库的 sql 线程。

备库的 sql 线程会从中继日志中读取日志并在备库上重演操作,实现备库数据的更新,说白了就是把用户在主库上的操作,然后我备库再根据中继日志演练一遍。


主从复制两种方式

主从复制有 row 和 staement 两种复制方式,还有一种 mixed 是在两者之间随机选择的。与其说是两种复制方式,其实不如说是 binlog 日志的记录的格式。我们可以在 my.cnf 文件中修改配置,也可以直接在数据库的 client 端修改。

# 方式一:client 修改

mysql> set global binlog_format='mixed';
Query OK, 0 rows affected (0.00 sec)
# 方式二:my.cnf 修改

[mysqld]
binlog_format=row
binlog_format=staement
binlog_format=mixed

我们可以使用 show variables like 'binlog_format' 命令查看下当前的复制方式。

# client

mysql> show variables like "%binlog_format%";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW   |
+---------------+-------+
1 row in set (0.00 sec)

● row(行复制),binlog 日志中记录着主库上造成数据修改的 sql 语句,备库只是重演 sql 语句。

优点:这种复制方式会让二进制日志更加紧凑。所以相对而言,基于语句的复制不会占用太多的带宽。也更加的灵活。

缺点:主库上的执行 sql 的时间和备库上执行的 sql 时间可能会稍微或很不同。还有除了操作语句之外,比如时间函数 now(),这样可能导致主库和备库同样的 sql 语句,但是执行的结果却不一样,无法保证正确的复制。

● staement(语句复制):binlog 日志中记录着实际数据(也就是更改后的数据),备库会原封不同的复制过去。

优点:由于无需重演 sql 语句,最大的好处就是可以正确和更加高效的复制每一行。对于 sql 的构造,触发器,存储过程都可以正确的执行。

缺点:由于基于行复制会把所有改变的行都会记录下来,这使得二进制文件非常庞大,并且会给主库记录日志和恢复数据增加额外的负载,更慢的日志记录则会降低并发度。但是对于涉及到更多行或者全表更改的数据操作,这时候基于语句的复制就明显优秀很多和更加灵活。

● mixed(混合复制模式):会在行复制和语句复制中选择一种合适的复制方式。这种复制模式 mysql 可以根据具体情况会在语句复制和行复制中间选择一个合适的模式。

row 和 staement 两种方式都是通过在主库上记录二进制日志,在备库上重放二进制日志实现异步数据复制。只不过最先出现的是 staement 复制,mysql 5.1 才出现的行复制,两种复制方式各有优点和缺点,不过理论上我们要使用基于行复制,因为他在整体上更优,并且适用于大多数场景。


主从复制配置涉及的文件

mysql-bin.* 二进制文件

localhost-relay-bin.* 中继日志文件

mysql-bin.index 存放着 binlog 的日志文件名。

mysql-relay-bin-index 中继日志的索引文件,类似 mysql-bin.index 文件,里面存放着中继日志的文件名。

master.info 保存着备库连接到主库所需要的信息,不能删除,否则备库在重启之后无法连接主库,文件以文本的方式记录了复制用户的密码,注意文件的权限控制。

relay-log.info 文件包含了当前备库复制的二进制日志和中继日志的坐标,不能删除,否则在备库重启后无法获知从哪个位置开始复制,可能会导致重放已经执行过的语句。


主从复制的结构

● 一主多从:非常常见的一种复制结构,非常灵活,基本上可以满足多种需求。

优点:为不同的角色使用不同的备库(例如添加不同的存储引擎和索引)。把一台备库当做待用的主库,除了复制没有其他的数据传输。将一台备库放到远程数据中心,用作灾难恢复。将其中一个备库当做备份、开发或测试使用服务器。读写分离,更好的分摊主库的压力,提高并发能力。就算其中一个备库挂掉之后,还有其它的备库分摊压力。

缺点:当备库增多时,主库需要为每一个备库创建一个 binlog dump 线程,该命令会读取二进制文件中的数据并发送给每个备库,每个备库都会重复这样的工作,它们并不会共享 binlog dump 的资源。本来增加备库是要给主库减轻压力,这样反而会适得其反。

● 级联复制:使用分发主库,分发主库其实也是一个备库,它的唯一目的就是提取和提供主库的二进制日志。位了避免在分发主库上做实际的查询,可以将它的存储引擎修改为 blackhole。

优点:多个备库连接到分发主库,这使原来的主库摆脱了负担,也不一定使用一个分发主库,如果需要的话,可以使用多个分发主库(金字塔状)向大量的备库进行复制。还可以使用分发主库实现其他目的,例如:对二进制日志事件执行过滤和重写规则,这比在每个备库上重复进行日志记录、重写和过滤要高效的多。

缺点:无法使用备库来替代主库,因为由于分发主库的存在,导致各个备库与主库的二进制日志坐标已经不相同。还有就是如果一个分发主库出现问题,那么就会影响到多个服务器。

● 主主复制:顾名思义,对方既是对方的主库,也是对方的从库。

这种复制模式应用的场景非常少,因为这种复制模式带来很多问题,比如两台服务器修改同一行数据,或者向一个包含 auto_increment 列的表里插入数据。


主从复制基本原则

可以在任意个主库和备库之间建立复制,复制也可以有多种结构,每种结构都有优缺点。但无论任何复制结构都要遵循以下几个原则:

● 一个 mysql 备库实例只能有一个主库。

● 每个备库必须有一个唯一的服务器 id。

● 一个主库可以有多个备库。

● 一个备库也可以有多个兄弟备库。

● 如果打开了 log_slave_updates 选项,一个备库可以把其主库上的数据变化传播到其它备库。


主从复制的模式

● 异步复制,mysql 默认的复制模式是异步复制,master 不会等待 slave 是否同步上数据,有可能会丢失数据,有可能存在主从复制延时。

● 半同步复制,在提交过程中增加了一个延时,当提交事务时,在客户端接收到查询结束反馈之前必须保证二进制日志已经传输到至少一台备库上,只是确保是否传输到,而不是确保等备库同步完。这样带来的问题是,主库将事务提交到磁盘上之后会增加延迟,同样也增加了客户端的延迟。

关于半同步复制的几个误解:

1. 备库提示收到事件前,会阻塞主库上的事务提交,这是错误的。事实上主库已经完成事务的提交,只有通知客户端被延迟了。

2. 直到备库执行完事务后,才不会阻塞客户端,这是错误的。事实上备库是在接受到事务后发送反馈而非完成事务后。

3. 但是半同步不总是能够工作,如果备库一直没有回应已收到事件,会超时并转化为正常的异步复制模式。

● 全同步复制,在提交事务时,需要等待备库全部同步完数据才会给客户端反馈。相对来说比较安全,可以有效的避免主从复制延时带来的读取数据不一样的问题,但是会增加主从复制的延时和客户端等待的延迟。


主从复制延迟解决方案

主从复制延迟一直是一个很头疼的问题,因为复制是通过网络的,只要是有网络的地方肯定会有延迟。比如主库写完之后需要往从库去同步数据,首先经过网络肯定有网络延迟,然后从库重演中继日志中的数据时也会有延迟,这些延迟都是无法避免的。我们能做的就是将这些延迟尽量降低,还有就是如何从业务上去优化这种复制延迟带来的问题。一般常见的处理解决方案有以下几点:

1. 最笨的方案就是,对于那种数据一致性要求很高的,我们只能让读取的时候去读取写库。但是这样对于写库的压力很大,因为一般都是一主多从的复制结构。写库本来就少压力很大,这时候还要承担读的压力,这对于写库来说确实是一个考验。

2. 己写己先读,一般来说我们都是读的时候才会把数据库的数据更新到缓存中间件(比如 redis、memcache 等)。但是我们可以在用户写完以后就把数据更新到缓存中去,但是这种方式只是针对插入的时候,因为更新操作本来就会更新缓存(这里还要注意就是数据库缓存双写一致性问题,此处不表)。还有一种就是可以把用户插入的数据写到客户端本地,比如浏览器的 cookie 中存 10s 有效期,如果失效后就再去读取数据库。

3. 还有一种比较笨的方法就是全同步复制,因为全同步复制,是等复制完成后才给客户端返回数据。这种方法和读写库一样的简单粗暴,但是对客户端使用上很不友好,因为我们不确定同步时间,需要客户端一致等待。

4. 从库的配置和不如主库也会导致复制延迟,所以我们尽量要担任主从数据库的服务器配置一样最好。


主从复制不一致解决方案


参考:

《高性能MySQL》 第十章 复制

深度探索MySQL主从复制原理

结束语:感谢您对本网站文章的浏览,欢迎您的分享和转载,但转载请说明文章出处。
Top