目前 MySQL 已经成为市场上主流数据库之一,考虑到业务的重要性,MySQL 数据库单点问题已成为企业网站架构中最大的隐患。随着技术的发展,MHA 的出现就是解决 MySQL 单点的问题。另外随着企业数据量越来越庞大,数据库的压力又成为企业的另一个瓶颈,MySQL 多主多从架构的出现可以减轻 MySQL 本身的压力。本章将主要围绕 “MySQL 主主复制 + Keepalived + HAProxy” 这一经典高可用架构展开,通过理论结合实践,解析如何通过开源工具实现数据库的故障自动转移、负载均衡和读写分离。
MySQL 高可用(High Availability)是指通过冗余设计,确保数据库服务在单节点故障、网络中断或硬件损坏等异常情况下,仍能持续对外提供服务,同时保证数据一致性。其核心目标是实现 “零停机、零数据丢失” 的业务连续性。
MySQL 主主复制 + Keepalived + HAProxy 的高可用方案由三部分组成:
MySQL 主主复制:两台 MySQL 实例互为主从,双向同步数据,均支持读写操作,提供冗余和扩展能力。
Keepalived:通过 VRRP 协议管理虚拟 IP(VIP),监控 MySQL 状态,故障时自动将 VIP 漂移至存活节点,确保服务地址不变。
HAProxy:作为反向代理和负载均衡器,将流量分发至 MySQL 节点,支持健康检查、读写分离(可选)和故障节点自动剔除。
高可用性:Keepalived 实现秒级故障切换,HAProxy 健康检查确保流量仅路由到正常节点,避免单点故障。
读写扩展:主主架构支持双节点并发写入,提升写入性能;HAProxy 可配置读写分离,利用备节点分担读压力。
灵活扩展:可横向扩展 HAProxy 或 MySQL 节点,支持动态调整负载均衡策略(如轮询、权重)。
运维友好:基于开源工具,无厂商锁定,社区支持丰富,适合自建数据库集群。
案例环境如表 7-1 所示。
主机 | 操作系统 | IP 地址 | 应用 |
---|---|---|---|
Master1 | openEuler 24.03 | 192.168.10.101 | Mysql8 |
Master2 | openEuler 24.03 | 192.168.10.102 | Mysql8 |
Keepalived1 | openEuler 24.03 | 192.168.10.103 | Keepalived、haproxy |
Keepalived2 | openEuler 24.03 | 192.168.10.104 | Keepalived、haproxy |
本案例要求通过 MHA 监控 MySQL 数据库在故障时进行自动切换,不影响业务。
(1)安装 MySQL 数据库;
(2)配置 MySQL 互为主从;
(3)安装 haproxy 软件并配置复制均衡;
(4)安装 keepalived 软件并配置故障转移;
(5)模拟 master 故障切换
在 Master1、Master2 服务器上安装 MySQL 数据库。本案例采用二进制安装
如果采用 OpenEuler minimal 安装的系统,在使用前需要安装一些基础软件包工具。
[root@localhost ~]# yum -y install gcc vim wget net-tools lrzsz
安装 MySQL 依赖的软件包
[root@localhost ~]# dnf install -y libaio numactl openssl ncurses-compat-libs
创建运行 MySQL 程序的用户
[root@localhost ~]# useradd -M -s /sbin/nologin mysql
关闭 SELinux 和防火墙
[root@localhost ~]# sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
[root@localhost ~]# setenforce 0
[root@localhost ~]# systemctl disable firewalld
[root@localhost ~]# systemctl stop firewalld
二进制安装的版本采用跟上面编译安装的版本一样 MySQL 8.0.36。首先需要下载该软 件包或者提前上传,然后再解压进行配置。
[root@localhost ~]# tar xJf mysql-8.0.36-linux-glibc2.28-x86_64.tar.xz
[root@localhost ~]# mv mysql-8.0.36-linux-glibc2.28-x86_64 /usr/local/mysql
2025-03-19T13:28:28.959651Z 0 [System] [MY-013169] [Server] /usr/local/mysql/bin/mysqld (mysqld 8.0.36) initializing of server in progress as process 6414
2025-03-19T13:28:28.968618Z 1 [System] [MY-013576] [InnoDB] InnoDB
initialization has started.
2025-03-19T13:28:29.141927Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
2025-03-19T13:28:30.473508Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: Xr6:Ggnu8?/8
此次初始化没有采用无密码模式,因此会生成初始随机密码,需要保存,用以后续登录 mysql 数据库使用
MySQL 的配置文件跟上面编译安装的配置文件类似。
[root@localhost ~]# vim /etc/my.cnf
[client]
socket=/usr/local/mysql/data/mysql.sock
[mysqld]
socket=/usr/local/mysql/data/mysql.sock
bind-address = 0.0.0.0
skip-name-resolve
port = 3306
basedir=/usr/local/mysql
datadir=/usr/local/mysql/data
max_connections=2048
character-set-server=utf8
default-storage-engine=INNODB
max_allowed_packet=16M
将 MySQL 的可执行文件写入环境变量中。
[root@localhost ~]# echo "export PATH=$PATH:/usr/local/mysql/bin">> /etc/profile
[root@localhost ~]# . /etc/profile //使配置在当前 Shell 中生效
将 MySQL 添加成为系统服务,通过使用 systemctl 来管理。在 /usr/local/mysql/support-files 目录下找到 mysql.server 文件,将其复制到 /etc/rc.d/init.d 目录下,改名为 mysqld 并赋予可执行权限。
[root@localhost ~]# cp /usr/local/mysql/support-files/mysql.server /etc/rc.d/init.d/mysqld
[root@localhost ~]# chmod +x /etc/rc.d/init.d/mysqld
编辑生成 mysqld.service 服务,通过 systemctl 方式来管理。
[root@localhost ~]# vim /lib/systemd/system/mysqld.service
[Unit]
Description=mysqld
After=network.target
[Service]
Type=forking
ExecStart=/etc/rc.d/init.d/mysqld start
ExecReload=/etc/rc.d/init.d/mysqld restart
ExecStop=/etc/rc.d/init.d/mysqld stop
PrivateTmp=true
[Install]
WantedBy=multi-user.target
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl enable mysqld
[root@localhost ~]# systemctl start mysqld
[root@localhost ~]# netstat -tunlp |grep 3306
tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN 1785/mysqld
[root@localhost ~]# mysqladmin -u root -p password 'pwd123' //为 root 用户设置密码
登录 MySQL 程序,创建测试用户,后续验证实验时使用。(Master1、Master2 都执行)
[root@localhost ~]#mysql -u root -p
mysql> CREATE USER 'test'@'192.168.10.%' IDENTIFIED BY '123456';
Query OK, 0 rows affected (0.01 sec)
mysql> GRANT ALL ON *.* TO 'test'@'192.168.10.%';
Query OK, 0 rows affected (0.01 sec)
mysql> ALTER USER 'test'@'192.168.10.%' IDENTIFIED WITH mysql_native_password BY '123456';
#MySQL 8.0 默认使用 caching_sha2_password 认证插件,将 mysql_native_password 替换为旧版认证插件,确保从库能兼容
mysql> FLUSH PRIVILEGES;
对于 MySQL 的主主架构,其实原理就是两台服务器 Master1、Master2 互为主从,双向复制
在 Master1 /etc/my.cnf 中修改或者增加下面内容。
log - bin = /usr/local/mysql/data/mysql - bin #启用二进制日志(Binary Log)并指定其存储路径
binlog_format = MIXED #定义二进制日志的记录格式为混合模式
server - id = 1 #为 mysql 实例分配一个唯一的服务器标识符
在 Master2 /etc/my.cnf 中修改或者增加下面内容。
log - bin = /usr/local/mysql/data/mysql - bin #启用二进制日志(Binary Log)并指定其存储路径
binlog_format = MIXED #定义二进制日志的记录格式为混合模式
server - id = 2 #为 mysql 实例分配一个唯一的服务器标识符
重启 MySQL 服务。(Master1、Master2 都执行)
[root@localhost ~]# systemctl restart mysqld
登录 MySQL 程序,给从服务器授权。(Master1、Master2 都执行)
[root@localhost ~]# mysql -u root -p
mysql> CREATE USER 'myslave'@'192.168.10.%' IDENTIFIED BY '123456';
Query OK, 0 rows affected (0.01 sec)
mysql> GRANT REPLICATION SLAVE ON . TO 'myslave'@'192.168.10.%';
Query OK, 0 rows affected (0.01 sec)
mysql> ALTER USER 'myslave'@'192.168.10.%' IDENTIFIED WITH mysql_native_password BY '123456';
MySQL 8.0 默认使用 caching_sha2_password 认证插件,将 mysql_native_password 替换为旧版认证插件,确保从库能兼容
mysql> FLUSH PRIVILEGES;
mysql> show master status;
其中 File 列显示日志名,Position 列显示偏移量,这两个值在后面配置从服务器的时候需要。Slave 应该从该点上进行新的更新。
Master1
按主服务器结果更改下面命令中 master_log_file 和 master_log_pos 参数。
[root@localhost ~]# mysql -uroot -p
mysql> change master to master_host='192.168.10.102',master_user='myslave', master_password='123456',master_log_file='mysql - bin.000001',master_log_pos=157;
Query OK, 0 rows affected, 8 warnings (0.01 sec)
启动同步。
mysql> start slave;
Query OK, 0 rows affected (0.00 sec)
查看 Slave 状态,确保以下两个值为 YES
[root@localhost ~]# mysql -uroot -p -e "show slave status\G" | grep Yes
Enter password:
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
按主服务器结果更改下面命令中 master_log_file
和 master_log_pos
参数。
[root@localhost ~]# mysql -uroot -p
mysql> change master to master_host='192.168.10.101',master_user='myslave', master_password='123456',master_log_file='mysql-bin.000001',master_log_pos=157;
Query OK, 0 rows affected, 8 warnings (0.01 sec)
启动同步。
mysql> start slave;
Query OK, 0 rows affected (0.00 sec)
查看 Slave 状态,确保以下两个值为 YES
[root@localhost ~]# mysql -uroot -p -e "show slave status\G" | grep Yes
Enter password:
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
HAProxy 是一个开源的高性能负载均衡器和代理工具,支持 TCP/HTTP 应用的流量分发,具备健康检查、SSL 终止、会话保持等功能,广泛应用于 Web 服务器集群、数据库读写分离及 API 网关场景,以高效稳定的特性提升系统可用性和扩展能力。
该阶段操作在 Keepalived1、Keepalived2 都要执行
[root@localhost ~]# sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
[root@localhost ~]# setenforce 0
[root@localhost ~]# systemctl disable firewalld
[root@localhost ~]# systemctl stop firewalld
[root@localhost ~]# dnf install haproxy
内容如下,更改部分红色标注(Keepalived1、Keepalived2 配置相同)
[root@localhost ~]# vim /etc/haproxy/haproxy.cfg
global
log 127.0.0.1 local2
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
user haproxy
group haproxy
daemon
maxconn 4000
defaults
mode tcp
log global
option tcplog
option dontlognull
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server 1m
timeout http-keep-alive 10s
timeout check 10s
maxconn 3000
listen mysql
bind 0.0.0.0:3306 # 显式指定监听地址和端口
balance leastconn # 负载均衡算法
server mysql1 192.168.10.101:3306 check port 3306 maxconn 300
server mysql2 192.168.10.102:3306 check port 3306 maxconn 300
mode tcp
:表示 tcp 代理listen mysql 0.0.0.0:3306
:表示创建一个名为 mysql 的监听服务,bind 0.0.0.0:3306
绑定到所有网卡的 3306 端口(MySQL 默认端口),作为流量入口。balance leastconn
:指定使用 “最少连接数算法” 分配请求,将新连接导向当前活跃连接数最少的后端服务器,避免单点过载server mysql1 192.168.10.101:3306
和 server mysql2 192.168.10.102:3306
:分别指向 192.168.10.101:3306 和 192.168.10.102:3306。check port 3306
:表示通过检查节点的 3306 端口是否响应,判断其存活状态限制每个后端节点的最大并发连接数为 300,防止节点被压垮
plaintext
[root@localhost ~]# haproxy -c -f /etc/haproxy/haproxy.cfg
[root@localhost ~]# systemctl restart haproxy
使用测试用户 test,访问 haproxy 的代理端口登录 mysql
[root@localhost ~]# mysql -u test -p123456 -h192.168.10.103 -P3306
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 26
Server version: 8.0.36 Source distribution
Copyright (c) 2000, 2025, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
提取文字
Keepalived 是一个用于实现系统高可用性和负载均衡的工具,通过 VRRP(Virtual Router Redundancy Protocol)等协议管理虚拟 IP 地址,持续监测服务器健康状态,当主节点故障时自动将流量切换至备用节点,确保服务不中断,常用于数据库、Web 服务等集群环境,提升系统可靠性并简化故障恢复流程。
该阶段操作在 Keepalived1、Keepalived2 都要执行
[root@localhost ~]# sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
[root@localhost ~]# setenforce 0
[root@localhost ~]# systemctl disable firewalld
[root@localhost ~]# systemctl stop firewalld
[root@localhost ~]# dnf install keepalived
Keepalived1 配置,红色标注为两台不同之处,需要注意
[root@localhost ~]# cp /etc/keepalived/keepalived.conf.sample /etc/keepalived/keepalived.conf
[root@localhost ~]# vim /etc/keepalived/keepalived.conf
!Configuration File for keepalived
global_defs {
router_id r1
}
vrrp_script chk_haproxy {
script "/etc/keepalived/chk.sh"
interval 2
}
vrrp_instance VI_1 {
state BACKUP
nopreempt
interface ens33
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.10.100
}
track_script {
chk_haproxy
}
notify_backup "/etc/init.d/haproxy restart"
notify_fault "/etc/init.d/haproxy stop"
}
启用 nopreempt
的作用:
节点 A(优先级 100,配置 nopreempt
)故障 → 节点 B 成为 Master。
节点 A 恢复 → 不触发抢占,节点 B 继续作为 Master,VIP 不切换。
仅当节点 B 故障时,节点 A 才会重新成为 Master。
添加监控脚本并启动 Keepalived
[root@localhost ~]# vim /etc/keepalived/chk.sh
#!/bin/bash
if [ `ps -C haproxy --no-header | wc -l` -eq 0 ]; then
/etc/init.d/keepalived stop
fi
[root@localhost ~]# chmod +x /etc/keepalived/chk.sh
[root@localhost ~]# systemctl start keepalived
[root@localhost ~]# cp /etc/keepalived/keepalived.conf.sample /etc/keepalived/keepalived.conf
[root@localhost ~]# vim /etc/keepalived/keepalived.conf
!Configuration File for keepalived
global_defs {
router_id r2
}
vrrp_script chk_haproxy {
script "/etc/keepalived/chk.sh"
interval 2
}
vrrp_instance VI_1 {
state BACKUP
interface ens33
virtual_router_id 51
priority 99
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.10.100
}
track_script {
chk_haproxy
}
notify_backup "/etc/init.d/haproxy restart"
notify_fault "/etc/init.d/haproxy stop"
}
添加监控脚本并启动keepalived
[root@localhost ~]# vim /etc/keepalived/chk.sh
#!/bin/bash
#
if [ `ps -C haproxy --no-header | wc -l` -eq 0 ]; then
/etc/init.d/keepalived stop
fi
[root@localhost ~]# chmod +x /etc/keepalived/chk.sh
[root@localhost ~]# systemctl restart keepalived
此处两台主机均配置为 BACKUP
,因此哪台先运行 keepalived
,VIP 就在哪台上。
本案例刚开始 VIP 运行在 Keepalived1 上。
在 Keepalived1 测试查看 VIP(注意查看 VIP 只能用 ip
命令,ifconfig
不显示 VIP)
[root@localhost ~]# ip a
1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens33: mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:0c:29:ac:a8:01 brd ff:ff:ff:ff:ff:ff
inet 192.168.10.103/24 brd 192.168.10.255 scope global noprefixroute ens33
valid_lft forever preferred_lft forever
inet 192.168.10.100/32 scope global link
valid_lft forever preferred_lft forever
inet6 fe80::20c:29ff:feac:a801/64 scope link
valid_lft forever preferred_lft forever
[root@localhost ~]# mysql -utest -p123456 -h192.168.10.100
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 28
Server version: 8.0.36 Source distribution
Copyright (c) 2000, 2025, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> \q
Bye
关闭 master1 后,haproxy 检测其状态异常,会从负载中移除,不进行流量转发;依然可以通过 vip 访问 mysql,实现 mysql 的高可用
[root@localhost ~]# ping 192.168.10.101 # ping master1 发现已离线
PING 192.168.10.101 (192.168.10.101) 56(84) bytes of data.
来自 192.168.10.104 icmp_seq=1 目标主机不可达
[root@localhost ~]# mysql -utest -p123456 -h192.168.10.100
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 22
Server version: 8.0.36 Source distribution
Copyright (c) 2000, 2024, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
接着关闭 keepalived1,测试使用 vip 能否正常访问 mysql 数据库
关闭 keepalived1 后,keepalived 检测到主节点离线,VIP 192.168.10.100 会漂移至 keepalived2 节点,mysql 依然可以访问
[root@localhost ~]# mysql -utest -p123456 -h192.168.10.100
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 22
Server version: 8.0.36 Source distribution
Copyright (c) 2000, 2024, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>