复制概述
复制解决的基本问题是让一台服务器的数据与其他服务器保持同步。一台主库的数据可以同步到多台备库上,备库本身也可以被配置成另外一台服务器的主库。主库和备库之间可以有多种不同的组合方式。
Mysql 支持两种复制方式:基于行的复制和基于语句的复制。这两种方式都是通过在主库上记录二进制日志,在备库重放日志的方式来实现异步的数据复制的。这意味着,在同一时间点备库上的数据可能与主库存在不一致,并且无法保证主备之间的延迟。一些大的语句可能导致备库产生几秒,几分钟甚至几个小时的延迟。
复制通常不会增加主库的开销,主要是启用二进制日志带来的开销。每个备库也会对主库增加一些负载,尤其当备库请求从主库读取旧的二进制日志文件时,可能会造成更高的IO开销。另外锁竞争也可能阻碍事务的提交。
复制解决的问题
数据分布
Mysql 复制通常不会对带宽造成很大的压力。
负载均衡
通过 Mysql 复制可以将读操作分布到多个服务器上,实现对读密集型应用的优化,并且实现方便。
备份
对于备份来说,复制是一项很有意义的技术补充,但是复制不是备份也不能取代备份。
高可用性和故障切换
复制能够帮助应用程序避免Mysql单点失败,一个包含复制的故障切换系统能够缩短宕机时间。
复制如何工作
复制有三个步骤:
- 在主库上把数据更改记录到二进制日志中。
- 备库将主库上的日志复制到自己的中继日志中。
- 备库读取中继日志中的事件,将其重放到备库数据之上。
第一步是在主库上记录二进制日志。在每次提交事务完成数据之前,主库将数据更新的事件记录到二进制日志中。Mysql会按事务提交的顺序而非每条语句的执行顺序来记录二进制日志。在记录二进制日志后,主库会告诉存储引擎可以提交事务了。
第二步备库将主库的二进制日志复制到本地的中继日志中。首先备库会启动一个工作线程即IO线程,IO线程跟主库建立一个普通的客户端连接,然后再主库上启动一个特殊的dump线程,这个 dump 线程会读取主库上二进制日志中的事件。如果该线程追上了主库,就会进入休眠状态,直到主库发送信号量通知 dump 线程有新的事件产生。备库IO线程会将接收到的事件记录到中继日志中。
第三步备库会开启一个SQL线程,该线程从中继日志中读取事件并在备库执行,从而实现备库数据的更新。当SQL线程追赶上IO线程时,中继日志通常已经在系统缓存中,所以中继日志的开销很低。
Mysql 的这种主备复制架构实现了获取事件和重放事件的解耦,允许两个过程异步运行。
配置复制
总的分为以下几步:
1. 在每台服务器上创建复制账号2. 配置主库和备库3. 通知备库连接到主库并从主库复制数据
创建复制账号
Mysql 会赋予一些特殊的权限给复制线程。在备库运行的IO线程会建立一个到主库的TCP/IP连接。
配置主库和备库
假设主库是服务器 server1,需要打开二进制日志并制定一个唯一的服务器ID,在主库的 my.cnf 文件中增加以下配置
log_bin=mysql-bin
server_id=1
备库也要在 my.cnf 文件中增加相应的配置,并且都需要重启服务器
log_bin=mysql-bin
server_id=2
relay_log=/var/lib/mysql/mysql-relay-bin
log_slave_updates=1
read_only=1
启动复制
这一步需要告诉备库如何连接到主库并重放二进制日志,我们可以使用 CHANGE MASTER TO 语句替代 my.cnf 中的配置,并且以后需要指向别的主库时无须重启备库。
CHANGE MASTER TO MASTER_HOST='SERVER1',
MASTER_USER='USER1',
MASTER_PASSWORD='PWD1',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=0; # 指定日志开始读取的位置,初始值为0,从头开始读
从另一个服务器开始复制
大多数情况下,我们是从一台已经运行一段时间的主库复制数据到新的备库。目前有几种方法来初始化备库或者从其他服务器克隆数据到备库。包括从主库复制数据、从另外一台备库克隆数据、以及使用最近的一次备份来启动备库,需要三个条件来让主库和备库保持同步:
- 某个时间点的主库的数据快照
- 主库当前的二进制日志文件,或获得数据快照时在该二进制日志文件中的偏移量
- 从快照时间到现在的二进制日志
推荐的复制配置
在主库上二进制日志最重要的选项是 sync_binlog:
sync_binlog=1
如果该选项开启,Mysql每次提交事务前会将二进制日志同步到磁盘上,保证在服务器崩溃时不会丢失事件。如果禁止该选项,服务器会少做一些工作,但二进制日志文件可能在服务器崩溃时损坏或丢失信息。在备库上该选项会带来不必要的开销,所以他只适用于二进制日志。
复制的原理
基于语句的复制
Mysql 5.0及以前的版本中只支持基于语句的复制。在基于语句的复制模式下,主库会记录那些造成数据更改的查询。当备库读取并重放这些事件时,实际上只是把主库上执行过的SQL再执行一遍。
这种方式最明显的好处就是实现简单。理论上讲,简单地记录和执行这些语句,能够让主备保持同步,另一个好处就是二进制日志里的事件更加紧凑。当然这种方式的缺点就是更新必须串行,需要更多的锁。
基于行的复制
Mysql 5.1开始支持基于行的复制,这种方式会将实际数据记录到二进制日志中,最大的好处就是可以正确地复制每一行。
基于行或基于语句哪个更优
基于语句的复制模式的优点
当主备的模式不同时,逻辑复制能够在多种情况下工作。例如在主备上的表的定义不同,但数据类型相兼容、列的顺序不同等情况。这样就很容易先在备库上修改schema,然后将其提升为主库,减少停机时间。
基于语句的方式执行复制的过程基本上就是执行SQL语句,这意味着所有在服务器上发生的变更都以一种容易理解得方式运行。
基于语句的复制模式的缺点
很多情况下通过基于语句的复制无法正确复制。事实上对于存储过程,触发器以及其他的一些复制语句在5.0和5.1版本中存在大量bug。
基于行的复制模式的优点
几乎没有基于行的复制模式无法处理的场景,对于所有的SQL构造、触发器、存储过程等都能正确执行。这种方式同样能减少锁的使用,因为并不要求串行化执行。
基于行的复制模式的缺点
由于语句并没有在日志里记录,因此无法判断执行了哪些SQL。
复制拓扑
可以在任意个主库和备库之间建立复制,但是有一个限制:每一个备库只能有一个主库。
基本原则是:
1. 一个Mysql 备库实例只能有一个主库2. 每个备库必须有一个唯一的服务器ID3. 一个主库可以有多个备库4. 如果打开了 log_slave_updates 选项,一个备库可以把其主库上的数据变化传播到其他备库
一主库多备库
有以下一些用途:
- 为不同的角色使用不同的备库
- 把一台备库当做待用的主库,除了复制没有其他数据传输
- 将一台备库放到远程数据中心,用作灾难恢复
- 延迟一个或多个备库,以备灾难恢复
- 使用其中一个备库,作为备份、培训、开发或测试使用服务器
主动-主动模式下的主-主复制
主-主复制包含两台服务器,每一台都被配置成对方的主库和备库。其实就是一对主库。但是这样配置的话,两个可写的主库容易导致写冲突。
主动-被动模式下的主-主复制
便于切换主动和被动服务器,很容易进行故障转移和故障恢复。
拥有备库的主-主结构
这种配置的优点是增加了冗余,对于不同地理位置的复制拓扑,能够消除站点单点失效的问题。
主库、分发主库以及备库
如果需要多个备库,一个比较好的方法就是从主库移除负载并使用分发主库。分发主库事实上也是一个备库,他的唯一目的就是提取和提供主库的二进制日志。
树或金字塔形
这种设计的好处是减轻了主库的负担。缺点是中间层出现的任何错误都会影响到多个服务器。如果每个备库和主库直接相连就不会存在这样的问题。
定制的复制方案
- 选择性复制
- 分离功能
- 数据归档
- 将备库用作全文索引
- 只读备库
- 模拟多主库复制
- 创建日志服务器
复制的问题和解决方案
数据损坏或丢失的错误
由于各种原因,Mysql 的复制并不能很好的从服务器崩溃、断电、磁盘损坏、内存或网络错误中恢复。大部分由于非正常关机后导致的复制问题都是由于没有把数据及时的刷到磁盘。下面是意外关闭服务器的几种情况:
- 主库意外关闭
如果没有设置主库的 sync_binlog 选项,就可能在崩溃前没有将最后的几个二进制日志事件刷新到磁盘中。备库UI线程因此也可能一直处于读不到尚未写入磁盘的事件。当主库重新启动时,备库将重新连到主库并再次尝试读取该事件,但是主库会告诉备库没有这个二进制日志的偏移量。
解决这个问题的方法是指定备库从下一个二进制日志的开头读取。但是一些日志事件会永久丢失。可以通过在主库开启 sync_binlog 来避免事件丢失。即使开启了 sync_binlog,MyISAM表的数据仍然可能在崩溃的时候损坏,对于InnoDB表,如果innodb_flush_log_at_trx_commit 没有设为1,也可能丢失数据。
- 备库意外关闭
当备库意外关闭后重启时,回去读 master.info 文件找到上次停止复制的位置。但是该文件并没有同步写到磁盘。如果使用的都是InnoDB表,可以在重启后观察Mysql的错误日志。InnoDB在恢复过程中会打印出他的恢复点的二进制日志坐标。
除了由于Mysql 意外关闭导致的数据丢失外,也有很多场景导致磁盘上的二进制日志或中继日志文件损坏:
- 主库上的二进制日志损坏
如果主库上的二进制日志损坏,除了忽略损坏的位置外别无选择。可以在主库上执行 FLUSH LOGS 命令,这样主库会开始一个新的日志文件。
- 备库上的中继日志损坏
如果主库上的日志是完好的,就可以通过 CHANGE MASTER TO 命令丢弃并重新获取损坏的事件,只需要将备库指向他当前正在复制的位置,这会导致备库丢弃所有在磁盘上的中继日志。
- 二进制日志与InnoDB事务日志不同步
当主库崩溃时,InnoDB可能将一个事务标记为已提交,此时该事务可能还没有记录到二进制日志中。除非是某个备库的中继日志已经保存,否则没有办法恢复丢失的事务。在Mysql5.0版本可以设置 sync_binlog 来避免该问题。
当一个二进制日志损坏时,能恢复多少数据取决于损坏的类型:
- 数据改变,但事件仍是有效的SQL
Mysql无法察觉这种损坏,最好还是经常检查备库的数据是否正确。
- 数据改变并且事件也是无效的SQL
可以通过 mysqlbinlog 命令提取出事件并看到一些错误数据。
- 数据遗漏并且事件的长度是错误的
这种情况下 mysqlbinlog 可能会发生错误退出或者直接崩溃,因为他无法读取事件,并且找不到下一个事件的开始位置。
- 某些事件已经损坏或被覆盖,或者偏移量已经改变并且下一个事件的起始偏移量也是错误的
使用非事务型表
如果一切正常,基于语句的复制能够很好地处理非事务型表,但是当对非事务型表的更新发生错误时,就可能导致主备数据不一致。
如果使用的是MyISAM表,在关闭Mysql之前需要确保已经运行了STOP SLAVE ,否则服务器在关闭时会 kill 所有正在运行的查询。事务型存储引擎则没有这个问题。
混合事务型和非事务型表
如果使用的是事务型存储引擎,只有在事务提交后才会将查询记录到二进制日志中。因此如果事务回滚,Mysql 就不会记录这条查询,也就不会在备库上重放。
但是如果混合适合事务型和非事务型表,并且发生了一次回滚,Mysql 能够回滚事务型表的更新,但非事务型表则被永久地更新了。
防止这个问题的方法就是避免混合使用事务型和非事务型表。
不确定语句
当使用基于语句的复制模式时,如果通过不确定的方式更改数据可能会导致主备不一致。例如使用带 LIMIT 关键字的 UPDATE 操作更改的数据取决于查询找的顺序,除非能保证主备上的顺序相同。
一般禁止对UPDATE 操作使用 LIMIT 关键字。
主库和备库使用不同的存储引擎
当使用基于语句的复制时,如果备库使用了不同的存储引擎,则可能造成一条查询在主库和备库上的执行结果不同。
如果发现主库和备库的某些表已经不同步,除了检查更新这些表的查询外,还要检查两天服务器上使用的存储引擎是否相同。
备库发生数据改变
基于语句的复制前提是确保备库上有和主库相同的数据,因此不应该允许对备库数据的任何更改。如果备库上某个表的数据跟主库不一样,INSERT INTO table1 SELECT * FROM table2;该语句会导致 table1表的数据也不一致。解决的唯一方法就是重新从主库同步数据。
不唯一的服务器ID
如果给两天服务器设置了一样的ID,在主库上会发现只有一台能连接到主句上,在备库的错误日志中,则会发现反复的重连和连接断开的信息。解决的唯一方法是小心设置服务器ID。
未定义的服务器ID
如果没有在 my.cnf 里定义服务器ID,可以通过 CHANGE MASTER TO来设置备库。但是却无法启动复制。必须为备库显示的设置服务器ID。
对未复制数据的依赖性
如果在主库上有备库不存在的数据库或表,服务很容易被中断,唯一的方法就是避免在主库上创建备库上没有的表。
丢失的临时表
临时表在某些时候比较有用,他与基于语句的复制方式是不相容的。如果备库崩溃或者正常关闭,任何复制线程拥有的临时表都会丢失。重启备库后,所有依赖于该临时表的语句都会失败。
不复制所有的更新
如果错误地使用 SET SQL_LOG_BIN=0或者没有理解过滤规则,备库可能会丢失主库上已经发生的更新。
InnoDB加锁读引起的锁争用
正常情况下,InnoDB的读操作是非阻塞的,但在某些情况下需要加锁。特别是在使用基于语句的复制时,执行 INSERT … SELECT 操作会锁定表上的所有行。Mysql需要加锁确保该语句的执行结果在主库和备库上是一致的。实际上,加锁导致主库上的语句串行化,以确保和备库上执行的方式相同。
这种设计会导致锁竞争、阻塞,以及锁等待等超时情况。一种缓解的办法就是避免让事务开启太久以减少阻塞。把大命令拆分成小命令,使其尽可能简短。另一种方式就是替换掉 INSERT … SELECT 语句。
过大的复制延迟
复制延迟是一个很普遍的问题。Mysql单线程复制的设计导致备库的效率相当低下。即使备库有很多磁盘、CPU或这内存。因为备库的单线程通常只会有效的使用一个CPU和磁盘。
复制一般有两种产生延迟的方式:突然产生延迟然后再跟上或者稳定的延迟增大。前者通常是由于一条运行很长时间的查询导致的,而而后者即使没有长时间运行的查询时也会出现。
来自主库的过大的包
受限制的复制带宽
磁盘空间不足
复制的局限性
复制有多快
简单来讲,与Mysql从主库复制事件并在备库重放的速度一样。如果网络很慢并且二进制日志事件很大,记录二进制日志和在备库上执行的延迟可能会非常明显。如果查询需要执行很长时间而网络很快,通常认为查询时间占据了更多的复制时间开销。
Mysql复制的高级特性
第一个是半同步复制,可以帮助确保备库拥有主库数据的拷贝,减少了潜在的数据丢失危险。
半同步复制在提交过程中增加了一个延迟:当提交事务时,在客户端接收到查询结束反馈前必须保证二进制日志已经传输到至少一台备库上,主库将事务提交到磁盘上之后会增加一些延迟。同样的,这也增加了客户端的延迟。因此其执行大量事务的速度不会比将这些事务传递给备库的速度快。
除了半同步复制,Mysql5.5还提供了复制心跳,保证备库一直与主库相联系,避免悄无声息地断开连接。如果出现断开的网络连接,备库会注意到丢失的心跳数据。当使用基于行的复制时,还提供了一种改进的能力来处理主库和备库上不同的数据类型。