0x01 前言

我这个博客的服务器架构有点奇怪,后端有两个处于不同地域的PHP服务器,一个在我家里、另一个在腾讯云,而数据库只有一个,在腾讯云上。

因为我家里的服务器资源比较充足,可以承载更多的负载,但有时候会出现停电的情况。为此,我特意在腾讯云上部署第二个PHP服务器,以实现高可用。

但这里会有个问题,数据库在腾讯云,如果我家里的服务端需要查询数据库,则需要较长的时间才能得到结果,这样会导致整体的响应时间被拉长。

访客在我的站点上几乎没有需要写数据库的操作,所以我决定在家里部署一个从数据库,实现读写分离,将所有读库的操作都强制连接至从数据库。

其实我也想过配置主主模式,但考虑到家里有停电的可能,我还是采用主从模式。

0x02 准备

为实现主从模式,至少需要2个mariadb服务器:

在这里,我将mariadb-t1设为master;mariadb-t2设为slave。

为了这次测试,我将我博客的数据库dump一份出来用于测试:

[root@mariadb-node1 ~]# mysqldump -u root -p ngx.hk > ngx.hk.sql

大小约为29M:

[root@mariadb-node1 ~]# ll -h
total 29M
-rw-r--r-- 1 root root 29M Jun  6 22:42 ngx.hk.sql

然后根据以下文章进行mariadb的安装与预配置:

以下是我用于测试的两台mariadb服务器:

用于主从复制的mariadb版本不一致一般不会有什么问题,但为了可靠性与稳定性,建议不要跨大版本。

完成服务器的搭建后,我需要在主服务器里新建一个数据库并将上面导出的数据库导入到这个新的数据库中:

#新建数据库
MariaDB [(none)]> create database ngx_hk;
Query OK, 1 row affected (0.00 sec)

#列出所有数据库
MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| ngx_hk             |
| performance_schema |
+--------------------+
4 rows in set (0.00 sec)

#导入数据库
[root@mariadb-t1 ~]# mysql -u root -p ngx_hk < ngx.hk.sql

#进入ngx_hk数据库
MariaDB [(none)]> use ngx_hk;
Database changed

#列出库中的所有表
MariaDB [ngx_hk]> show tables;
+-------------------------------+
| Tables_in_ngx_hk              |
+-------------------------------+
| hk_commentmeta                |
| hk_comments                   |
| hk_layerslider                |
| hk_layerslider_revisions      |
| hk_links                      |
| hk_ngg_album                  |
| hk_ngg_gallery                |
| hk_ngg_pictures               |
| hk_options                    |
| hk_postmeta                   |
| hk_posts                      |
| hk_revslider_css              |
| hk_revslider_layer_animations |
| hk_revslider_navigations      |
| hk_revslider_sliders          |
| hk_revslider_slides           |
| hk_revslider_static_slides    |
| hk_term_relationships         |
| hk_term_taxonomy              |
| hk_termmeta                   |
| hk_terms                      |
| hk_usermeta                   |
| hk_users                      |
| hk_wfBadLeechers              |
| hk_wfBlockedCommentLog        |
| hk_wfBlockedIPLog             |
| hk_wfBlocks                   |
| hk_wfBlocks7                  |
| hk_wfBlocksAdv                |
| hk_wfConfig                   |
| hk_wfCrawlers                 |
| hk_wfFileChanges              |
| hk_wfFileMods                 |
| hk_wfHits                     |
| hk_wfHoover                   |
| hk_wfIssues                   |
| hk_wfKnownFileList            |
| hk_wfLeechers                 |
| hk_wfLiveTrafficHuman         |
| hk_wfLockedOut                |
| hk_wfLocs                     |
| hk_wfLogins                   |
| hk_wfNet404s                  |
| hk_wfNotifications            |
| hk_wfPendingIssues            |
| hk_wfReverseCache             |
| hk_wfSNIPCache                |
| hk_wfScanners                 |
| hk_wfStatus                   |
| hk_wfThrottleLog              |
| hk_wfVulnScanners             |
+-------------------------------+
51 rows in set (0.00 sec)

至此,准备工作已全部完成,接下来开始配置主服务器。

0x03 主服务器

我们需要先准备以下配置信息:

#设定监听地址
bind-address=0.0.0.0

#启用二进制日志
log-bin

#设定服务器ID
server_id=1

#设定二进制日志的前缀
log-basename=master1

以上配置中的第二和第三项的值必须唯一,监听地址可以设为特定的IP,但无论如何,都需要通过防火墙进行控制,谨防未经授权的连接。

在实际环境中,一个mariadb服务器中可能有多个数据库,那么可以选择仅复制某个库或者不复制某个库:

#仅复制某个库
replicate-do-db=ngx_hk

#忽略某个库
binlog-ignore-db=mysql

如果需要添加多个库,则在新的一行添加即可。

完成后即可将配置信息写入文件,在这里我遵循mariadb文档的要求,将其写入到以下文件:

#打开文件
[root@mariadb-t1 ~]# vim /etc/my.cnf.d/server.cnf

#在[mariadb]块中写入内容
bind-address=0.0.0.0
log-bin
server_id=1
log-basename=master1
replicate-do-db=ngx_hk

最终的结果如下:

完成后重启mariadb主服务器的服务:

[root@mariadb-t1 ~]# systemctl restart mariadb.service

然后新建一个名为slave的用户,并为其分配REPLICATION SLAVE权限:

#新建用户
MariaDB [(none)]> CREATE USER 'slave'@'%' IDENTIFIED BY 'slave_password';
Query OK, 0 rows affected (0.00 sec)

#分配权限
MariaDB [(none)]> GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%';
Query OK, 0 rows affected (0.00 sec)

#刷新权限表
MariaDB [(none)]> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)

然后启用读锁:

#启用读锁
MariaDB [(none)]> FLUSH TABLES WITH READ LOCK;
Query OK, 0 rows affected (0.00 sec)

#列出主服务器的状态
MariaDB [(none)]> SHOW MASTER STATUS;
+--------------------+----------+--------------+------------------+
| File               | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+--------------------+----------+--------------+------------------+
| master1-bin.000001 |      737 |              |                  |
+--------------------+----------+--------------+------------------+
1 row in set (0.00 sec)

一定要启用读锁后再检查主服务器的状态,然后把File名称和Position的值记录下来:

File: master1-bin.000001
Position: 737

最后把数据库dump一份出来,但这里需要注意!!不要关闭当前的mysql会话,请重新打开一个shell会话,在这新的shell会话中执行mysqldump命令。因为一旦关闭mysql会话,读锁会自动解除。

为什么要启用读锁?

如果业务在运行,当有新数据写入库的时候,二进制日志就会更新,那么这个Position会随之增长,这时候去dump这个库的时候,与记录的Position会不一致。当配置从服务器时会出现异常,从而导致同步失败。

了解基本的知识后,就可以打开一个新的shell会话并执行以下命令以导出数据库:

[root@mariadb-t1 ~]# mysqldump -u root -p ngx_hk > ngx_hk.sql

然后回到mysql会话的shell窗口,执行以下命令解除读锁并退出mysql会话:

#解除读锁
MariaDB [(none)]> UNLOCK TABLES;
Query OK, 0 rows affected (0.00 sec)

#退出会话
MariaDB [(none)]> exit;
Bye

最后把上面dump出来的数据库文件传输到从服务器上:

[root@mariadb-t1 ~]# scp ngx_hk.sql [email protected]:/root/
[email protected]'s password: 
ngx_hk.sql         100%   29MB  58.0MB/s   00:00

至此,主服务器的部分已全部结束。

0x04 从服务器

首先开启一个mysql会话并新建数据库、新建用户并然后将dump出来的数据库导入到该数据库中:

#建立会话
[root@mariadb-t2 ~]# mysql -u root -p

#建立数据库
MariaDB [(none)]> create database ngx_hk;
Query OK, 1 row affected (0.00 sec)

#退出会话
MariaDB [(none)]> exit;
Bye

#导入数据库
[root@mariadb-t2 ~]# mysql -u root -p ngx_hk < ngx_hk.sql

同样地,从服务器也需要修改配置文件,但是配置内容比主服务器简单得多:

bind-address=0.0.0.0
server_id=2
replicate-do-db=ngx_hk

打开文件并将其写入到文件:

#打开文件
[root@mariadb-t2 ~]# vim /etc/my.cnf.d/server.cnf

#将配置信息写入到[mariadb]块中
[mariadb]
bind-address=0.0.0.0
server_id=2
replicate-do-db=ngx_hk

然后重启mariadb从服务器的服务:

[root@mariadb-t2 ~]# systemctl restart mariadb

在继续往下之前,我们先准备一下mysql语句:

CHANGE MASTER TO
  MASTER_HOST='10.1.1.103',
  MASTER_USER='slave',
  MASTER_PASSWORD='slave_password',
  MASTER_PORT=3306,
  MASTER_LOG_FILE='master1-bin.000001',
  MASTER_LOG_POS=737,
  MASTER_CONNECT_RETRY=10;

从字面上的意思不难看出,主要是告诉从服务器与主服务器连接的一些关键参数。主要是主服务器的IP地址、用户名、密码、端口、二进制文件名、日志记录节点与重连次数等。

准备好这些信息后,建立一个mysql会话并执行以上mysql语句:

#建立会话
[root@mariadb-t2 ~]# mysql -u root -p

#执行语句
MariaDB [(none)]> CHANGE MASTER TO
    ->   MASTER_HOST='10.1.1.103',
    ->   MASTER_USER='slave',
    ->   MASTER_PASSWORD='slave_password',
    ->   MASTER_PORT=3306,
    ->   MASTER_LOG_FILE='master1-bin.000001',
    ->   MASTER_LOG_POS=737,
    ->   MASTER_CONNECT_RETRY=10;
Query OK, 0 rows affected (0.00 sec)

然后通过以下命令启动复制进程:

MariaDB [(none)]> START SLAVE;
Query OK, 0 rows affected (0.00 sec)

通过以下命令检查复制情况:

MariaDB [(none)]> START SLAVE;
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> SHOW SLAVE STATUS\G;
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 10.1.1.103
                  Master_User: slave
                  Master_Port: 3306
                Connect_Retry: 10
              Master_Log_File: master1-bin.000001
          Read_Master_Log_Pos: 737
               Relay_Log_File: mariadb-t2-relay-bin.000002
                Relay_Log_Pos: 539
        Relay_Master_Log_File: master1-bin.000001
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: ngx_hk
          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: 737
              Relay_Log_Space: 842
              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
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
                   Using_Gtid: No
                  Gtid_IO_Pos: 
      Replicate_Do_Domain_Ids: 
  Replicate_Ignore_Domain_Ids: 
                Parallel_Mode: conservative
1 row in set (0.00 sec)

ERROR: No query specified

检查的时候要注意以下两项:

Slave_IO_Running: Yes
Slave_SQL_Running: Yes

需要确认服务是否处于运行的状态,如果不是,则需要检查日志查找原因。

还需要确认Read_Master_Log_Pos与Exec_Master_Log_Pos的值是否一致,如果稍等片刻之后还是不一致,则主从复制可能出现了问题,也需要检查日志查找原因。

如果需要停止slave复制,只需要在mysql会话下执行以下语句即可:

MariaDB [(none)]> STOP SLAVE;

0x05 结语

主从服务器意外断开后,从服务器会尝试重连,如果连上,会再次复制同步,如果重连次数超过设定的值,则不会再次尝试,此时需要手动启动slave进程。

最后,附上我博客主从复制的情况图: