编译安装Percona Server并使用Transfer实现主从数据库同步加速(下)

引子

MySQL的主从同步一直有从库延迟的问题,背景资料网上很多,原因简单描述如下:

  1. MySQL从库上有一个IO线程负责从主库取binlog到写到本地。另外有一个SQL线程负责执行这些本地日志,实现命令重放;

  2. 正常网络状况下IO线程没有性能问题(这个待会会用到),问题是SQL线程只有一个,导致更新速度跟不上。所以经常会看到明明从库的CPU idle很高,但同步性能就是上不去。

既然主从同步的性能问题是由于SQL单线程导致的,最直接的想法就是把它改成多线程版本。Transfer就是这么一个工具,它在MySQL源码的基础上打patch,以多线程的方式实现主从数据同步,达到提高同步性能的目的。基本结构如下:

以上内容参考追风刀·丁奇的博客。

安装与配置

目前,Transfer的最新版本是Transfer 2.3,基于版本Percona 5.5.34。安装方法非常简单,直接将下载的Transfer 2.3 替换官方版的mysqld,现在对Slave模式和Transfer模式分别进行说明。至于要如何安装Percona 5.5.34,可以参考编译安装Percona Server并使用Transfer实现主从数据库同步加速(上)

Slave模式

Transfer的Slave模式跑起来比较简单,这里假设原生主从同步已经配置好,详细配置过程可以参考Mysql主从复制和读写分离方案分析。然后,只需将Slave上的mysqld替换为Transfer,现在以本机配置为例进行说明。
启动Mysql后,通过ps命令查看mysqld所在目录:

[root@chenllcentos ~]# ps -ef | grep mysqld
root     14156     1  0 Nov06 ?        00:00:00 /bin/sh /usr/local/mysql/bin/mysqld_safe --datadir=/usr/local/mysql/data --pid-file=/usr/local/mysql/data/chenllcentos.localdomain.pid
mysql    14433 14156  0 Nov06 ?        00:04:49 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data --plugin-dir=/usr/local/mysql/lib/mysql/plugin --user=mysql --log-error=/usr/local/mysql/data/chenllcentos.localdomain.err --pid-file=/usr/local/mysql/data/chenllcentos.localdomain.pid --socket=/tmp/mysql.sock --port=3306
root     22894 22772  0 11:27 pts/0    00:00:00 grep mysqld

可以知道,当前mysqld所在目录为/usr/local/mysql/bin
备份mysqld为mysqld_bak:

[root@chenllcentos ~]# mv /usr/local/mysql/bin/mysqld /usr/local/mysql/bin/mysqld_bak

复制Transfer到/usr/local/mysql/bin目录下:

[root@chenllcentos ~]# cp ~/dev/transfer.2.3-based-PS-5.5.34 /usr/local/mysql/bin

重命名transfer.2.3-based-PS-5.5.34mysqld:

[root@chenllcentos ~]# mv /usr/local/mysql/bin/transfer.2.3-based-PS-5.5.34 /usr/local/mysql/bin/mysqld

添加mysqld可执行权限:

[root@chenllcentos ~]# chmod 755 /usr/local/mysql/bin/mysqld

重启mysql服务:

[root@chenllcentos ~]# service mysqld restart

到这里,我们就将原生的Slave数据库替换成Transfer 2.3 Slave啦。登录Mysql数据库,使用mysql> show slave status\G看看主从同步是否成功。

mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 1XX.XX.XX.181
                  Master_User: slave
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000019
          Read_Master_Log_Pos: 192479
               Relay_Log_File: chenllcentos-relay-bin.000030
                Relay_Log_Pos: 252
        Relay_Master_Log_File: mysql-bin.000019
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 192479
              Relay_Log_Space: 415
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 1

可以看出,此时Slave_IO_RunningSlave_SQL_Running都显示为Yes,因此Transfer Slave模式配置成功。

Transfer模式

使用Transfer模式相比Slave模式复杂一些,因为需要多配置并维护一个MySQL实例。此时的Transfer其实就是一个运行的MySQL实例,配置成Master的从库,然后Slave再配置成Transfer的从库,当Transfer收到Master的Binlog后转发给后端多个Slave数据库,实现多线程数据同步。

若没有多主的需求,测试数据库主从关系为Master->Transfer->Slave。

Transfer既可以部署在单独机器上,也可以部署在Slave机器上。其实两种部署方式在性能上并没有区别,只是若部署在同一机器上,需要配置MySQL多实例运行。作者推荐的线上配置是同Slave部署在同一机器。接下来,以同一机器部署为例进行说明。

原型环境

  • 服务器A

IP:1XX.XX.XX.181
部署Master数据库

  • 服务器B

IP:1XX.XX.XX.182
部署Transfer和Slave数据库

替换mysqld文件

将官方版的mysqld替换成下载的Transfer 2.3,这一步骤和Slave模式相同。

修改配置文件

由于需要单机启动MySQL多实例,这里采用mysqld_multi对MySQL进行管理。
复制/etc/my.cnf/etc/multi.cnf,用于管理MySQL多实例:

[root@chenllcentos ~]# cp /etc/my.cnf /etc/multi.cnf

打开/etc/multi.cnf

[root@chenllcentos ~]# vi /etc/multi.cnf

在文件末尾添加如下内容:

[mysqld_multi]
mysqld     = /usr/local/mysql5/bin/mysqld_safe
mysqladmin = /usr/local/mysql5/bin/mysqladmin
user       = root
password   = root

[mysqld2]
socket     = /tmp/mysql.sock2
port       = 3306
pid-file   = /data/mysql5/data2/hostname.pid2
datadir    = /data/mysql5/data2
user       = mysql

server-id=18255
binlog-do-db=test
log-bin=mysql-bin

transfer_mode = on

remote_slave_hostname = 1XX.XX.XX.182
remote_slave_username = root
remote_slave_password = root
remote_slave_port = 13306

其中server-idbinlog-do-dblog-bin是传统主从配置,transfer_moderemote_slave_*是Transfer模式配置。

Transfer参数说明:
transfer_parallel_on
1.on—多线程复制
2.off—单线程 默认值on
transfer_mode
1.on—多线程复制
2.off—单线程 默认值on
remoteslave*
具有访问Slave的super权限帐号

使用mysqld_multi启动Transfer,并指定配置文件为:/etc/multi.cnf

[root@chenllcentos ~]# mysqld_multi --defaults-file=/etc/multi.cnf start 2

登录Transfer:

[root@chenllcentos ~]# mysql -uroot -proot -S /tmp/mysql.sock2 

这里需要指定MySQL连接为:/tmp/mysql.sock2

查看刚才配置的Transfer参数:

mysql> show variables like "transfer%";
+-------------------------+---------------+
| Variable_name           | Value         |
+-------------------------+---------------+
| transfer_mode           | ON            |
| transfer_parallel_on    | ON            |
| transfer_slave_host     | 1XX.XX.XX.182 |
| transfer_slave_password | ****          |
| transfer_slave_port     | 13306         |
| transfer_slave_thread   | 16            |
| transfer_slave_username | root          |
| transfer_verbos         | OFF           |
+-------------------------+---------------+
8 rows in set (0.00 sec)

配置Slave

之前已经配置好了Transfer,现在需要配置Slave真实数据库。在Master->Transfer->Slave模式下,只需要保证Master和Slave的MySQL版本一致即可。因此,本例子中Slave使用的是MySQL5.1版本,采用yum安装方式部署。修改配置信息如Transfer指定的transfer_slave_*参数信息。/etc/my.cnf配置文件内容如下:

[mysqld]
port = 13306

server-id = 18251

datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
user=mysql
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0

[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

配置Slave账户信息:

[root@chenllcentos ~]# mysqladmin -u root password 'root'

启动Slave:

[root@chenllcentos ~]# service mysqld start

若启动失败,请关闭SELinux:

[root@chenllcentos ~]# setenforce 0
[root@chenllcentos ~]# getenforce
Permissive

同步表结构

在Transfer模式下,必须保证Master->Transfer->Slave需要同步的表结构相同。其中Transfer只需要同步表结构即可,不需要数据,这是因为Transfer在主从同步过程中只负责转发Binlog,并不保存任何数据。由于例子中设置主从同步test数据库,因此需要同步test表结构。
dump主库test表结构:

[root@chenllcentos ~]# mysqldump -uroot -proot --no-data --add-drop-table --add-drop-database test > /data/db.sql

分别登录Transfer和Slave数据库,执行db.sql脚本文件:

mysql> use test
mysql> source /data/db.sql

Transfer模式主从配置

在Transfer模式下,若无多主的需求,Transfer设置为Master的从库,然后Slave设置成Transfer的从库,具体的配置流程和传统主从配置相。至此,Transfer模式配置完成,现在让我们一起验证。

Transfer模式验证

首先,登录服务器B Transfer数据库:

[root@chenllcentos ~]# mysql -uroot -proot -S /tmp/mysql.sock2

查询Slave状态:

mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 1XX.XX.XX.181
                  Master_User: transfer
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000021
          Read_Master_Log_Pos: 3148
               Relay_Log_File: hostname-relay-bin.000070
                Relay_Log_Pos: 440
        Relay_Master_Log_File: mysql-bin.000021
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 3148
              Relay_Log_Space: 744
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 1
1 row in set (0.00 sec)

可以知道,Transfer的主库设置为服务器A的Master数据库。
再登录服务器B Slave数据库:

[root@chenllcentos ~]# mysql -uroot -proot 

查看Slave状态:

mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 1XX.XX.XX.182
                  Master_User: slave2
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000022
          Read_Master_Log_Pos: 503
               Relay_Log_File: mysqld-relay-bin.000036
                Relay_Log_Pos: 252
        Relay_Master_Log_File: mysql-bin.000022
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 503
              Relay_Log_Space: 554
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
1 row in set (0.00 sec)

这里,Slave的主库设置成同台主机下的另一个MySQL实例,即Transfer。
现在,我们在Master插入一条数据,看看主从是否生效:

mysql> insert into test.t_user (user_id, user_name, user_address) values (3, 'Mike', 'Fuzhou');
Query OK, 1 row affected (0.01 sec)

查询Transfer test数据库t_user表:

mysql> select * from test.t_user;
Empty set (0.00 sec)

这里,为什么是空的呢?大家还记得吗,前面提到过,在Transfer模式下,Transfer只负责转发Binlog,并不会保存任何数据。所以,在Transfer查询t_user表数据当然为空啦!
接下来,让我们看看Slave数据:

mysql> select * from test.t_user;
+---------+-----------+--------------+
| user_id | user_name | user_address |
+---------+-----------+--------------+
|       3 | Mike      | Fuzhou       |
+---------+-----------+--------------+
1 row in set (0.00 sec)

果然不出所料,刚才在Master插入的数据,现在已经同步到Slave上了。说明我们配置的Transfer模式已经成功运行。

Transfer模式转换

Transfer模式已经部署运行,但是现在需求变化了,并不需要另外维护一个MySQL实例,而是想切换成Slave模式。Transfer提供了非常简单的实现,只需要更改配置文件参数即可,然后重启Transfer。若此时在Master t_user表插入记录,Transfer将直接进行复制,在这种情况下和传统的MySQL主从没有任何区别。让我们做个例子验证下。
修改Transfer的transfer_mode参数:

[root@chenllcentos ~]# vi /etc/multi.cnf

查找transfer_mode将值设置成off
重启Transfer:

[root@chenllcentos ~]# mysqld_multi --defaults-file=/etc/multi.cnf stop 2
[root@chenllcentos ~]# mysqld_multi --defaults-file=/etc/multi.cnf start 2

登录Transfer:

[root@chenllcentos ~]# mysql -uroot -proot -S /tmp/mysql.sock2

查看Transfer参数:

    mysql> show variables like "transfer%";
+-------------------------+---------------+
| Variable_name           | Value         |
+-------------------------+---------------+
| transfer_mode           | OFF           |
| transfer_parallel_on    | ON            |
| transfer_slave_host     | 1XX.XX.XX.182 |
| transfer_slave_password | ****          |
| transfer_slave_port     | 13306         |
| transfer_slave_thread   | 16            |
| transfer_slave_username | root          |
| transfer_verbos         | OFF           |
+-------------------------+---------------+
8 rows in set (0.00 sec)

登录Master数据库:

[root@chenllcentos ~]# mysql -uroot -proot

往t_user表再插入一条数据:

mysql> insert into test.t_user (user_id, user_name, user_address) values (1, 'Lucy', 'Xiamen');
Query OK, 1 row affected (0.01 sec)

在Transfer中查询t_user表数据:

mysql> select * from test.t_user;
+---------+-----------+--------------+
| user_id | user_name | user_address |
+---------+-----------+--------------+
|       1 | Lucy      | Xiamen       |
+---------+-----------+--------------+
1 row in set (0.00 sec)

可以发现,此时Transfer会同步Master刚插入的数据。那么,之前配置的Slave数据库呢?让我们看看。
在Slave中查询t_user表数据:

mysql> select * from test.t_user;
+---------+-----------+--------------+
| user_id | user_name | user_address |
+---------+-----------+--------------+
|       3 | Mike      | Fuzhou       |
+---------+-----------+--------------+
1 row in set (0.00 sec)

果然,Slave数据库并没有同步刚才在Master插入的Lucy记录。说明Transfer模式切换Slave模式成功。

小结

其实,在做Transfer模式的时候真正纠结了一把。多实例场景下,Transfer数据库的端口必须是3306。若更改Transfer端口为其他数值(例如3307),Slave始终无法连接上Transfer,报2013错误:

[ERROR] Slave I/O: error connecting to master 'slave2@1XX.XX.XX.182:3307' - retry-time: 60  retries: 86400, Error_code: 2013

目前还未找到解决办法。无奈之下,只能修改Slave端口达到单主机配置Slave和Transfer的目的。