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。