发布时间:2025-11-05 07:34:47 来源:创站工坊 作者:数据库
业界流传一句话:没有做过运维的实战程序员不是好架构师。
不知是可用真是假。

对于 MySQL 数据库作为各个业务系统的架构存储介质,在系统中承担着非常重要的实战职责,如果数据库崩了,可用那么对于读和写数据库的架构操作都会受到影响。如果不能迅速恢复,实战对业务的可用影响是非常大的。之前 B 站不是架构出过一次事故么,2 小时才恢复过来,实战详细可以看之前写的可用文章。
B 站崩了,架构总结下「高可用」和「异地多活」
上次折腾完 ELK 日志检索平台后,实战开发环境可以正常查询日志了。可用最近在做系统高可用相关的架构工作,这次我来分享下 MySQL 双主 + Keepalived 的高可用落地和踩坑之路。
一文带你搭建一套 ELK Stack 日志平台
对于 MySQL 的高可用,主要分为两步,云南idc服务商配置 MySQL 主主模式和 keepalived 软件。拓扑图如下所示:

两个数据库分别部署在两台服务器上,相互同步数据,但是只有一个提供给外部访问,当一个宕机后,另外一个可以继续提供服务,在没有 keepalived 软件的帮助下,只能手动切换
keepalived 监测、自动重启、流量切换检测和重启:两台服务器上都部署 keepalived 软件,定时检测 MySQL 服务是否正常,如果一个数据库服务崩了,keepalived 会用脚本尝试重启 mysql 服务。备份:两个 keepalived 服务都提供了虚拟 IP 供客户端使用,但是流量只会转到一台 MySQL 服务上。虚拟 IP:keepalived 配置好了后,会有一个 虚拟 IP,对于客户端来说,不关心连接的是哪台 MySQL,访问虚拟 IP 就可以了。流量切换:如果客户端正在访问的 MySQL 服务崩了后,keepalived 会用我们写的脚本自动重启 MySQL,如果重启失败,脚本主动停掉 keepalived,b2b信息网客户端的流量就不会访问到这台服务器上的 MySQL 服务,后续访问的流量都会切到另外一台 MySQL 服务。检测和重启的原理如下所示:

需要配置的内容如下:
两台 Ubuntu 服务器上启动 MySQL 容器。配置 MySQL 主从复制架构。将 MySQL 主从改为主主复制架构。两台服务器搭建 keepalived 环境监控 MySQL 和自动重启 MySQL。对于 MySQL 的主主架构,其实原理就是两台服务器互为主从,双向复制。而复制的原理如下:
主从复制主要有以下流程:
主库将数据的改变记录到 binlog 中;从库会在一定时间间隔内对master 的 binlog 进行检查,如果发生改变,则开始一个 I/O Thread 请求读取 master 中 binlog ;同时主库为每个 I/O 线程启动一个 dump 线程,用于向其发送二进制事件,并保存至从节点本地的中继日志中,从库将启动 SQL 线程从中继日志中读取二进制日志,在本地重放,使得其数据和主节点的保持一致,最后 I/O Thread 和 SQL Thread 将进入睡眠状态,等待下一次被唤醒;
大白话就是亿华云:
从库会生成两个线程,一个 I/O 线程,一个 SQL 线程;
I/O 线程会去请求主库的 binlog,并将得到的 binlog 写到本地的 relay-log (中继日志)文件中;
主库会生成一个 dump 线程,用来给从库 I/O 线程传 binlog;
SQL 线程,会读取 relay log 文件中的日志,并解析成 SQL 语句逐一执行;
接下来我们先把 MySQL 的基础环境在两台 Ubuntu 服务器上搭建好,后续操作都是基于这个来做的。
作为演示,我在本机启动了两台 Ubuntu 虚拟机,安装有 docker。因为我们的测试和生产环境是用 Docker 跑的,所以我将环境的镜像打包后,还原到我的虚拟机上面。
保存测试环境的 mysql 镜像
复制sudo docker save -o mysql.tar hcr:5000/hschub/hscmysql:0.0.2sudo chmod 777 mysql.tar1.2.3.两台机器导入镜像
复制sudo docker load -i mysql.tar1.启动容器,需要注意的是需要映射本地文件夹。
复制sudo docker run -p 3306:3306 --name mysql\
-v /home/hss/mysql/data:/var/lib/mysql\
-v /home/hss/mysql/etc/mysql:/etc/mysql\
-e MYSQL_ROOT_PASSWORD=123456\
-d 46b1.2.3.4.5.-v 代表映射的文件夹,-d 表示后台运行,46b 代表镜像 id。
进入容器,连接 mysql,node1的mysql 密码是 123456,node2 是 123456
复制# 查询容器 iddocker ps# 进入 mysql 容器docker exec -it <容器 id> /bin/bash# 连接 mysqlmysql -u root -p1.2.3.4.5.6.7.8.接下来我们配置 MySQL 的主从架构,需要注意的是后续搭建的主主架构是基于主从架构来的,区别就是修改了一部分配置。
拓扑结构:
192.168.56.11 node1,主节点192.168.56.12 node2,从节点
修改 /home/hss/mysql/etc/mysql/my.cnf 文件
复制server_id = 11log_bin = /var/lib/mysql/log/mysql-binbinlog-ignore-db=mysqlbinlog_format= mixedsync_binlog=100log_slave_updates = 1binlog_cache_size=32mmax_binlog_cache_size=64mmax_binlog_size=512mrelay_log = /var/lib/mysql/log/relay-binrelay_log_index = /var/lib/mysql/log/relay-bin.indexmaster_info_repository=TABLErelay-log-info-repository=TABLErelay-log-recovery1.2.3.4.5.6.7.8.9.10.11.12.13.14.创建 /home/hss/mysql/data/log/mysql-bin 文件夹
创建 /home/hss/mysql/data/log/relay-bin 文件夹
给两个文件夹加上 777 权限,然后重启 MySQL 容器。
和主节点配置类似,需要修改 server_id = 12
;
ALTER USER vagrant@192.168.56.12 IDENTIFIED WITH mysql_native_password BY vagrant;
GRANT REPLICATION SLAVE ON *.* TO vagrant@192.168.56.12;
FLUSH PRIVILEGES;1.2.3.4.5.6.7.FLUSH TABLES WITH READ LOCK;

记住 File 和 Position,后面会用到。这里 File = mysql-bin.000008,Position = 1020。
查看挂载目录下是否有生成 all_databases.sql 文件,如下图所示:

mysql -uroot -p -hlocalhost -P3306 < /var/lib/mysql/backup/all_databases.sql
在 MySQL 命令行窗口中执行以下命令设置同步信息。这里就是配置主数据库的 IP 地址、Port、用户名、密码,二进制文件名,偏移量。
复制CHANGE MASTER TO MASTER_HOST=192.168.56.11,
MASTER_PORT=3306,
MASTER_USER=vagrant,
MASTER_PASSWORD=vagrant,
MASTER_LOG_FILE=mysql-bin.000008,
MASTER_LOG_POS=1020;1.2.3.4.5.6.在 MySQL 命令行窗口中执行以下命令启动从数据库的复制线程。
复制START salve;1.如果 Slave_IO_Running 和 Slave_SQL_Running 显示 Yes,就表示启动同步成功。如下图所示:

在主库上执行以下命令显示当前连接过来的从库线程。
复制SHOW PROCESSLIST1.如下所示,Slave has read all relay log; wating for more updates,说明从库已经同步完了。

使用上面的两个命令,我们可以判断当前的复制情况。
下面验证下主从节点之间是否能正常同步数据。
主节点创建 testdb 数据库和 member 表。
刷新下从节点,发现从节点自动创建了 member 表。如下图所示。
然后在主节点插入一条数据,刷新从节点后,发现从节点也自动创建了一条数据。

配置主主架构就是在主从架构中交换下配置信息。步骤如下:
在节点 node2 上创建复制账户。查看二进制日志文件和位置信息。在节点 node1 上设置主从复制的信息,包括 ip,port,用户名,密码,二进制日志文件和位置信息。node1 开启主从复制,查看主从复制状态STOP slave
5.2.2 添加主节点 mysql 账户信息 复制CREATE USER vagrant@192.168.56.11 IDENTIFIED BY vagrant;
ALTER USER vagrant@192.168.56.11 IDENTIFIED WITH mysql_native_password BY vagrant;
GRANT REPLICATION SLAVE ON *.* TO vagrant@192.168.56.11;
FLUSH PRIVILEGES;1.2.3.4.5.6.7. 5.2.3 查看二进制日志文件和位置信息SHOW MASTER STATUS

start slave
5.2.5 查看同步状态
,
MASTER_PORT=3306,
MASTER_USER=vagrant,
MASTER_PASSWORD=vagrant,
MASTER_LOG_FILE=mysql-bin.000001,
MASTER_LOG_POS=2453;1.2.3.4.5.6. 5.3.2 启动从数据库的复制线程START salve
5.3.3 查看从数据库的同步状态SHOW slave status \G

node2 的 member 表增加一条数据 (2,zzz),node1 上同步成功

node 1 的 member 表增加一条数据(3,aaa),node2 上同步成功

Keepalived 软件在主主架构中,可以配置成两种应用场景:
① 当这台服务器上的 keepalived 发现 MySQL 服务崩了后,立刻停掉这台服务器上 keepalived 自己,这样流量就会自动切到另外一台 keepalived 服务器。② 当这台服务器上的 keepalived 发现 MySQL 服务崩了后,立刻尝试重启 MySQL 服务,如果重启失败,则停掉 keepalived 自己。和第一种方案的区别是会尝试重启 MySQL 服务。这里我配置成第二种功能场景,保障 MySQL 服务的高可用。另外可以配置 MySQL 服务异常时,发送邮件给运维或开发人员,由他们检查服务器的状态。

Keepalived 提供了一个虚拟 IP (简称 VIP),对外提供访问。当客户端连接这个虚拟 IP 后,只会访问其中一个 MySQL。MySQL 节点故障后,keepalived 执行脚本进行重启,如果重启失败,脚本自动停掉 keepalived,备用节点自动切换为主节点。
keepalived 检测和重启的流程图如下:

安装依赖、获取 keepalived 安装包、解压安装包、删除安装包。
复制# 安装依赖sudo apt-get install -y libssl-devsudo apt-get install -y opensslsudo apt-get install -y libpopt-devsudo apt-get install -y libnl-devsudo apt-get install -y libnl-3-devsudo apt-get install -y libnl-genl-3.devsudo apt-get install daemonsudo apt-get install libc-devsudo apt-get install libnfnetlink-devsudo apt-get install gcc# 获取 keepalived 安装包cd /usr/localsudo susudo wget https://www.keepalived.org/software/keepalived-2.2.2.tar.gz# 解压安装包sudo tar -zxvf keepalived-2.2.2.tar.gz# 删除安装包mv keepalived-2.2.2 keepalived1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.配置 keepalived 软件
复制cd keepalived./configure --prefix=/usr/local/keepalived --disable-dependency-tracking1.2.3.执行结果如下所示:

编译 keepalived 软件
复制sudo make && make install1.执行结果如下所示:

对于 Ubuntu ,需要做一点特别的改动,创建链接
复制mkdir -p /etc/rc.d/init.dln -s /lib/lsb/init-functions /etc/rc.d/init.d/functions1.2.拷贝配置文件
复制sudo mkdir /etc/sysconfig
sudo cp /usr/local/keepalived/etc/sysconfig/keepalived /etc/sysconfig/
sudo cp /usr/local/keepalived/keepalived/etc/init.d/keepalived /etc/init.d/
sudo cp /usr/local/keepalived/sbin/keepalived /sbin/
sudo mkdir /etc/keepalived
sudo cp /usr/local/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/1.2.3.4.5.6.修改配置文件 /etc/keepalived/keepalived.conf
复制daemon keepalived ${KEEPALIVED_OPTIONS}1.改为
复制daemon -- keepalived ${KEEPALIVED_OPTIONS}1.先用 ifconfig 查看当前的网卡,比如我的服务器上是 enp0s8。
复制ip addr del 192.168.56.88 dev enp0s8:1ifconfig enp0s8:1 192.168.56.88 broadcast 192.168.56.255 netmask 255.255.255.0 uproute add -host 192.168.56.88 dev enp0s8:11.2.3.4.将命令写到 /usr/local/script/vip.sh文件中。最好将 /usr/local/script/vip.sh文件添加到服务器的开机启动项中,将 Keepalived 服务设置为开机自启动(未写)。
备份配置文件
复制sudo mv /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.backup1.修改配置文件
复制sudo vim /etc/keepalived/keepalived.conf1.配置文件的内容如下:
复制global_defs{
router_id MYSQL_HA #当前节点名}
vrrp_script restart_mysql{
script "/usr/local/keepalived/restart_mysql.sh" #重启 mysql 容器 interval 2 weight 2}
vrrp_instance VI_1{
state BACKUP #两台配置节点均为BACKUP interface enp0s8 #绑定虚拟IP的网络接口 virtual_router_id 51 #VRRP组名,两个节点的设置必须一样,以指明各个节点属于同一VRRP组 priority 101 #节点的优先级,另一台优先级改低一点 advert_int 1 #组播信息发送间隔,两个节点设置必须一样 nopreempt #不抢占,只在优先级高的机器上设置即可,优先级低的机器不设置 authentication { #设置验证信息,两个节点必须一致 auth_type PASS auth_pass 123456}
track_script{
restart_mysql #检测 mysql 状态,如果失败,则重启 mysql 容器}
virtual_ipaddress { #指定虚拟IP,两个节点设置必须一样 192.168.56.88}
}
virtual_server 192.168.56.88 3306 { #linux虚拟服务器(LVS)配置 delay_loop 2 #每个2秒检查一次real_server状态 lb_algo wrr #LVS调度算法,rr|wrr|lc|wlc|lblc|sh|dh lb_kind DR #LVS集群模式 ,NAT|DR|TUN persistence_timeout 60 #会话保持时间 protocol TCP #使用的协议是TCP还是UDP real_server 192.168.56.11 3306{
weight 3 #权重 TCP_CHECK{
connect_timeout 10 #连接超时时间 nb_get_retry 3 #重连次数 delay_before_retry 3 #重连间隔时间 connect_port 3306 #健康检查端口}
}
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.编写异常处理脚本
复制sudo vim /usr/local/keepalived/restart_mysql.sh1.内容如下,
复制#!/bin/bash# 定义变量,重启 mysql 容器START_MYSQL="docker restart mysql"# 定义变量,停止 mysql 容器STOP_MYSQL="docker stop mysql"# 定义变量,日志文件路径LOG_FILE="/usr/local/keepalived/logs/mysql-check.log"# 定义变量,检查 mysql 服务是否正常的命令HAPS=`ps -C mysqld --no-header |wc -l`# 打印当前时间到日志文件date "+%Y-%m-%d %H:%M:%S" >> $LOG_FILE# 打印提示信息到日志文件echo "check mysql status" >> $LOG_FILE# 检查数据库状态,如何返回 0,则重启 mysql 容器,然后休眠 3s 后,再次检测 mysql 状态,如果还是返回 0,则停止 keepalived。if [ $HAPS -eq 0 ];thenecho $START_MYSQL >> $LOG_FILE$START_MYSQL >> $LOG_FILE 2>&1sleep 3if [ `ps -C mysqld --no-header |wc -l` -eq 0 ];thenecho "start mysql failed, killall keepalived" >> $LOG_FILEkillall keepalivedfifi1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.给脚本分配权限
复制sudo chmod +x /usr/local/keepalived/restart_mysql.sh1.创建 logs 文件夹,给 logs 文件夹分配权限
复制sudo mkdir /usr/local/keepalived/logssudo chmod +x /usr/local/keepalived/logs -r1.2.重新加载配置文件
复制sudo systemctl daemon-reload1.启动 node2 节点:
复制sudo pkill keepalivedsudo systemctl start keepalivedsudo systemctl status keepalived1.2.3.4.5.
启动 node1 节点:
复制pkilll keepalived
sudo systemctl status keepalived1.2.3.
我们可以通过这个命令查看 keepalived 进程
复制ps -ef | grep keepalived1.查看日志
复制sudo cat /var/log/syslog1.停止 node2 上的 mysql 容器
复制docker stop 8cc1.查看 keepalived 状态,提示移除了 mysql 服务。

因为 keepalived 会每 2s 检查一次 MySQL 的状态,发现 MySQL 异常后,就会重启 mysql 容器。所以过几秒后,重新查看容器状态,会看到 mysql 容器重新启动了。
复制docker ps1.查看 keepalived 状态,执行 restart_mysql 成功

查看执行日志

问题:每 2s 会打印一次,文件可能会很大。需要执行定期删除。
验证下当 MySQL 重启失败后,keepalived 自动停止后,客户端连接的 MySQL 是否会自动切到另外一个 MySQL 节点上。
首先用 mysql 客户端工具 navicat 连接虚拟 ip 地址,账号和密码就是 node 1 和 node2 的 mysql 账号密码(root/123456)

可以连接上,然后执行以下命令,查看当前虚拟 ip 连接的是哪个数据库
复制SHOW VARIABLES LIKE %hostname%1.可以看到连接的是 node2 的容器的 id,说明 keepalived 已经通过虚拟 ip 连接到 node2 的 mysql 了,是正常工作的,node2 现在是作为主节点,node1 作为备用节点。

由于本地环境重新启动 MySQL 都是成功的,不会停掉 keepalived 服务。出于演示目的,我就直接停掉 keepalived 服务。
复制pkill keepalived1.执行下面这个命令可以查看 keepalived 进程,发现已经没有了。(控制台显示的 grep --color=auto keepalived 表示是查找命令)
复制ps -ef | grep keepalived1.重新查询客户端的连接信息,发现已经切换到 92b (node1)机器上的 mysql 了。
复制SHOW VARIABLES LIKE %hostname%1.
再次查看 node1 上 keepalived 上的状态,再发送信息给
复制sudo systemctl status keepalived1.
密码不正确,设置跳过密码验证
复制apt-get updateapt install vim1.2.3.修改 mysql 配置文件
复制vim /etc/mysql/my.cnf1.添加一行配置,跳过 mysql 密码验证
复制skip-grant-tables1.重启容器
docker restart 9e6
重新计入 mysql 容器,连接 mysql,不需要密码就可以连接上 mysql。
复制mysql1.
修改登录密码
复制update mysql.user set authentication_string=PASSWORD(123456) where User=root; 1.重启容器
拷贝 mysql 文件夹
.
This may mean that the package is missing, has been obsoleted, oris only available from another sourceE: Package libnl-dev has no installation candidate1.2.3.4.5.6.7.8.9.10.解决方案:更新包
复制sudo apt-get -y update1.Err:15 https://download.docker.com/linux/ubuntu bionic Release Could not wait for server fd - select (11: Resource temporarily unavailable) [IP: 198.18.0.39 443] Reading package lists... Done E: The repository https://download.docker.com/linux/ubuntu bionic Release no longer has a Release file. N: Updating from such a repository cant be done securely, and is therefore disabled by default. N: See apt-secure(8) manpage for repository creation and user configuration details.
解决方案:
修改配置文件
复制sudo mv /etc/apt/sources.list /etc/apt/sources.list.backupsudo vim /etc/apt/sources.list1.2.配置内容如下:
复制deb http://archive.ubuntu.com/ubuntu/ trusty main restricted universe multiverse deb http://archive.ubuntu.com/ubuntu/ trusty-security main restricted universe multiverse deb http://archive.ubuntu.com/ubuntu/ trusty-updates main restricted universe multiverse deb http://archive.ubuntu.com/ubuntu/ trusty-proposed main restricted universe multiverse deb http://archive.ubuntu.com/ubuntu/ trusty-backports main restricted universe multiverse1.2.3.4.5.执行更新
复制sudo apt-get update1.:
libnl-3-dev : Depends: libnl-3-200 (= 3.2.21-1ubuntu4.1) but 3.2.29-0ubuntu3 is to be installed Conflicts: libnl-dev but 1.1-8ubuntu1 is to be installed libnl-genl-3-dev : Depends: libnl-genl-3-200 (= 3.2.21-1ubuntu4.1) but 3.2.29-0ubuntu3 is to be installedE: Unable to correct problems, you have held broken packages.1.2.3.4.5.6.7.解决方案:
按照这个报错信息来进行降级,等号后面就是提示信息里括号的版本信息。
复制sudo apt-get install libnl-3-200=3.2.21-1ubuntu4.1sudo apt-get install libnl-genl-3-200=3.2.21-1ubuntu4.11.2..
root@node1:/usr/local/keepalived/etc/keepalived# systemctl status keepalived.service● keepalived.service Loaded: masked (/dev/null; bad)
Active: inactive (dead)
Condition: start condition failed at Wed 2022-05-11 02:40:46 UTC; 1 day 3h ago1.2.3.4.5.6.7.
解决方案:
复制systemctl unmask sshd1.再次启动,提示另外一个错误。

解决方案,因为 ubuntu 没有这个命令 /etc/rc.d/init.d/functions,所以需要添加一个命令链接
复制mkdir -p /etc/rc.d/init.dln -s /lib/lsb/init-functions /etc/rc.d/init.d/functions1.2.切换回 ubuntu 官方源
复制sudo mv /etc/apt/sources.list /etc/apt/sources.list.backup2sudo mv /etc/apt/sources.list.backup /etc/apt/sources.listsudo apt-get updatesudo apt-get install gcc1.2.3.4.5.6.7.
解决方案:
配置文件的 global_defs 配置里面增加 script_user root
复制global_defs{
script_user rot}1.2.3..
May 16 03:50:54 node1 Keepalived_vrrp[19855]: SECURITY VIOLATION - scripts are being executed but script_security not enabled.1.2.给脚本添加权限。