1.复制的概述
MySQL 支持2种复制方式:基于行的复制和基于语句的复制。这2种方式都是通过在主库上记录二进制日志,在备库重放日志的方式
来实现异步的数据复制。这意味着,在同一时间点备库上的数据可能跟主库存在不一致。
MySQL 的复制大部分是向后兼容的,新版本的服务器可以作为老版本服务器的备库,但反过来,将老版本作为新版本服务器的备库
通常是不行的。
复制通常不会增加主库的开销,主要是启用二进制日志带来的开销,但出于备份或者及时从崩溃中恢复的目的,这点开销也是必要的。
除此之外,每个备库也会对主库增加一些负载(例如网络IO开销),尤其当备库请求从主库读取旧的二进制日志文件时,可能会造成更高
的IO开销。另外锁竞争也可能阻碍事务的提交。最后,如果从一个高吞吐量的主库上复制到多个备库,唤醒多个复制线程发送时间的开销
会累积增加。
通过复制可以将读操作指向备库来获得更好的读扩展,但对于写操作,除非设计得当,否则并不适合通过复制扩展写操作。
复制要解决的问题:
1.数据分布:
mysql 复制通常不会对带宽造成很大的压力,但在5.1版本引入的基于行的复制会比传统的基于语句的复制模式的带宽压力更大。
2.负载均衡:
通过mysql复制可以将读操作分布到多个服务器上,实现对读密集型应用的优化。
3.备份:
对备份来说,复制是一项有意义的技术补充,但复制既不是备份也不能够取代备份。
4.高可用和故障切换:
复制能够帮助应用程序避免mysql单点故障。
5.MySQL升级测试:
复制如何工作:
1.在主库上把数据更改记录到二进制日志(Binary log)中
第一步是在主库上记录二进制日志。在每次准备提交事务完成数据更新前,主库将数据更新的事件记录到二进制日志中。mysql 会按照事务
提交的顺序而非每条语句执行的顺序来记录二进制日志。在记录二进制日志后,主库会告诉存储引擎可以提交事务了。
2.备库将主库上的日志复制到自己的中继日志(Relay log) (IO线程)
下一步,备库将主库的二进制日志复制到本地的中继日志中。首先,备库会启动一个工作线程,称为IO线程,IO线程跟主库建立一个普通的客户端
连接,然后在主库上启动一个特殊的二进制转储(binlog dump)线程(该线程没有对应的sql命令),这个二进制转储线程会读取主库上二进制日志中
的事件。它不会对事件进行轮询。如果该线程追赶上了主库,它会进入睡眠状态,直到主库发信号量通知其有新的事件产生时才会被唤醒,备库IO线程
会将收到事件记录到中继日志中。
3.备库读取中继日志中的时间,将其重放到备库数据之上(SQL线程)
备库SQL线程执行最后一步,该线程从中继日志中读取事件并在备库执行,从而实现备库数据更新。当sql线程追赶上IO线程时,中继日志通常已经在
系统缓存中,所以中继日志的开销很低。sql线程执行的事件也可以通过配置选项来决定是否写入其自己的二进制日志中。
这种复制架构实现了获取事件和重放事件的解耦,允许这2个过程异步进行。也就是说IO线程能够独立于SQL线程之外工作。这种架构也限制了复制的过程,
其中最重要的一点是主库上并发运行的查询在备库上只能串行化执行,因为只有一个SQL线程来重放中继日志的事件。
2.配置复制
1.创建复制账号
mysql 会赋予一些特殊的权限给复制线程。在备库运行的IO线程会建立一个到主库的tcp/ip连接,这意味着必须在主库创建一个用户,并赋予合适的权限。
备库的IO线程以该用户名连接到主库并读取其二进制日志。
grant replication slave,replication client on *.* to repl@'192.168.0.%' identified by '123456';
复制账号实际上只需要有主库上的 replication slave 权限,并不一定需要每一端服务器都需要有 replication client 权限,那为什么要加上呢?
原因如下:
1.用来监控和管理复制的账号需要 replication client 权限,并且针对这2种目的使用同一个账号更加容易。
2.如果在主库上建立了账号,然后从主库将数据克隆到备库上时,备库也就设置好了---变成主库所需要的配置。方便主备库交换角色。
2.配置主库和从库
主库配置:
主库上需要打开二进制日志并且指定一个独一无二的服务器ID(server ID)。
vim my.conf
log_bin=mysql-bin
server_id=10
show master status; //查看
从库配置:
vim my.conf
log_bin=mysql-bin
server_id=2
relay_log=/var/lib/mysql/mysql-relay-bin
log_slave_updates=1
read_only=1
从技术上说,这些选项不总是必要的。其中一些选项我们只是显示的列出了默认值。事实上只有 server_id 是必须的。这里我们同样使用了 log_bin
并赋予了一个明确的名字。默认情况下,它是根据机器名字来命名的,但如果机器名字变化了可能会导致问题。
我们还增加了2个配置选项: relay_log(指定中继日志的位置和命名)和 log_salve_updates(允许备库将其重放事件也记录到自己的二进制日志中),
后一个选项会给备库增加额外的工作。
read_only 该选项会阻止任何没有特权的线程修改数据。
3.启动复制
告诉备库如何连接到主库并重放其二进制日志。这一步不需要修改 /etc/my.cnf ,而是使用 change master to 语句,该语句替代了 my.conf
中相应的位置,并且允许以后指向别的主库时无需重启备库。
change master to
master_host='192.168.0.116',
master_user='repl',
master_password='123456',
master_log_file='mysql-bin.000001',
master_log_pos=0; // 从头开始复制
show slave status; //查看状态
Slave_IO_State:
Master_Host: 192.168.0.116
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000001
Read_Master_Log_Pos: 4 // 日志的开头不是0而是4,因为0其实不是日志真正的开始
Relay_Log_File: mysql-relay-bin.000001
Relay_Log_Pos: 4
Relay_Master_Log_File: mysql-bin.000001
Slave_IO_Running: No
Slave_SQL_Running: No
Slave_IO_State, Slave_IO_Running, Slave_SQL_Running //当前备库复制尚未开始
//启动复制
start slave;
show slave status;
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.0.116
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000001
Read_Master_Log_Pos: 245
Relay_Log_File: mysql-relay-bin.000002
Relay_Log_Pos: 529
Relay_Master_Log_File: mysql-bin.000001
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master:0
从输出可以看到IO线程和SQL线程都已经开始运行,Seconds_Behind_Master 的值也不再为null。IO线程正在等主库传递过来的事件,这意味着IO
线程已经读取了主库所有的事件。日志位置也发生了变化,表明已经从主库获取和执行了一些事件。
主库:show processlist;
Id: 3
User: repl
Host: 192.168.0.115:43230
db: NULL
Command: Binlog Dump
Time: 350
State: Master has sent all binlog to slave; waiting for binlog to be updated
Info: NULL
Progress: 0.000
备库:show processlist;
Id: 3
User: system user
Host:
db: NULL
Command: Connect
Time: 415
State: Waiting for master to send event
Info: NULL
Progress: 0.000
*************************** 3. row ***************************
Id: 4
User: system user
Host:
db: NULL
Command: Connect
Time: 33 //空闲了33s
State: Slave has read all relay log; waiting for the slave I/O thread to update it
Info: NULL
Progress: 0.000
SQL 线程在备库已经空闲了33秒,这意味着33秒内没有重放任何事件。这些线程总是运行在 'system user'账号下。
4.从另外一台服务器开始复制
大多数情况下有一个运行了一段时间的主库,然后用一台新的备库与之同步,此时备库还没有数据。
有几种办法来初始化备库或者从其他服务器克隆数据到备库。包括主从复制数据,从另外一台备库克隆数据,以及使用最近的一次备份来启动备库,
需要有3个条件来让主库和备库保持同步:
1.在某个时间点的主库的数据快照
2.主库当前的二进制日志文件,和获得数据快照时在该二进制日志文件中的偏移量,我们把这2个值称为日志文件位置。通过这2个值可以确定二进制
日志的位置。可以通过 show master status 命令来获取这些值。
3.从快照时间到现在的二进制日志
下面是一些从别的服务器克隆备库的方法:
1.使用冷备份
关闭主库,把数据复制到备库。重启主库后,会使用一个新的二进制日志文件,我们在备库通过 change master to 指向这个文件的起始处,
这个方法缺点很明显:在复制数据的时候,需要关闭主库
2.使用热备份
如果仅使用了 MyISAM 表,可以在主库运行时使用 mysqlhostcopy 或 rsync 来复制数据。
3.使用 mysqldump
如果只包含 InnoDB 表,那么可以使用如下命令来转储主库数据并将其加载到备库,然后设置对应的二进制日志坐标:
mysqldump --single-transaction --all-databases --master-data=1 --host=server1 | mysql --host=server2
选项 --single-transaction 是的转储的数据为事务开始前的数据。如果使用的是非事务型表,可以使用 --lock-all-tables 选项
来获得所有表的一致性转储。
4.使用快照或者备份
只要知道对应的二进制日志坐标,就可以使用主库的快照或者备份来初始化备库(如果使用备份,需要确保从备份的时间点开始的主库的二进制日志
都要存在)。只需要把备份或快照回复到备库,然后使用 change master to 指定二进制日志的坐标。
5.使用 Percona Xtrabacku
使用另外的备库:
可以使用任何一种提及的克隆或者拷贝技术来从任意一台备库上将数据克隆岛另外一台服务器。但是如果使用的是 mysqldump ,--master-data 选项
将不会起作用。此外,不能使用 show master status 来获得主库的二进制日志坐标,而是在获得快照时使用 show slave status 来获得备库在主库
上的执行位置。
缺点是:如果这台备库的数据已经和主库不同,克隆到的数据就是脏数据。
5.推荐的复制配置
在主库上二进制日志最重要的选项是 sync_binlog:
sync_binlog=1
如果该选项开启,mysql每次在提交事务前会将二进制日志同步到磁盘,保证服务器在崩溃时不会丢失事件。如果禁止该选项,服务器会少做一些工作,但
二进制日志文件可能在服务器崩溃时损坏或者丢失信息。在一个不需要作为主库的备库上,该选项带来的不必要的开销。它只适用于二进制日志,而非中继日志。
如果无法容忍服务器崩溃时表损坏,推荐使用 InnoDB。在表损坏无关紧要时,MyISAM 是可以接受的,但在一次备库服务器崩溃重启后,MyISAM表可能已经
处于不一致状态。一种可能是语句没有完全应用到一个或多个表上,那么即使修复了表,数据也可能是不一致的。
如果使用 InnoDB,我们强烈推荐如下配置:
innodb_flush_logs_at_trx_commit
innodb_support_xa=1
innodb_safe_binlog
这些是mysql5.0默认的配置,我们推荐明确指定二进制日志的名字,以保证二进制日志名在所有服务器上都是一致的,避免因为服务器名的变化而导致日志文件
名的变化。你可能认为以服务器名来命名二进制日志无关紧要,但经验表明,当在服务器间转移文件,克隆新的备库,转储备份或者其他一些你想象不到的场景下,可能
会导致很多问题。为了避免这些问题,需要给 log_bin 选项指定一个参数。可以随意的给一个绝对路径,但必须明确的指定基本的命名:
log_bin=/var/lib/mysql/mysql-bin
在备库上,我们同样推荐开启如下配置,为中继日志指定了绝对路径:
relay_log=/path/to/logs/relay-bin
skip_slave_start
read_only
通过设置 relay_log 可以避免中继日志文件基于机器名来命名。指定绝对路径可以避免多个mysql版本中存在bug,这些bug可能会导致中继日志在一个意外的位置创建。
skip_slave_start 选项能够阻止备库在崩溃后自动启动复制。这可以给你一些机会来修复可能发生的问题。如果备库在崩溃后自动启动并且处于不一致状态,就可能导致
更多的损坏,最后将不得不把所有的数据丢弃,并重新开始配置备库。
read_only 选项可以阻止大部分用户修改非临时表,除了复制sql线程和其他拥有超级权限的用户之外,这也是要尽量避免给正常账号授予超级权限的原因之一。
sync_master_info=1
sync_relay_log=1
sync_relay_log_info=1
如果备库与主库的延迟很大,备库的IO线程可能会写很多中继日志文件,SQL线程在重放完一个中继日志中的事件后会尽量将其删除(通过 relay_log_purge选项来控制)。
但如果延迟非常严重,IO线程可能会把整个磁盘撑爆。解决办法是配置 relay_log_space_limit 变量。如果所有中继日志的大小之和超过整个值,IO线程会停止,等待
SQL线程释放磁盘空间。
3.复制的原理
1.基于语句的复制
在mysql5.0以及之前的版本只支持基于语句的复制(也称为逻辑复制),这在数据库领域是很少见的。基于语句的复制模式下,主库会记录那些造成数据更改的查询,当备库读取
并重放这些事件时,实际上只是把主库上执行的sql再执行一遍。
好处是实现简单。另一个好处是二进制日志更加紧凑,所以相对而言,基于语句的模式不会使用太多带宽。一条更新好几M数据的语句在二进制日志里面可能只占几十字节。
但事实上基于语句的方式可能并不如看起来那么顺利。因为主库上的数据更新除了执行语句外,可能还依赖于其他因素。如执行时间等。存储过程和触发器在使用基于语句的复制
模式时可能也存在问题。
另外一个问题是更新必须是串行的。不是所有的存储引擎都支持这种复制模式。
2.基于行的复制
mysql 5.1 开始支持基于行的复制,这种方式会将实际数据记录在二进制日志中,跟其他数据库很像。最大的好处是可以正确的复制每一行,一些语句可以被更加高效的复制。
由于没有哪种模式对所有情况都是完美的,mysql 能够在这2种复制模式之间动态切换。默认情况下使用的是基于语句的复制方式,但如果发现语句无法被正确复制,就切换到行
的复制模式。还可以根据需要来设置会话级别的变量 binlog_format ,控制二进制日志格式。
3.基于行或基于语句:哪种更优
理论上基于行的复制模式更优。但这种方式太新了以至于没有将一些特殊的功能加入到其中来满足数据库管理员的操作需求。
1.基于语句的复制模式的优点
当主备模式不同时,逻辑复制能够在多种情况下工作。基于语句的复制方式一般允许更灵活的操作。基于语句的方式执行复制的过程基本上就是执行SQL语句。
2.基于语句的复制模式的缺点
很多情况下基于语句的模式无法正确复制。如使用了触发器等。
3.基于行的复制模式的优点
几乎没有基于行的复制模式无法处理的场景。对于所有的sql构造,触发器,存储过程等都能正确的执行。只是当你试图做一些诸如修改表的 schema 这样的事情
时才可能失败。
这种方式同样可以减少锁的使用,因为它并不要求这种强串行化是可重复的。
基于行的复制只会记录数据变更,因此在二进制日志中记录的都是实际上在主库发生变化的数据。另外在一些情况下基于行的二进制日志还会记录发生改变之前的数据,
因此这可能有利于某些数据恢复。
在很多情况下,由于无需像基于语句的复制那样需要为查询建立执行计划并执行查询,因此基于行的复制占用更少的cpu。
最后,在某些情况下,基于行的复制能够帮助更快的找到并解决数据不一致的情况。
4.基于行的复制模式的缺点
由于语句并没有记录在日志里,因此无法判断执行了哪些sql。
基于行的变化的过程就像一个黑盒子,你无法知道服务器在做什么。因此当出现问题时,可能很难找到问题所在。
在某些情况下,如果找不到要修改的行时,基于行的复制可能会导致复制停止,而基于语句不会。这也可以认为是一个优点,可以通过 slave_exec_mode 来进行
配置。
4.复制文件
复制使用到的文件,除了二进制日志和中继日志,还有:
1.mysql-bin.index
当服务器上开启二进制日志时,同时会生成一个和二进制日志同名的但以 .index 作为后缀的文件,该文件用于记录磁盘上的二进制日志文件。
2.mysql-relay-bin-index
中继日志的索引文件。
3.master.info
这个文件用于保存备库连接到主库所需要的信息,格式为纯文本。此文件不能删除,否则备库在重启后无法连接到主库。这个文件以文本的方式记录了复制用户的
密码,所以需要注意此文件的权限控制。
4.relay-log.info
这个文件包含了当前备库复制的二进制日志和中继日志坐标(例如,备库复制在主库上的位置),同样也不要删除,否则在备库重启后将无法获知从哪个位置开始复制,
可能会导致重放已经执行的语句。
以 .index 作为后缀的文件也与设置 expire_logs_days 存在交互,该参数定义了mysql清理过期日志的方式。
5.发送复制事件到其他备库
log_slave_updates 选项可以让备库变成其他服务器的主库。在设置该选项后,mysql会将其执行过的事件记录到它自己的二进制日志中。这样它的备库就可以从其日志
中检索并执行事件。
当第一个备库将从主库获得的事件写入其二进制日志中时,这个事件在备库二进制日志中的位置与其在主库二进制日志中的位置几乎肯定是不同的,可能在不同的日志文件或
文件内不同的位置。这意味着你不能假定所有拥有同一逻辑复制点的服务器拥有相同的日志坐标。这种情况下会使得某些任务更加复杂,例如,修改一个备库的主库或者将备库
提升为主库。
除非你已经注意到了要给每一个服务器配置一个 server ID,否则按照这种方式配置备库会导致一些奇怪的错误,甚至可能会导致复制停止。一个更常见的问题是:为什么
要指定服务器ID ? 为什么mysql 要在意服务器ID是全局唯一的? 答案在于:mysql在复制过程中如何防止无限循环。当复制sql线程读取中继日志时,会丢弃事件中记录的
服务器ID和该服务器本身ID相同的事件,从而打破了复制过程中的无限循环。在某些复制拓扑结构下打破无限循环非常重要,如 主---主复制结构。
6.复制过滤器
复制过滤选项允许你仅复制服务器上的一些数据,不过这可能没想象中那么好用。有2种复制过滤的方式:在主库上过滤记录到二进制日志中的事件,以及在备库上过滤记录到
中继日志的事件。
使用选项 binlog_do_db 和 binlog_ignore_db 来控制过滤。
在备库上使用 replicate_do_db, replicate_do_table, replicate_ignore_db, replicate_ignore_table, replicate_rewrite_db, replicate_wild_do_table,replicate_wild_ignore_table, 在中继日志中读取事件时进行过滤。你可以复制或忽略一个或多个数据库,把一个数据库
重写到另外一个数据库,使用类似 LIKE 的模式复制或者忽略数据库。
4.复制拓扑
可以在任意个主库和备库之间建立复制,只有一个限制:每一个备库只能有一个主库。基本原则是:
1.一个mysql备库实例只能拥有一个主库
2.每个备库必须有一个唯一的服务器ID
3.一个主库可以拥有多个备库
4.如果打开了 log_slave_updates 选项,一个备库可以把其他主库上的数据变化传播到其他备库
1.一主库多备库
事实上一主多备的结构和主从差不多,因为备库之间根本没有交互(从技术上讲这并非正确。如果有重复的服务器ID,它们将陷入竞争,并反复将对方从主库上踢出),
它们仅仅是连接到同一个主库上。
在少量的写和大量读时,这种配置非常有用。可以把读分摊到多个备库上,直到备库给主库造成了太大的负担,或者主备之间的带宽称为瓶颈为止。
用途:
1.为不同的角色使用不同的备库(例如添加不同的索引或使用不同的存储引擎)
2.把一台备库当做待用的主库,除了复制没有其他数据传输
3.将一台备库放到远程数据中心,用作灾难恢复
4.延迟一个或多个备库,以备灾难恢复
5.使用其中一个备库,作为备库,培训,开发或者测试使用的服务器。
这种结构流行的原因是它避免了很多其他拓扑结构的复杂性。例如:可以方便的比较不同备库重放事件在主库二进制日志中的位置。换句话说,如果在同一个逻辑点停止
所有备库的复制,它们正在读取的是主库上同一个日志的相同物理位置。这是个很好的特性,减轻了管理员的许多工作,如把备库提升为主库。
2.主动---主动模式下的主---主复制
主---主复制(也叫做双主复制或者双向复制)包含两台服务器,每一个都被配置成对方的主库和备库,它们是一对主库。主动---主动模式下 主---主复制有一些应用
场景,但通常用于特殊的目的。一个可能的应用场景是2个处于不同地理位置的办公室,并且都需要一份可写的数据拷贝。
这种配置的最大问题是如何解决冲突,2个可写的互主服务器导致的问题非常多。这通常发生在2台服务器同时修改一行记录,或同时在两台服务器上向一个包含
auto_increment 列的表里插数据。
mysql 不支持多主复制。
mysql 5.0 增加了一些特性,使得这种配置稍微安全点,就是设置 auto_increment_increment 和 auto_increment_offset。通过这2个选项可以让mysql自动
为 insert 语句选择不互相冲突的值。
3.主动---被动模式下的主---主复制
这是前面描述的主---主结构的变体,它能够避免我们之前讨论的问题。主要区别在于其中的一台服务器只是只读的被动服务器。
这种方式使得反复切换主动和被动服务器非常方便,因为服务器的配置是对称的。这使得故障转移和故障恢复非常方便。它也可以让你在不关闭服务器的情况下执行维护,
优化表,升级操作系统或其他任务。
例如执行 alter table 操作可能会锁住整个表,阻塞对表的读写操作,这可能会花费很长时间并导致服务中断。然后在主---主配置下,可以先停止主动服务器的备库
复制线程(这样就不会再被动服务器上执行任何更新),然后在被动服务器上执行 alter 操作,交换角色,最后在先前的主动服务器上启动复制线程。这个服务器将会读取中继
日志并执行相同的 alter table 语句。这可能会花费很长时间,不要紧,因为该服务器没有为任何活跃的查询提供服务。
让我们看看如何配置主---主服务器对,在2台服务器上执行如下设置后,会使其拥有对称的设置:
1.确保2台服务器上拥有相同的数据
2.启动二进制日志,选择唯一的服务器ID,并创建复制账号
3.启用备库的更新日志,这是故障转移和故障恢复的关键
4.把被动服务器配置成只读,防止可能与主动服务器上的更新产生冲突,这一点是可选的
5.启动每个服务器的mysql实例
6.将每个主库设置为对方的备库,使用新创建的二进制日志开始工作
让我们看看主动服务器上更新时会发生什么事情。更新被记录到二进制日志中,通过复制传递给被动服务器的中继日志中。被动服务器执行查询并将其记录到自己的二进制
日志(因为开启了 log_slave_updates 选项)。由于事件的服务器ID与主动服务器的相同,因此主动服务器将忽略这些事件。
设置主动-被动的主---主拓扑结构在某种意义上类似于创建了一个热备份,但是可以使用这个 '备份' 来提高性能。然后,你不会比获得单台服务器更好的写性能。
4.拥有备库的主---主结构
这种配置的优点是增加了冗余,对于不同地理位置的复制拓扑,能够消除站点单点失效问题。
5.环形复制
双主结构实际上是环形结构的一种特例。环形结构可以有3个或者更多的主库。每个服务器都是它之前的服务器的备库,是在它之后的服务器的主库。
环形结构没有双主结构的一些优点,例如对称的配置和简单的故障转移。并且完全依赖于环上的每一个节点,这大大增加了系统失效的几率。如果从环中移除一个节点,
这个节点发起的事件就会陷入无限循环:它们将永远围绕着服务器链循环。因为唯一可以根据服务器ID将其过滤的服务器是创建这个事件的服务器。总的来说,环形服务器
非常脆弱,应该尽量避免。
6.主库,分发主库以及备库
我们之前提到当备库足够多时,会对主库造成很大的负载。每个备库会在主库上创建一个线程,并执行 binlog dump 命令。该命令会读取二进制日志文件中的数据并将
其发送给备库。每个备库都会重复这样的工作,它们不会共享 binlog dump 的资源。
如果有很多这样的备库,并且有很大的事件,例如一次很大的 load data infile 操作,主库上的负载会显著上升,甚至可能由于备库同时请求同样的事件而耗尽内存
并崩溃。另一方面,如果备库请求的数据不在文件系统缓冲中,可能会导致大量的磁盘检索,这同样会影响到主库的性能并增加锁的竞争。
因此,如果需要多个备库,一个好的办法是从主库移除负载并使用分发主库。分发主库事实上也是一个备库,它的唯一目的就是提取和提供主库的二进制日志。多个备库
连接到分发主库,这使原来的主库摆脱了负担。为了避免在分发主库上做实际的查询,可以将它的表修改为 blackhole 存储引擎。
很难说当备库数据达到多少时需要一个分发主库。按照通用标准,如果主库接近满负载,不应该为期建立10个以上的备库。如果有少量的写操作,或者只复制其中一部分表,
主库就可以提供更多的复制。另外,也不一定只使用一个分发主库。如果需要的话,可以使用多个分发主库向大量的备库进行复制,或者使用金字塔的分发主库。在某些情况下,
可以通过设置 slave_compressed_protocol 来节约一些主库带宽。这对跨数据中心的复制很有好处。
还可以通过设置分发主库来实现其他目的,例如,对二进制日志事件执行过滤和重写规则。这比在每个备库上重复进行日志记录,重写和过滤要高效的多。
如果在分发主库上使用 blackhole 表,可以支持更多的备库。虽然会在分发主库上执行查询,但其代价非常小,因为 blackhole 表中没有任何数据。blackhole 表的
缺点是存在 bug。
一个比较常见的问题是如何确保分发服务器上的每个表都是 blackhole 存储引擎。设置服务器的 storage_engine=blackhole。这只会影响那些没有指定存储引擎的
create table 语句。通过 skip_innodb 选项禁止 InnoDB,将表退化为 MyISAM。
使用分发主库的另外一个主要缺点是,无法使用一个备库替代主库。因为由于分发主库的存在,导致各个备库与原始主库的二进制日志坐标已经不同。
7.树或者金字塔形
如果正在将主库复制到大量的备库中。不管是把数据分发到不同的地方,还是需要提供更高的读性能,使用金字塔结构都能很好的管理。
这种设计的好处是减轻了主库的负担,缺点是中间层出现的任何错误都会影响到多个服务器。如果每个备库和主库直接相连就不会存在这样的问题。同样,中间层越多,
处理故障会更困难,更复杂。
8.定制的复制方案
mysql 的复制非常灵活,可以根据需要定制解决方案。如,组合过滤,分发和向不同的存储引擎复制。
1.选择性复制
为了利用局部性原理,并将需要读的工作集驻留在内存中,可以复制少量数据到备库中。如果每个备库只拥有主库的一部分数据,并且将读分配给备库,就可以很好
的利用备库的内存。并且每个备库也只有主库一部分的写入负载,这样主库的能力更强并且能保证备库的延迟。
这个方案有点类似水平数据划分,但它的优势在于主库包含了所有的数据集,这意味着无需为了一条写入查询去访问多个服务器。如果读操作无法再备库上找到数据,
还可以通过主库来查询。即使不能从备库上读取所有数据,也可以移除大量的主库读负担。
2.分离功能
许多应用都混合了在线事务处理(OLTP)和在线数据分析(OLAP)的查询。OLTP查询比较短,且都是事务型的,OLAP查询则通常非常大,也很慢,并且不要求绝对是最新
的数据。这2种查询给服务器带来的负担完全不同,因此它们需要不同的配置,甚至可能使用不同的存储引擎或者硬件。
一个常见的方法是将 OLAP服务器的数据复制到专门为 OLAP 工作负载准备的备库上。
3.数据归档
可以在备库上实现数据归档,也就是说可以在备库上保留主库上删除的数据,在主库上delete语句删除数据是确保delete语句不传递到备库就可以实现。有2种通常
的办法:1.是在主库上选择性的禁止二进制日志,2.是在备库上使用 replicate_ignore_db规则。
第一种方法需要将 SQL_LOG_BIN 设置为0,然后再进行数据清洗。这种方法的好处是不需要在备库上进行任何设置,由于sql语句根本没有记录到二进制日志中,效率
所有提升。最大的缺点也正因为没有将主库的修改记录下来,因此无法使用二进制日志来进行审计或者按时间点的数据恢复,另外还需要 super 权限。
第二种方法是在清理数据之前对主库上特定的数据库使用 use 语句。例如,可以创建一个名为 purse 的数据库,然后在备库的 my.conf 文件里设置
replicate_ignore_db=purge 并重启服务器。备库将会忽略使用了 use 语句指定的数据库。这种方法没有第一种方法的缺点,但是它的缺点是:备库需要去读取它不需要
的事件。
4.将备库用作全文检索
许多应用要求合并事务和全文索引。仅MyISAM 支持全文索引,但是MyISAM 不支持事务。一个普遍的做法是配置一台备库,将某些表设置为 MyISAM,然后创建全文
索引并执行全文查询。这样避免了在主库上同时使用事务型和非事务型存储引擎带来的复制问题,减轻了主库维护全文索引的负担。
5.只读备库
许多机构会将备库设置为只读,以防止在备库进行无意识修改导致复制中断。可以通过设置 read_only 选项来实现。它会禁止大部分写操作,除了复制线程和拥有
超级权限的用户以及临时表操作。
6.模拟多主库复制
当前的mysql并不支持多主库复制(一个备库拥有多个主库)。但是可以通过把一台备库轮流指向多台主库的方式模拟这种结构。例如,可以先将备库指向主库A,运行片刻,
然后将备库指向主库B,然后再切回备库A。
7.创建日志服务器
使用mysql复制的另外一个用途就是创建没有数据的日志服务器。它唯一的目的就是更加容易重放并且/或者过滤二进制日志事件。
5.复制和容量规划
写操作通常是瓶颈,并且很难使用复制来扩展写操作。
1.为什么复制无法扩展写操作
复制只能扩展读操作,无法扩展写操作。对数据进行分区是唯一扩展写入的方法。
2.备库什么时候开始延迟
3.规划冗余容量
有意让服务器不被充分利用,是一种聪明且划算的方法。
6.复制管理和维护
show master status;
show master logs; //这个命令用于给 PURGE MASTER LOGS 命令决定用哪些参数
show binlog events; //查看复制事件
show binlog events in 'mysql-bin.000001' from 397; // 可以看 pos 397 以后的事件
测试备库延迟:
show slave status;
Seconds_Behind_Master
一个比较普遍的问题是如何监控备库落后主库的延迟有多大。虽然 show slave status; 输出的 Seconds_Behind_Master
理论上显示了备库的延迟,但由于各种各样的原因,并不总是准确的:
1.备库 Seconds_Behind_Master 值是通过将服务器当前的时间戳与二进制日志中的事件的时间戳做对比得到的,所以只有执行事件时
才能报告延迟。
2.如果备库复制线程没有运行,就会报告延迟为 null
3.一些错误(例如 max_allowed_packet 不匹配,或者网络不稳定)可能中断复制并且/或者停止复制线程,但 Seconds_Behind_Master
将显示为0而不是显示错误。
4.一个大事务可能会导致延迟波动。例如,有一个事务更新数据长达一小时,最后提交。这条更新将比它实际发生时间要晚一个小时才记录到二进制日志
中。当备库执行这条语句时,会临时的报告备库延迟为一个小时,然后又很快的变成0
5.如果分发主库落后了,并且其本身也有已经追上了它的备库,备库的延迟将显示为0,而事实上和源主库之间是有延迟的。
最好的解决办法是 heartbeat record,这是一个在主库上会每秒更新一次的时间戳。为了计算延迟,可以直接用备库当前的时间减去心跳记录的值。
另外一个好处是我们还可以通过时间戳知道备库当前的复制情况。包含在 Percona Toolkit 里的 pt-heartbeat 脚本是通过'心跳复制'最流行的实现。
还有其他好处,记录在二进制日志中的心跳记录,还可以用于灾难恢复。
确定主备是否一致:
即使没有明显的错误,备库同样可能因为 mysql 自身的特性导致数据不一致,例如mysql 的 bug, 网络中断,服务器崩溃等。
主备一致应该是一种规范,而不是例外。也就是说,检查你的主备一致性应该是一个日常工作。
Percona Toolkit 里的 pt-table-checksum 能够解决上述问题。其主要特性是用于确认备库和主库的数据是否一致。工作方式是通过在主库上
执行 insert ... select 查询。
这些查询对数据进行校验并将结果插入到一个表中。这些语句通过复制传递到备库,并在备库上执行一遍,然后可以比较主备上的结果是否一样。由于
该方法是通过复制工作的,它能够给出一致的结果而无需同时把主备上的表都锁上。
通常情况下是可以在主库上运行该工具的,参数如下:
pt-table-checksum --replicate=test.checksum <mast_host>
该命令检查所有的表,并将结果插入到 test.checksum 表中。当查询在备库执行完后,就可以简单 的比较主备之间的不同了。pt-table-checksum
能够发现服务器所有的备库,在每台备库上运行查询,并自动的输出结果。
在主库上重新同步备库:
传统修复不一致的办法是关闭备库,然后重新从主库复制一份数据。
最简单的方法是使用 mysqldump 存储受影响的数据并重新导入。在整个过程中,如果数据没有发生变化,这种方法很好。你可以在主库上简单的锁住
表然后进行转储,再等备库赶上主库,然后将数据导入备库中。
这种方法在许多场景下是可行的,但在一台繁忙的服务器上有可能行不通。另外一个缺点是在备库上通过非复制的方式改变数据。通过复制改变备库的
数据(通过在主库上更新)通常是一种安全的技术 ,因为它避免了竞争条件和其他意外的事情。
pt-table-sync 的一种工具。
改变主库:
使用 change master to 命令,并指定合适的值。备库将抛弃之前的配置和中继日志并从新的主库开始复制。同样新的参数会被更新到 master.info
文件中,这样就算重启,备库配置信息也不会丢失。
整个过程最难的是获取新主库上合适的二进制日志位置,这样备库才可以从何老主库相同的逻辑位置开始复制。
把备库提升为主库要更困难一点。有2种场景需要将备库替换为主库,一种是计划内的提升,一种是计划外的提升。
计划内的提升:
步骤:
1.停止向老的主库写入
2.让备库赶上主库
3.将一台备库配置为新的主库
4.将备库和写操作都指向新的主库,然后开启主库的写入
flush tables with read lock; //停止所有活跃的写入
更深入的步骤:
1.停止当前主库上所有的写操作。
2.通过 flush tables with read lock 在主库上停止所有活跃的写入。也可以在主库上设置 read_only,但不会阻止已经存在的事务继续提交,可以kill掉。
3.选择一个备库作为新的主库,并确保它已经跟上了主库
4.确保新主库和旧主库的数据是一致的。可选
5.在新主库上执行 stop slave
6.在主库上执行 change master to master_host='',然后再执行 reset slave, 使其断开与老主库的连接,并丢弃 master.info 里记录的信息。
7.执行 show master status 记录更新主库的二进制日志坐标
8.确保其他备库已经赶上
9.关闭旧主库
10.在mysql 5.1 以上版本中,如果需要,激活新主库上的事件
11.将客户端连接到新主库
12.在每台备库上执行 change master to 语句
计划外的提升:
假设已经在所有的备库上开启了 log_bin 和 log_slave_updates ,这样可以帮助你将所有的备库恢复到一个一致的时间点。
确定期望的日志位置:
如果有备库和新主库的位置不相同,则需要找到备库最后一条执行的事件在新主库的二进制日志中相应的位置,然后执行 change master to 。可以通过mysqlbinlog
工具来找到备库执行的最后一条查询,然后在主库上找到同样的查询,进行简单的计算即可得到。
在一个主---主配置中交换角色:
主---主复制拓扑结构的一个好处是很容易切换主动和被动的角色,因为其配置是对称的。
当主---主配置下切换角色,必须保证任何时候只有一个服务器可以写入。步骤如下:
1.停止主动服务器上的写入
2.在主动服务器上设置 set global read_only=1,同时配置文件也设置下,防止重启失效。
flush tables with read lock;
kill 所有线程
3.在主动服务器上执行 show master status 并记录二进制日志的坐标。
4.使用主动服务器上的二进制日志坐标在被动服务器上执行 select master pos_wait()。该语句将阻塞,直到复制跟上主动服务器。
5.在被动服务器上执行 select global read_only=0,这样主动服务器就变成被动服务器
6.修改应用配置
7.复制的问题和解决方案
1.数据损坏或丢失的错误
1.主库意外关闭
如果没有设置主库的 sync_binlog 选项,就可能在崩溃之前没有将最后的几个二进制日志刷新到磁盘中。备库IO线程因此也可一直处于读不到尚未
写入到磁盘的事件的状态中。当主库重新启动时,备库将重连到主库并再次尝试读该事件,但主库会告诉备库没有这个二进制日志偏移量。
解决这个问题的办法是指定备库从下一个二进制日志文件的开头读日志。即便开启了 sync_binlog, MyISAM 表的数据仍然可能在崩溃的时候损坏,
对于 InnoDB 事务,如果 innodb_flush_log_at_trx_commit 没有设置为1,也可能丢失数据,但数据不会损坏。
2.备库意外关闭
当备库在一次非计划的关闭重启后,会去读 master.info 文件以找到上次停止复制的位置。不幸的是,该文件并没有同步到磁盘,文件中存储的信息
可能是错的。备库可能会重新执行一些二进制日志事件,这可能会导致唯一索引错误。
如果使用的是 InnoDB 表,可以在重启后观察 MySQL 错误日志。InnnoDB在恢复过程中会打印出它恢复点的二进制日志坐标。可以使用这个值来决定
备库指向主库的偏移量。
磁盘上的二进制日志或中继日志文件损坏并不罕见。
1.主库上的二进制日志损坏
如果主库上的二进制日志损坏,除了忽略损坏的位置你别无选择。可以在主库上执行 flush logs 命令,这样主库会开始一个新的日志文件,然后备库
指向该文件的开始位置。某些情况下,可以设置 set global sql_slave_skip_counter=1 来忽略一个损坏的事件。
2.备库上的中继日志损坏
如果主库的日志是完好的,可以通过 change master to 命令丢弃并重新获取损坏的事件。只需要将备库指向它当前正在复制的位置(relay_master_log_file/
exec_master_log_pos)。这会导致备库丢弃所有在磁盘上的中继日志。
3.二进制日志与 InnoDB 事务日志不同步
当主库崩溃的时候,InnoDB 可能将一个事务标记为已提交,此时该事务可能还没有记录到二进制日志中。除非是某个备库的中继日志已经保存,否则没有任何办法恢复
丢失的事务。在mysql5.0 版本可以设置 sync_binlog 选项来阻止该问题。
当一个二进制日志损坏时,能恢复多少数据取决于损坏的类型,有几种常见的类型:
1.数据改变,但事件仍然是有效的sql
不幸的是,mysql甚至无法察觉这种损坏。因此最好还是在备库经常检查备库的数据是否正确。
2.数据改变并且事件是无效的sql
这种情况可以通过 mysqlbinlog 提取出事件并看到一些错误的数据。如 update tbl set col???
可以通过增加偏移量的方式来尝试找到下一个事件。
3.数据遗漏并且/或者事件的长度是错误的
这种情况下,mysqlbinlog 可能会发生错误并且直接崩溃,因为它无法读取事件,并且找不到下一个事件的开始位置。
4.某些事件已经损坏或被覆盖,或者偏移量已经改变并且下一个事件的开始偏移量也是错误的。
同样,mysqlbinlog 也起不来什么作用。
mysqlbinlog mysql-bin.000001 | grep 'at'
strings -n 2 -t d mysql-bin.000001
2.使用非事务型表
如果一切正常,基于语句的复制通常能够很好的处理非事务型的表。但是当对非事务型表的更新发生错误时,例如查询在完成之前被 kill,就可能导致
主库和备库的数据不一致。
例如,更新一个MyISAM表的100行数据,若查询更新到了其中50行被kill了,一半的数据改变,一半没改变,结果是复制必然不同步,因为该查询会在
备库重放并更新完100行数据。
如果使用的是MyISAM表,在关闭mysql之前需要确保运行了 stop slave,否则服务器在关闭时会kill所有正在运行的查询。事务型存储引擎则没有这
个问题。如果使用的是事务型表,失败的更新会在主库上回滚并且不会记录到二进制日志中。
3.混合事务型和非事务型表
如果使用的是事务型存储引擎,只有在事务提交后才会将查询记录到二进制日志中。因此事务回滚,mysql 就不会记录这条查询,也就不会在备库上重放。
但如果是混合使用事务型和非事务型表,并且发生了一次回滚,mysql 能够回滚事务型表的更新,但非事务型表则被永久的更新了。只要不要发生类似查询
中途被kill,这就不是问题:mysql 此时会记录该查询并记录一条 rollback 语句到日志中。结果是同样的语句也在备库执行,所有的都很正常。这样效率会
低点,因为备库需要做一些工作并且最后再把它们丢弃。但理论上能够保证主备的数据一致。
但是如果备库发生死锁而主库没有也可能会导致问题。事务型表的更新会被回滚,而非事务型表则无法回滚,此时备库和主库的数据是不一致的。
防止该问题的唯一办法是避免混合使用事务型和非事务型的表。
基于行的复制不会受到这个问题的影响。因为它记录的是数据的更改,而不是sql语句。如果一条语句改变了一个MyISAM表和一个InnoDB表的某些行,然后
主库上发生了一次死锁,InnoDB 表的更新会被回滚,而MyISAM 表的更新仍然会记录到日志中并在备库重放。
4.不确定语句
使用基于语句的复制模式时,如果通过不确定的方式更改数据可能会导致主备不一致。例如,一条带 limit 的update 语句更改的数据取决于查找行的顺序,
除非能够保证主库和备库上的顺序相同。例如,若行根据主键排序,一条查询可能在主库和备库上更新不同的行,这些问题非常微妙且难以察觉。所以有些人禁止
更新数据使用 limit。另外一个不确定的行为是在一个拥有多个唯一索引的表上使用 replace 或者 insert ignore 语句---mysql在主库和备库上可能会选择
不同的索引。
基于行的复制则没有上述问题。
5.主库和备库使用不同的存储引擎
在备库上使用不同的存储引擎,有时候可以带来好处。但是在一些场景下,当使用基于语句的复制方式时,如果备库使用了不同的存储引擎,则可能造成
一条查询在主库和备库上的执行结果不同。
如果发现主库和备库的某些表已经不同,除了检查更新这些表的查询外,还需要检查2台服务器上使用的存储引擎是否相同。
6.备库发生数据改变
基于语句的复制方式前提是备库上有和主库相同的数据,因此不应该允许对备库进行数据的任何修改(设置 read_only)。
insert into table1 select * from table2;
如果备库 table2 的数据和 主库不同,该语句会导致 table1 也会不同。也就是说,数据的不一致会在表之间传播。
唯一的解决办法是重新从主库同步数据。
7.不唯一的服务器ID
在主库上会发现2台备库只有一台连接到主库。在备库的错误日志中,则会发现反复的连接和连接断开的信息,但不会提及被错误配置的服务器ID.
8.未定义的服务器ID
如果没有在 my.conf 里定义服务器ID, 可以通过 change master to 来设置备库,但无法启动复制。执行 select @@server_id 也可以获得一个值,
但这只是默认值,必须为备库显示的设置服务器id。
9.对未复制数据的依赖性
如果主库上存在有备库不存在的数据库或者表,复制很容易中断,反之亦然。原先在备库有一些操作,然后主备切换的时候,忘了。。
10.丢失的临时表
临时表在某些时候比较有用,但不幸的是,它与基于语句的复制方式是不相容的。如果备库崩溃或者正常关闭,任何复制线程拥有的临时表都会丢失。重启备库
后,所有依赖于该临时表的语句都会失败。
11.不复制所有的更新
错误的使用 set sql_log_bin=0或者没有理解过滤规则,备库可能会丢失主库上已经发生的更新。例如,假设设置了 replication_do_db 规则,把sakila
数据库的数据复制到一台备库。如果在主库上执行了下面语句,会导致主备数据不一致:
use test;
update sakila.actor ...
12.InnoDB 加锁读引起的锁争用
正常情况下,InnoDB的读操作是非阻塞的,但在某些情况下是需要加锁的。特别是在使用基于语句的复制方式,执行 insert ... select 操作会锁定源表上的
所有行。mysql 需要加锁以确保该语句的执行结果在主库和备库上是一致的。实际上加锁导致主库上的语句串行化,以确保和备库上的执行方式是一样的。
这种设计导致锁竞争,阻塞以及锁等待超时等。一种方法就是避免让事务开启太久。把大命令拆成小命令。另外一种方法是替换掉 insert ... select 语句,
先在主库上执行 select into outfile,再执行 load data infile。
13.在主---主复制结构中写入2台主库
在mysql5.0中,有2个变量可以用于帮助 auto_increment 自增主键冲突问题:auto_increment_increment 和 auto_increment_offset。通过设置
这2个变量来错开主库和备库生成的数字,这样可以避免自增列的冲突。
14.过大的复制延迟
最好在设计应用程序时能够让其容忍备库出现延迟。如果系统在备库延迟时就无法好好工作,那么应用程序也许就不应该用到复制。
mysql 单线程复制的设计导致备库的效率相当低下.即使备库有很多磁盘,cpu或者内存,也会很容易落后于主库。因为备库的单线程通常只会有效的使用一个cpu
和磁盘。
复制一般有2种产生延迟的方式:突然产生延迟然后再跟上,或者稳定的延迟增大。前一种通常是由于运行了一条很长时间的查询导致的,而后者即使在没有长时间的
查询也会出现。
当备库无法跟上时,可以记录备库上的查询并使用一个日志分析工具分析哪里慢了。最好的办法是暂时在备库上打开慢查询日志,然后使用 pt-query-digest。如果
打开了 log_slow_slave_statements 选项,在标准的mysql 慢查询日志中可以记录复制线程执行的语句。
除了购买更快的磁盘和cpu,备库没有太多的调优空间。大部分选项都是禁用某些额外的工作以减少备库的负载。一个简单的办法是通过配置
innodb_flush_log_at_trx_commit 的值为2来实现。还可以在备库上禁用二进制日志记录,把 innodb_locks_unsafe_for_binlong设置为1,并把MyISAM的
delay_key_write 设置为 ALL。牺牲安全换取速度。
不要重复写操作中代价比较高的部分:
重构应用程序并且/或者优化查询通常是最好的保持备库同步的办法。任何在主库上昂贵的写操作都会在每一个备库上重放。如果可以把工作转移到备库,那么就只有
一台备库需要执行,然后我们可以把写的结果回传到主库。例如,通过执行 load data infile。
在复制之外并行写入:
另外一种避免备库严重延迟的办法是绕过复制。任何在主库的写入操作必须在备库串行化。对写入进行分区,如果能确定一些写入可以轻易的在复制之外执行,就可以
并行化这些操作以利用备库的写入容量。
一个很好的例子是数据归档。如果只是把不需要的记录从一个表移到另外一个表,就没必要将这些写入复制。
为复制线程预取缓存:
如果有正确的工作负载,就能通过预先将数据读入内存中,以受益于在备库上的并行IO所带来的好处。
如果表上有很多索引,同样无法预取所有将要被修改的数据。update 语句可能需要更新所有的索引,但select语句通常只会读取主键和一个二级索引。update
语句依然需要去读取其他索引的数据并更新。在多索引表上这种方法比较低效。
15.来自主库的的过大的包
另外一个难以追踪的问题是主库的 max_allowed_packet 的值和备库的不匹配。这种情况下主库可能会记录一个备库认为过大的包。当备库获取该二进制日志时,
可能会碰到各种问题,包括无限报错和重试,或者中继日志损坏。
16.受限制的复制带宽
如果使用受限制的带宽进行复制,可以开启备库上的 slave_compressed_protocol 选项。当备库连接上主库时,会请求一个被压缩的连接 --- 和mysql客户端
使用的压缩连接一样。使用的压缩引起是 zlib。需要的代价是需要额外的cpu时间。
17.磁盘空间不足
可以通过监控磁盘并设置 relay_log_space 选项来避免这个问题。
18.复制的局限性
8.复制有多快
它与mysql主从库复制事件病在备库重放的速度一样快。
9.MySQL 复制的高级特性
1.半同步复制
它可以确保备库拥有主库数据的拷贝,减少了潜在的数据丢失风险。半同步复制在提交过程中增加了一个延迟:当事务提交时,在客户端接收到查询结束反馈前
必须保证二进制日志已经传输到至少一个备库上。主库讲事务提交到磁盘上去之后会增加一些延迟。同样,这也增加了客户端的延迟。
2.复制心跳
10.其他复制技术
Tungsten