mysql的复制

这篇算是我的学习笔记,主要内容都来自[mysql官方文档](http://dev.mysql.com/doc/refman/5.1/en/replication.html),然后参考一些网上的文章自己实践了个例子,所以内容上前后也有些重复。

mysql支持从一个主服务器到多个从服务器的复制,并且复制是异步的,也就是说从服务器不用一直保持与主服务器的连接,这使得一些远程、临时性的节点也能进行同步。同时,还可以指定参与同步的数据库或者数据表。

同步目前主要用于以下几个方面:

  • 架构扩容,一主写多从读的方式已经用得非常普遍了
  • 数据安全,同步也是一种备份,而且还可以是实时的,多好啊
  • 数据分析,大量的分析运算在从服务器上运行,不影响主系统的负载
  • 远程数据发布,或者说远程备份

同步的基本设置

主服务器的my.cnf设置

同步是通过binlog来实现的,虽然主服务器不能决定哪些操作在binlog中记录(默认记录全部),但从服务器可以根据配置来有选择的执行。每个主、从服务器都要有一个唯一ID,从服务器还要记录主服务器的主机名、log文件名以及位置等信息(在master.info中),并保存一份收到的binlog,这样多台从服务器就可以有不同的同步状态或进度,并且下次连线的时候再以“续传”的方式继续同步进程。binlog的开启需要修改主服务器的my.cnf

# 必须写在[mysqld]节里面
[mysqld]
# mysql-bin是log文件的前缀,也可以使用其它的名字,比如服务器名
# 如果不带路径,会把log文件写到`/var/lib/mysql`下
log-bin=mysql-bin
# serverid在一个同步体系中必须是唯一的,大于等于1且小于2^32-1的整数
server-id=1

如果使用InnoDb,为了保证稳定,还应设置如下两行:

innodb_flush_log_at_trx_commit=1
sync_binlog=1

并且确保没有设置skip-networking,禁用网络自然无法同步。但是,innodb_flush_log_at_trx_commit=1在某些服务器上会导致写数据速度急剧下降,可尝试调整为2。

用户

每个从服务器都要使用标准的mysql用户来连接主服务器,对应的权限名称为REPLICATION SLAVE(这是一个和库无关的权限,属于服务器管理权限的一种)。虽然使用独立用户并不是必需的,但由于用户名和密码都将以明文方式存储在记录replication相关信息的master.info文件中,还是采用独立用户更安全一些。语法如下:

mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%.domain.tld' IDENTIFIED BY 'passwd';

从服务器的my.cnf设置

server-id是必须设置的,binlog不用开。

获取主服务器的同步信息

从服务器必须知道要从主服务器binlog的哪个位置开始进行同步,相当于有一份数据、一份要执行的指令(binlog)以及一个当前执行哪一条指令,要获得这三个信息,必须临时暂停主服务器上的处理:

mysql> FLUSH TABLES WITH READ LOCK;

(这个连InnoDB的commit都能停住),暂停状态下数据、指令就都不会变了,查询出当前记录的binlog的位置:

mysql > SHOW MASTER STATUS;

得到的File列是当前的binlog记录文件名,Position就是日志记录,如果还没东西可记,File为空字符串而Position为4。后面设置从服务器的时候,CHANGE MASTER TO命令就要用到这些信息,比如:

mysql> CHANGE MASTER TO
    ->     MASTER_HOST='master_hostname',
    ->     MASTER_USER='repl_username',
    ->     MASTER_PASSWORD='repl_password',
    ->     MASTER_LOG_FILE='binlog_filename',
    ->     MASTER_LOG_POS=binlog_position;

复制主服务器的数据到从服务器

保持主服务器的暂停处理状态,先把数据都弄到从服务器上去。导出可以用mysqldump:

mysqldump -h db_host --add-drop-table --default-character-set=utf8 --user=root -pPASSWD --extended-insert=false db_name > db_name.sql

带上--all-databases参数可以导出所有库,不过不想参与同步的库是没必要导的。如果带上--master-data参数,还能够把从服务器设置的一些sql一并导出来。

当然,直接拷贝数据库文件也是可以的,速度也更快,但如果在mysql服务启动的状态下拷贝,可能会存在缓存、日志不同步的情况,所以要停掉服务以后再拷贝,并且不要拷贝和目标库无关的日志等文件。

为了进一步保证数据的一致性,可以在导出数据前给所有数据加锁:

mysql> FLUSH TABLES WITH READ LOCK;

导出完成后再解开:

mysql> UNLOCK TABLES;

同步的进阶说明

binlog的几种形式

一种是记录所操作的“命令”(mysql 5.1.4前版本只能用这个),简称为命令复制SBR;另外一种是记录所有受影响的行,简称为行复制RBR。从mysql 5.1.8开始支持包含上述两种方式的混合型日志。命令复制的优点是:

  • 产生的日志小
  • 不要求每个表都要有主键

缺点是:

  • 某些函数的返回值是无法复制的(比如LOAD_FILE(), UUID(), UUID_SHORT(), USER(), FOUND_ROWS(), SYSDATE(),但RAND(), NOW()能正常复制)
  • 写数据时用到的锁较多
  • 主从服务器的表必须一样

行复制的优点为:

  • 最安全,所有的操作都能记录和同步
  • 写数据的时候使用到的锁较少

缺点为:

  • 写大量数据时(比如全表UPDATE)日志里要写很多东西,日志锁定时间也相对更长,可能会带来一些并发问题
  • 所有的表必须要有显式主键(一般情况下都会有主键的,除非超大数据量或特殊设计)
  • 带有大文本的数据复制可能要慢一些,这个好理解,一个update操作,如果是行复制的话,肯定是更新了n行,复制传递的数据量就是n倍大小
  • 查看更新传递和执行的进度不方便
  • 无法用WHERE @server_id <> server-id这个小技巧来临时指定只更新某台服务器

在具体处理的时候,依然会有一些操作比如GRANT REVOKE之类是用命令方式同步的。总体来讲,经常要进行大量数据变更的情况下,命令复制似乎更合适一些。

常用配置参数说明

控制主服务器的:

  • auto_increment_increment, auto_increment_offset 自增主键的跳号间隔和初始值,有什么用呢?有人作双向复制的时候,设置A服务器主键自增从1开始每次跳10,B服务器则从2开始每次跳10,这样双向复制的过程中就不会产生自增主键重号的问题了。这两个是全局而非针对某表的设置。不过我更倾向于用UUID实现。
  • slave_exec_mode 从服务器容错方式,5.1.24后版本才有。

控制从服务器的:

  • master.info中配置的优先级比my.cnf中的高。
  • log-slave-updates 从服务器的变更也记入日志,作链式复制时必须。
  • replicate-do-db=db_name 只复制指定数据库,可以写多行来指定复制多个库。注意在命令复制模式下mysql只认用use db_name选定的,不会从跨表操作的sql中自动判断;而行复制则是依照实际变更的数据属于哪个表来判断。
  • replicate-rewrite-db=”from_name->to_name” 主、从数据库名称不一样的时候,用此命令进行转换。

控制日志的:

  • binlog_format 日志类型,ROW或STATEMENT,新版中还可选MIXED。
  • log-bin[=base_name] 开启二进制日志(复制必须),可以指定日志文件名。
  • sync_binlog 什么时候写日志到磁盘,这个机制比较复杂,一般设为1相对较安全。

同步的管理

有一些命令:

  • SHOW SLAVE STATUS 在从服务器上查看状态。
  • SHOW SLAVE HOSTS 在主服务器上,启用了report-host以后,显式已连接的从服务器。
  • STOP/START SLAVE [IO_THREAD/SQL_THREAD] 在从服务器上,停止复制(的日志下载/SQL执行操作)。

同步实现实例

复制还是在局域网内方便,尤其是双向复制,因为跨公网的话不光要连进去,还要从里面连出来。 综合考虑,M-M-Slaves的模式的一般适用性可能更强一些。下面以Master-Master简要举例:

首先分别修改M1/2的my.cnf:

[mysqld]
# Replication
server-id=1/2
log-bin=/var/log/mysql/svr1/2
log-error=/var/log/mysql.err
relay-log=/var/log/mysql/svr1/2-relay
binlog_format='STATEMENT'
sync_binlog=1
log-slave-updates
innodb_flush_log_at_trx_commit=2
binlog-do-db=db_to_replicate

master-host=ip_of_M1/2
master-port=port_of_M1/2
master-user=rep
master-password=passwd
master-connect-retry = 60
replicate-do-db=db_to_replicate

然后在M1/2上创建同步用户:

mysql> grant replication slave on *.* to 'rep'@'%' identified by 'passwd';

重启M1/2的mysql服务,并停止slave:

mysql> stop slave;

下面,把数据从M1上复制到M2上,先在M1上:

mysql> use db_to_replicate;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> flush tables with read lock;
Query OK, 0 rows affected (0.00 sec)

mysql> show master status;
+-------------+----------+--------------+------------------+
| File        | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+-------------+----------+--------------+------------------+
| svr5.000002 |       98 | db_to_replicate   |                  | 
+-------------+----------+--------------+------------------+
1 row in set (0.00 sec)

目的是锁上M1上所有表(为只读状态),并记下当前binlog的记录位置。然后不要退出mysql,另外开一个session,导出数据:

$ mysqldump -h 127.0.0.1 --add-drop-table --default-character-set=utf8 --user=root -p --extended-insert=false --skip-lock-tables db_to_replicate > db_to_replicate.sql

mysqldump时用参数指定不再次加锁,也可以采用直接拷贝数据库文件的方式(导入时更快,也得更小心一点)。导出完成后可以解锁,让M1正常运行了:

mysql> unlock tables;

现在到M2上去,用刚才导出的数据把库建起来(如果采用直接执行sql的方式,记得先停掉binlog),然后进行比较重要的一步,让M2从M1刚才导出数据的时候开始恢复同步:

mysql> stop slave;
Query OK, 0 rows affected (0.00 sec)

mysql> CHANGE MASTER TO MASTER_HOST='ip_of_M1', MASTER_PORT=port_of_M1, MASTER_USER='rep', MASTER_PASSWORD='passwd', MASTER_LOG_FILE='svr5.000002', MASTER_LOG_POS=98;
Query OK, 0 rows affected (0.01 sec)

mysql> start slave;
Query OK, 0 rows affected (0.01 sec)

最后两个参数就是刚才在M1上用show master status;得到的数值。

同理,在M1上也作一次CHANGE MASTER TO操作,指向M2,两边的双向主主复制基本上就算是完成。

另外注意以后切换MASTER的时候,直接修改my.cnf是不行的,因为都存到优先级更高的master.info文件中去了,还是用CHANGE MASTER TO来操作更靠谱。

通过ssh进行远程同步的连接方法

以M2远程登录M1的方式为例,M2 ssh到M1,以便M2作为slave能连接master M1,使用的是ssh隧道:

M2 $  ssh -p 8022 -L 127.0.0.1:3307:ip_of_M1:3306 -CfNg user@domain.tld -o ControlPath=/tmp/ssh-mysql-3307-M1-3306

双向同步时,M1还要连接到M2才行,依然是从M2上来打洞:

M2 $ ssh -p 8022 -R 0.0.0.0:3307:localhost:3306 user@domain.tld -fN -o ControlPath=/tmp/ssh-mysql-3307-local-3306

注意在建立这些ssh隧道的时候,一般不能使用[ssh的连接共享](385)。

Update @ 2009-03-16

马上就遇到了innodb启用了innodb_file_per_table之后,直接拷贝.ibd文件过来不生效的问题,并且是在建立好M-M复制之后,如此这般(M1是旧服务器,M2上把文件拷贝过来导入的服务器)(补注:依然失败):

  • M1: 确认所有同步内容已经处理完毕。
  • M1: stop slave;
  • M2: flush tables with read lock;
  • M2: 备份mysql data目录下相应库的所有文件(就是拷贝过来的那些),并且删除掉(db.opt不要删)。
  • M2: unlock tables; 不然下面的操作无法进行。
  • M2: 使用其它手段重新创建所有表,数据不需要。
  • M2: show master status; 得到位置后面要用到。
  • M2: flush tables with read lock; 准备用拷贝过来的ibd覆盖刚才用sql重新创建的表,这样数据就有了。
  • M2: 复制/拷贝/恢复刚才备份的所有.frm, .ibd文件。
  • M2: unlock tables; M2恢复正常。
  • M1: CHANGE MASTER TO MASTER_HOST=’ip_of_M2′, MASTER_PORT=port_of_M2, MASTER_USER=’rep’, MASTER_PASSWORD=’passwd’, MASTER_LOG_FILE=’fwolf-office.000101′, MASTER_LOG_POS=57943; 目的是跳过刚才那些重新建表的处理。
  • M1: start slave; M1恢复正常。

至于原因嘛,因为拷贝过来的都是ibd文件,而innodb的schema信息都存在系统表INFORMATION_SCHEMA里,偏偏这个系统表又是只读的,所以只能重新建一遍库了。

还遇到了特定名称的库无法创建的情况,会提示.frm无法创建之类的,可以这样处理:创建失败后,从其它库中建一个名称相同的,然后把.frm, .ibd文件拷贝过来,然后执行drop table XXX,然后就能正常创建、删除了。

……慢,表都能看到了,可哪个里面都没有数据,还是不行,看来innodb迁移到其它服务器,尤其是配置不同、使用状态不同的情况下,还真不是一般的麻烦,我还是老老实实改用mysqldump得了,删库重建再导入,慢也就慢这一次而已。大体过程:

  • M1: stop slave;
  • M1: dump all data: mysqldump -h ip_of_M1 –add-drop-table –default-character-set=utf8 –user=root -p –extended-insert=true –net_buffer_length=600k–skip-lock-tables db_to_replicate > db_to_replicate.sql
  • M2: delete database & recreate it
  • M2: 执行刚才的sql,导入数据,最好先关闭binlog
  • M2: stop slave;
  • M1/2上分别change master to, start slave;

如果遇到这样的错误:

ERROR 1201 (HY000): Could not initialize master info structure; more error messages can be found in the MySQL error log

可以在CHANGE MASTER TO之前先RESET SLAVE;

参考

  • [A Better MySQL Replication Heartbeat(利用SYSDATE和NOW在同步时不同的处理机制,实现主从服务器延迟的心跳检查)](http://java.dzone.com/articles/better-mysql-replication)
  • [Advanced MySQL Replication Techniques(环式多主复制)](http://www.onlamp.com/pub/a/onlamp/2006/04/20/advanced-mysql-replication.html?page=1)
  • [Setting Up Master-Master Replication On Four Nodes With MySQL 5 On Debian Etch](http://www.howtoforge.com/setting-up-master-master-replication-on-four-nodes-with-mysql-5-on-debian-etch)
  • [How to make MySQL replication reliable](http://www.xaprb.com/blog/2007/01/20/how-to-make-mysql-replication-reliable/)
  • [MySQL Multi-Master Replication](http://capttofu.livejournal.com/1752.html)
  • [[MySQL 5.1 体验]MySQL 复制](http://imysql.cn/node/314)
  • [MySQL Master Master Repliction Tutorial ](http://www.howtoforge.com/mysql_master_master_replication)
  • [InnoDB数据表空间文件平滑迁移](http://imysql.cn/2008_12_17_migrate_innodb_tablespace_smoothly)
  • [Recovering an InnoDB table from only an .ibd file.(介绍了手工修改ibd文件编号的方法,危险,我试了不成功)](http://www.chriscalender.com/?p=28)

Leave a Reply

Your email address will not be published. Required fields are marked *