对网站维护者来说,可用性和数据安全非常重要。自发生了一些安全事件后,本站准备了几台备用服务器,以便在出现网络攻击、服务器故障时切换使用。之前多台服务器间数据库一直使用全量同步,今天忽然想到可以用Mysql主主同步/Redis主从复制备份网站,配置一次即可简单方便的切换服务器。

原来的方案

之前文章 WordPress备份方案和备份脚本 里给出了本人在多台服务器间备份的脚本,其核心思想是:

1. 网站程序和图片等文件使用rsync在多台服务器间同步;

2. 定时全量备份MysqlRedis数据库。

程序文件和数据库是最重要的数据,本人备份做法是:白天每小时备份一次,同步到备用服务器;每天凌晨备份一次,同步到次备用服务器;每星期备份一次,同步到备份服务器。通过不同地域和机房的三个备份,基本上确保了数据不会丢失,遇到问题时也能快速恢复网站。

这个方案看起来不错,就是恢复网站比较麻烦,基本上要经过这几步:

1. 切换域名DNS到新的服务器IP;这个比较简单,某些DNS支持宕机自动切换;

2. 因程序文件已经同步好,在备用服务器上直接取出备份数据,恢复数据即可让网站正常运行;

3. 主服务器恢复后,再把备用服务器上的数据同步到主服务器。

可以看到,方案中存在以下问题:

1. 可能存在数据丢失;数据不是实时备份的,恢复网站时的数据不是主服务器最新版本,会有少量数据丢失。当然对于个人WordPress网站而言,这个问题不大;

2. 恢复网站数据麻烦;备用服务器需要导入一次,等主服务器正常工作了,又要从备用服务器导出,然后再导入主服务器,不仅操作麻烦,还效率低下。

上周本站托管商家硬件升级花了几天才弄好,本人又重复了 “导出->导入->备用服务器恢复网站->导出->导入->切换到主服务器” 的过程,深感繁琐。

今天忽然意识到,完全可以使用Mysql主主复制(Mysql双主同步)以及Redis主从复制的方式来同步数据,从而避免上述的麻烦操作,并且保证数据不丢失。

新的备份方案

新的方案中,程序文件还是通过rsync在服务器间同步,但是Mysql数据和Redis数据的同步则通过主主复制/主从复制完成,既保证了数据完整性,还避免了手工操作。

接下来分别介绍Mysql双主同步和Redis主从复制设置。

Mysql主主复制(Mysql双主同步)

前文 mysql主从复制配置 介绍了Mysql主从复制的详细步骤,主主同步基本上差不多,只是在从服务器上多了一遍配置而已。

本文以Mariadb 10.4版本为例介绍Mysql主主同步配置,其操作流程如下:

1.  在第一个主服务器(master)上,配置同步用的server_id、binlog等:编辑 /etc/my.cnf.d/server.cnf 文件,server 段加入如下内容:

[server]
server-id = 1
log-bin = /var/lib/mysql/mysql-bin.log
binlog-do-db = tlanyan   # 如果有多个数据库要复制,写多行
replicate-do-db = tlanyan  # 如果有多个数据库要复制,写多行
expire_logs_days = 15
relay-log = /var/lib/mysql/mysql-relay-bin.log
auto-increment-increment = 2
auto-increment-offset = 1

一些教程里用的server_id、log_bin等字段,正常来说Mariadb/Mysql都支持横杆和下划线两种风格,写法都没问题

请务必设置auto-increment-increment和auto-increment-offset两个参数,否则可能会出现”Error ‘Duplicate entry xxx for key ‘PRIMARY” on query.“的错误,导致复制中断

然后重启Mariadb服务器:systemctl restart mariadb

2. 在Mysql中创建复制账号,并赋予复制权限:

mysql # 该命令进入数据库
# 创建复制数据库账号,把replica-username和password替换成具体的用户名和密码
create user 'replica-username'@'%' identified by 'password';
# 赋予复制权限
grant replication slave on *.* to 'replica-username'@'%';
flush privileges;

3. 导出数据,并记录当前数据库状态。可以使用Mysql自带的mysqldump工具导出:

mysqldump tlanyan  | gzip > tlanyan-20201103.sql.gz

如果你想导出时保证数据一致性,有两种做法:第一是锁表:

use tlanyan;
# 设置表为只读状态
FLUSH TABLES WITH READ LOCK;
# 接着mysqldump数据
# 解锁表
UNLOCK TABLES;

第二种是导出时使用--single-transaction选项,导出数据库的当前快照:

mysqldump --single-transaction tlanyan  | gzip > tlanyan-20201103.sql.gz

对于个人WordPress网站,除文章外的其他数据不重要,因此本人操作时未使用这两种方式。

导出数据后,在Mysql中查看二进制日志的状态:show master status,其输出如下(如果用了锁表方式,建议在解锁前执行):

+------------------+----------+----------------+------------------+
| File             | Position | Binlog_Do_DB   | Binlog_Ignore_DB |
+------------------+----------+----------------+------------------+
| mysql-bin.000001 |      233 |     tlanyan    |                  |
+------------------+----------+----------------+------------------+

请记住 FilePosition 的值,配置从库时会用到。

4. 将导出的数据库文件复制到第二个主服务器并导入(需要先创建好同名数据库):

mysql tlanyan < tlanyan-20201103.sql

5. 配置第二个主服务器的server_id等信息:编辑 /etc/my.cnf.d/server.cnf 文件,server 段加入如下内容:

[server]
server-id = 2 # 必须和第一个主服务器不同
log-bin = /var/lib/mysql/mysql-bin.log
binlog-do-db = tlanyan   # 如果有多个数据库要复制,写多行
replicate-do-db = tlanyan  # 如果有多个数据库要复制,写多行
expire_logs_days = 15
relay-log = /var/lib/mysql/mysql-relay-bin.log
auto-increment-increment = 2
auto-increment-offset = 2

然后重启Mariadb服务器:systemctl restart mariadb

6. 配置第二个主服务器从第一个主服务复制数据:

# 进入mysql
# 配置同步第一个主服务器的数据,ip、username、password、mysql-bin.000001和233都改成自己的
change master to master_host='ip',
master_user='username',
master_password='password',
master_log_file='mysql-bin.000001',
master_log_pos=233;
# 启动复制进程
start slave

在Mysql中,通过show slave status\G;命令可以查看复制状态,有类似如下输出:

                Slave_IO_State: Waiting for master to send event
                   Master_Host: xxxx
                   Master_User: xxxx
                   Master_Port: 3306
                 Connect_Retry: 60
               Master_Log_File: mysql-bin.000001
           Read_Master_Log_Pos: 571947
                Relay_Log_File: mysql-relay-bin.000002
                 Relay_Log_Pos: 569756
         Relay_Master_Log_File: mysql-bin.000001
              Slave_IO_Running: Yes
             Slave_SQL_Running: Yes
               Replicate_Do_DB: tlanyan
                 // 略去一些中间行
       Slave_SQL_Running_State: Slave has read all relay log; waiting for the slave I/O thread to update it
              Slave_DDL_Groups: 2
Slave_Non_Transactional_Groups: 1356
    Slave_Transactional_Groups: 4308

上述过程配置好了主从复制,主主复制还需要做如下操作:

  1. 在第二个主服务器上创建第一个服务器同步用的账号密码,并赋予权限,请参考第2步;
  2. 在第二个主服务器上执行 show master status,记住File和Position的值,请参考第3步;
  3. 在第一个服务器上配置同步第二个服务器,请参考第6步。

经过上述操作,Mysql双主同步/Mysql主主复制便完成了。在任何一个服务器上创建新的表,插入数据,其他服务器上很快便能看到,说明我们的配置已经成功。

另外需要注意的是,如果服务器有防火墙/安全组,请记得放行。

Redis主从复制

如果你的程序还用到了Redis,那么还需要配置Redis数据的同步。可惜的是,Redis官方只支持主从复制,不支持主主同步。

Redis主从复制配置很简单,基本上只需要在从服务器上配置即可。操作为:

1. 编辑主服务器的/etc/redis.conf文件,将 bind 12.0.0.1 改成 bind 0.0.0.0 ,然后重启Redis:systemctl restart redis

2. 编辑从服务器的 /etc/redis.conf 文件,找到 replicaof 指令,改成

replicaof 主服务器ip 6379   # 6379是默认Redis端口,如果修改过请记得保持一致

Redis默认从服务器只读,不能写入数据。在本人情形中,切换到备用服务器时需要可写,因此要设置replica-read-only

replica-read-only yes

配置好后重启Redis:systemctl restart redis

因为Black lives masters的影响,slave这个词在不少软件中被替换(例如Leader-follower)。网上许多教程用的旧版Redis,配置中的slaveof即本文中的replicaof

接着通过 redis-cli info keyspace 命令便可以看到数据已经与主服务器一致。

需要注意的是,如果主服务器有防火墙/安全组,请记得放行。

注意事项

经过上述设置,数据库中的数据会自动同步到备用服务器,恢复网站时终于不用再导入/导出数据文件了。但还有一些注意事项:

  1. Mysql主主同步/Redis复制对数据做的更改即时生效,如果数据出现意外,从库的数据立马受到影响,因此建议还是要用全量备份或者增量备份+binlog的方式确保数据安全;
  2. 千万要设置好防火墙,不要将无密码的Redis端口暴露在公网上,否则会有严重的安全风险;
  3. Redis的数据默认会同步到/var/lib/redis目录的dump.rdb文件中,使用其可以恢复Redis。

参考

  1.  mysql主从复制配置
  2.  Replication -Redis
  3.  CentOS系统运行多个redis实例
  4.  几个查看Redis内存信息的命令
  5.  WordPress备份方案和备份脚本
  6.  做一个用不暴露真实IP的网站