0x01 前言

我博客建立之初的数据库只有一个,后来因为多次搬迁导致运行环境不断重建,虽然有定时备份,但还是担心数据库丢失。因此,我重新设计博客的架构,将前端、后端与数据库都分开并且建立多个副本。

我博客运行环境的2.0版本是master-slave架构,master放置在腾讯云广州区的VPS中,而slave则部署在我家里的服务器里。同时在这两个不同的服务器上分别运行独立的PHP环境,然后在香港的VPS上配置nginx反向代理,所有数据均通过SSH隧道加密。

为了降低腾讯云的费用支出,我家中服务器的优先级在香港VPS上的nginx upstream配置高于腾讯云,所以大部分请求都会转发到我家里。

但这有个问题,因为我家里的数据库是slave身份,数据更新不会同步到master上,所以我将Wordpress设置成读写分离,需要写库的请求则发送到腾讯云上。毕竟腾讯云的机房在广州,延迟一般在8ms,数据来回加上处理的时间大大增加了我博客的响应时间。

最终,我决定采用mariadb galera实现数据库同步复制的功能。

0x02 准备

mariadb galera的配置内容比较多,在这里我只根据我的实际情况简略配置,如果需要用在生产环境请无比详细了解相关信息。

mariadb galera至少需要2个mariadb服务器,2个以上会更好;该服务对网络也有一定的要求,下面我会针对类似我这种DDNS的情况作出说明,但建议在拥有固定IP或内网可达的情况下部署。

这对这次测试,我建立了2个虚拟机,分别命名为db-t1与db-t2:

我内部会自动将hostname注册到DNS服务器中,所以在下面的配置我都使用hostname代替IP,这代表该配置支持域名与IP地址。

0x03 安装

首先在两个节点上安装mariadb:

[root@db-t1 ~]# yum install MariaDB-server MariaDB-client -y

然后检查mariadb的状态:

[root@db-t1 ~]# systemctl status mariadb.service 
● mariadb.service - MariaDB 10.2.16 database server
   Loaded: loaded (/usr/lib/systemd/system/mariadb.service; enabled; vendor preset: disabled)
  Drop-In: /etc/systemd/system/mariadb.service.d
           └─migrated-from-my.cnf-settings.conf
   Active: inactive (dead)
     Docs: man:mysqld(8)
           https://mariadb.com/kb/en/library/systemd/

默认情况下,mariadb服务会设为自动启动,但安装完成后并不会启动,所以需要手动启动并对两个节点进行初始化设定:

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

再次检查运行状态:

[root@db-t1 ~]# systemctl status mariadb.service 
● mariadb.service - MariaDB 10.2.16 database server
   Loaded: loaded (/usr/lib/systemd/system/mariadb.service; enabled; vendor preset: disabled)
  Drop-In: /etc/systemd/system/mariadb.service.d
           └─migrated-from-my.cnf-settings.conf
   Active: active (running) since 日 2018-08-12 17:18:55 CST; 56s ago
     Docs: man:mysqld(8)
           https://mariadb.com/kb/en/library/systemd/
  Process: 1909 ExecStartPost=/bin/sh -c systemctl unset-environment _WSREP_START_POSITION (code=exited, status=0/SUCCESS)
  Process: 1860 ExecStartPre=/bin/sh -c [ ! -e /usr/bin/galera_recovery ] && VAR= ||   VAR=`/usr/bin/galera_recovery`; [ $? -eq 0 ]   && systemctl set-environment _WSREP_START_POSITION=$VAR || exit 1 (code=exited, status=0/SUCCESS)
  Process: 1858 ExecStartPre=/bin/sh -c systemctl unset-environment _WSREP_START_POSITION (code=exited, status=0/SUCCESS)
 Main PID: 1873 (mysqld)
   Status: "Taking your SQL requests now..."
   CGroup: /system.slice/mariadb.service
           └─1873 /usr/sbin/mysqld

确认无误后执行以下命令:

# 执行初始化安装交互
[root@db-t1 ~]# mysql_secure_installation 

# 要求输入root密码,首次安装密码为空,回车即可
Enter current password for root (enter for none): 
OK, successfully used password, moving on...

# 设置root密码
Set root password? [Y/n] 
New password: 
Re-enter new password: 

# 移除匿名用户
Remove anonymous users? [Y/n] 

# 允许root用户远程登入,请根据实际情况选择
Disallow root login remotely? [Y/n] n

# 移除测试数据库
Remove test database and access to it? [Y/n] 

# 刷新权限表
Reload privilege tables now? [Y/n]

完成后尝试使用mysql命令登入数据库:

[root@db-t1 ~]# mysql -u root -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 16
Server version: 10.2.16-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> exit;
Bye

确认正常后继续下一步配置。

0x04 master db-t1

第一个启动的节点所使用的命令和后续节点的启动命令有所不同,因为我这是全新安装配置,所以将第一个启动的节点称为master。

当使用中的集群出现脑裂现象或其他异常需要重启时,有一套不同的流程,这个在文章后面简单说明。

在db-t1上建立一个用于同步的账户,通过mysql登入数据库,然后执行以下命令:

# 登入数据库
[root@db-t1 ~]# mysql -u root -p
Enter password: 

# 建立名为cluster_user,密码为cluster_passwd的用户,指定hostname为localhost
MariaDB [(none)]> GRANT ALL PRIVILEGES ON *.* TO cluster_user@localhost IDENTIFIED BY 'cluster_passwd';
Query OK, 0 rows affected (0.00 sec)

# 建立名为cluster_user,密码为cluster_passwd的用户,指定hostname为%
MariaDB [(none)]> GRANT ALL PRIVILEGES ON *.* TO 'cluster_user'@'%' IDENTIFIED BY 'cluster_passwd';
Query OK, 0 rows affected (0.01 sec)

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

# 登出数据库
MariaDB [(none)]> exit;
Bye

然后确认二进制文件libgalera_smm.so的绝对路径:

[root@db-t2 ~]# find / -name libgalera_smm.so
/usr/lib64/galera/libgalera_smm.so

最后准备配置信息:

[galera]
wsrep_on=ON
wsrep_provider=/usr/lib64/galera/libgalera_smm.so
wsrep_cluster_name='wsrep_cluster'
wsrep_cluster_address='gcomm://'
wsrep_node_address='db-t1.t.com:4567'
wsrep_sst_receive_address='db-t1.t.com:3306'
binlog_format=row
wsrep_node_name='db-t1'
wsrep_sst_auth='cluster_user:cluster_passwd'
wsrep_sst_method=mysqldump
bind-address=0.0.0.0

配置信息中一些字段的含义如下:

  • wsrep_cluster_name:集群名称,各个节点需一致;
  • wsrep_cluster_address:集群节点IP或域名,可附带端口,默认端口为4567,如
    • gcomm://10.1.1.97
    • gcomm://10.1.1.97:4567
    • gcomm://db-t1.t.com
    • gcomm://db-t1.t.com:4567
  • wsrep_node_address:定义本节点的域名或IP,可附带端口,默认为4567;
  • wsrep_sst_receive_address:定义本节点接受传入请求的域名或IP,可附带端口,默认的端口根据wsrep_sst_method定义的快照传输方式的不同而不同;
  • wsrep_node_name:本节点名称,在集群中需唯一;
  • wsrep_sst_auth:快照传输方式的验证信息;
  • wsrep_sst_method:定义快照传输方式,一共有以下方式:
    • rsync:默认端口:4444
    • mysqldump:3306
    • xtrabackup
    • xtrabackup-v2
    • mariabackup
  • bind-address:数据库监听的IP地址

默认情况下,wsrep会监听TCP 4567端口,当有新节点加入时,新节点会通过wsrep_cluster_address中设定的节点信息,拉取该节点的信息。

当没有设定wsrep_node_address或wsrep_sst_receive_address,galera将监听数据库bind-address中设定的IP地址,如果没有设定bind-address,则监听默认的IP地址。

如果mariadb服务器有多个IP地址,则可以通过上面两个变量设定监听特定的IP地址或端口。例如在腾讯云环境中,系统中的IP地址为10开头的内网IP,公网请求则通过NAT的方式传入,此时就需要设定上面的两个变量,指定域名或公网IP,否则galera集群将无法正常工作。

如果内网IP可达,则无需设定上面两个变量。

wsrep_sst_method可以设定不同的数据库同步方式,默认是使用rsync,请根据实际情况设定并在防火墙中放行特定的端口。

完成配置信息的设定后,将其写入以下文件:

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

# 将配置信息填写到galera块中
[galera]
wsrep_on=ON
wsrep_provider=/usr/lib64/galera/libgalera_smm.so
wsrep_cluster_name='wsrep_cluster'
wsrep_cluster_address='gcomm://'
wsrep_node_address='db-t1.t.com:4567'
wsrep_sst_receive_address='db-t1.t.com:3306'
binlog_format=row
wsrep_node_name='db-t1'
wsrep_sst_auth='cluster_user:cluster_passwd'
wsrep_sst_method=mysqldump
bind-address=0.0.0.0

而后停止正在运行的mariadb服务:

# 停止mariadb服务
[root@db-t1 ~]# systemctl stop mariadb.service 

# 检查服务状态
[root@db-t1 ~]# systemctl status mariadb.service 
● mariadb.service - MariaDB 10.2.16 database server
   Loaded: loaded (/usr/lib/systemd/system/mariadb.service; enabled; vendor preset: disabled)
  Drop-In: /etc/systemd/system/mariadb.service.d
           └─migrated-from-my.cnf-settings.conf
   Active: inactive (dead) since 日 2018-08-12 19:17:13 CST; 42s ago
     Docs: man:mysqld(8)
           https://mariadb.com/kb/en/library/systemd/
  Process: 1909 ExecStartPost=/bin/sh -c systemctl unset-environment _WSREP_START_POSITION (code=exited, status=0/SUCCESS)
  Process: 1873 ExecStart=/usr/sbin/mysqld $MYSQLD_OPTS $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION (code=exited, status=0/SUCCESS)
  Process: 1860 ExecStartPre=/bin/sh -c [ ! -e /usr/bin/galera_recovery ] && VAR= ||   VAR=`/usr/bin/galera_recovery`; [ $? -eq 0 ]   && systemctl set-environment _WSREP_START_POSITION=$VAR || exit 1 (code=exited, status=0/SUCCESS)
  Process: 1858 ExecStartPre=/bin/sh -c systemctl unset-environment _WSREP_START_POSITION (code=exited, status=0/SUCCESS)
 Main PID: 1873 (code=exited, status=0/SUCCESS)
   Status: "MariaDB server is down"

最后通过以下命令启动集群的第一个节点:

[root@db-t1 ~]# service mysql start --wsrep-new-cluster
Starting mysql (via systemctl):                            [  OK  ]

注意!只是启动集群中的第一个节点需要只用以上命令启动,并且wsrep_cluster_address的值需要设定为’gcomm://’。

成功启动后使用以下命令检查服务是否正常:

[root@db-t1 ~]# mysql -e "SHOW STATUS LIKE 'wsrep_%'; " -p

在返还的表格中需要注意两个内容:

  • wsrep_cluster_size:集群节点数量
  • wsrep_ready:服务状态

如果wsrep_ready的值为OFF,请检查数据库日志,排查原因;如果为ON,则服务正常。

然后检查wsrep_incoming_addresses的值,可以发现第一个节点的信息为:db-t1.t.com:3306,也就是wsrep_sst_receive_address设定的值。

0x05 master db-t2

在完成初始化设定的db-t2上修改server.conf配置文件,填入以下内容:

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

# 填入内容
[galera]
wsrep_on=ON
wsrep_provider=/usr/lib64/galera/libgalera_smm.so
wsrep_cluster_name='wsrep_cluster'
wsrep_cluster_address='gcomm://db-t1.t.com:4567'
wsrep_node_address='db-t2.t.com:4567'
wsrep_sst_receive_address='db-t2.t.com:3306'
binlog_format=row
wsrep_node_name='db-t2'
wsrep_sst_auth='cluster_user:cluster_passwd'
wsrep_sst_method=mysqldump
bind-address=0.0.0.0

因为这是集群中的第二个节点,只需要通过以下命令启动即可:

# 重启数据库服务
[root@db-t2 ~]# systemctl restart mariadb.service 

# 检查状态
[root@db-t2 ~]# systemctl status mariadb.service 
● mariadb.service - MariaDB 10.2.16 database server
   Loaded: loaded (/usr/lib/systemd/system/mariadb.service; enabled; vendor preset: disabled)
  Drop-In: /etc/systemd/system/mariadb.service.d
           └─migrated-from-my.cnf-settings.conf
   Active: active (running) since 日 2018-08-12 19:42:14 CST; 9s ago
     Docs: man:mysqld(8)
           https://mariadb.com/kb/en/library/systemd/
  Process: 1895 ExecStartPost=/bin/sh -c systemctl unset-environment _WSREP_START_POSITION (code=exited, status=0/SUCCESS)
  Process: 1778 ExecStartPre=/bin/sh -c [ ! -e /usr/bin/galera_recovery ] && VAR= ||   VAR=`/usr/bin/galera_recovery`; [ $? -eq 0 ]   && systemctl set-environment _WSREP_START_POSITION=$VAR || exit 1 (code=exited, status=0/SUCCESS)
  Process: 1776 ExecStartPre=/bin/sh -c systemctl unset-environment _WSREP_START_POSITION (code=exited, status=0/SUCCESS)
 Main PID: 1858 (mysqld)
   Status: "Taking your SQL requests now..."
   CGroup: /system.slice/mariadb.service
           └─1858 /usr/sbin/mysqld --wsrep_start_position=00000000-0000-0000-0000-000000000000:-1

重启后,db-t2节点会通过db-t1.t.com的4567端口索取集群各个节点的信息,然后根据wsrep_sst_method中设定的同步方式访问db-t1.t.com:3306端口,同步完成后才算启动完成。

如果集群中的数据库较大,请耐心等待,完成后通过以下命令即可检查相关信息:

[root@db-t2 ~]# mysql -e "SHOW STATUS LIKE 'wsrep_%'; " -p

返还的表格中需要注意以下内容:

  • wsrep_cluster_size:集群节点数量;
  • wsrep_incoming_addresses:集群各个节点的SST地址与端口;
  • wsrep_local_state_comment:本节点的同步状态;
  • wsrep_ready:服务状态。

0x06 收尾

在启动第一个节点的时候,wsrep_cluster_address的值为:’gcomm://’,因此需要作出修改。

在修改之前需要确认集群至少有2个节点,并且集群状态正常,集群所有数据都已经完成同步。

在检查集群状态时返还的列表中找到wsrep_last_committed项,比对各个节点的值,如果第一个节点的值在集群中是最大的,请不要停止服务!

确认已经同步完成后,通过以下命令停止db-t1中的mariadb服务:

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

然后修改server.conf配置文件,将db-t2的地址填写到wsrep_cluster_address中:

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

# 修改内容
wsrep_cluster_address='gcomm://db-t2.t.com:4567'

因为集群中已经有节点正在运行,所以使用以下命令正常启动mariadb即可:

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

然后使用以下命令再次检查集群状态:

[root@db-t1 ~]# mysql -e "SHOW STATUS LIKE 'wsrep_%'; " -p

主要检查以下内容:

  • wsrep_cluster_size:集群节点数量;
  • wsrep_incoming_addresses:集群各个节点的SST地址与端口;
  • wsrep_local_state_comment:本节点的同步状态;
  • wsrep_ready:服务状态。

如果一切正常,那么就可以进行测试了。

0x07 测试

如果你的集群正处于使用的状态,那么wsrep_last_committed这个参数应该是有数值的,在测试前我们先检查,在db-t1上执行以下命令:

因此我的集群是全新的,所以数值为0,如果检查db-t2上的数值,会发现也是如此。

现在登入到db-t1的数据库,然后新建一个数据库与用户,并将该数据库授权给该用户:

[root@db-t1 ~]# mysql -u root -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 13
Server version: 10.2.16-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> create database t1;
Query OK, 1 row affected (0.00 sec)

MariaDB [(none)]> GRANT ALL PRIVILEGES ON t1.* TO t1@localhost IDENTIFIED BY 't1_passwd';
Query OK, 0 rows affected (0.01 sec)

MariaDB [(none)]> flush privileges;
Query OK, 0 rows affected (0.01 sec)

MariaDB [(none)]> exit;
Bye

此时再检查集群中db-t1的数据:

[root@db-t1 ~]#  mysql -e "SHOW STATUS LIKE 'wsrep_%'; " -p | grep wsrep_last_committed
Enter password: 
wsrep_last_committed	3

可以看到wsrep_last_committed已经发生了变化。

现在在db-t2上使用t1这个用户登入到数据库:

[root@db-t2 ~]# mysql -u t1 -p 
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 23
Server version: 10.2.16-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| t1                 |
+--------------------+
2 rows in set (0.00 sec)

MariaDB [(none)]> exit;
Bye

可以发现,在db-t1上新建的用户与数据库已经同步到db-t2服务器上了,然后再db-t2上检查集群状态,会发现数值与db-t1一致。

此时我们用t1这个用户在db-t2上的t1数据库里新建一个表:

[root@db-t2 ~]# mysql -u t1 -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 25
Server version: 10.2.16-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> use t1;
Database changed
MariaDB [t1]> CREATE TABLE test_table (id int, username varchar(20), passwd varchar(20));
Query OK, 0 rows affected (0.05 sec)

MariaDB [t1]> exit;
Bye

然后再回到db-t1服务器里,检查该表是否完成建立:

[root@db-t1 ~]# mysql -u t1 -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 16
Server version: 10.2.16-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> use t1;
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
MariaDB [t1]> show tables;
+--------------+
| Tables_in_t1 |
+--------------+
| test_table   |
+--------------+
1 row in set (0.00 sec)

MariaDB [t1]> exit;
Bye

如果再检查两者的wsrep_last_committed,会发现是一致的,如果不一致,则说明集群出现了问题。

0x08 结语

集群可以添加多个节点,在实际应用中,强烈建议至少要有一个节点处于高可用的环境中,这样才能保证集群不会崩溃,毕竟重新启动集群是非常繁琐的一件事。

如果集群出现异常,导致脑裂或其他异常,这时候就需要手动启动各个节点。首先停止所有节点,然后用防火墙屏蔽掉4567端口,逐一启动节点,分别检查节点中wsrep_last_committed的值,选取wsrep_last_committed值最大的节点作为master,使用以下命令启动:

service mysql start --wsrep-new-cluster

最后逐一修改各个节点的防火墙,放行4567端口,如果有需要的话,还可以重启节点。

针对DDNS的环境会有很严重的问题,在DDNS环境中只能通过域名解析出IP获取当前的服务器公网IP。但解析工作会交由集群中的某个节点执行,而且一旦集群运行,该域名就不会再被解析,而是以IP地址的形式在集群中传播。

当DDNS的IP发生变化时,会产生集群崩溃的问题,我不知道是不是我配置有无还是别的原因,但在我测试过程中发现无法再DDNS环境中配置集群。

配置集群的时候建议使用固定的IP地址,如果是DDNS环境,则建议使用云服务或自建一个NAT网关或VPN网关,统一分配一个内网IP。