Skip to content

MySQL-同步常用指令

sql
-- 安全重启复制
STOP SLAVE;
START SLAVE;

-- 跳过指定错误(慎用)
SET GLOBAL sql_slave_skip_counter = 1;
START SLAVE SQL_THREAD;

-- 重置复制(初始化场景)
RESET SLAVE ALL;

-- 检查主库当前状态
SHOW MASTER STATUS;
-- 查看同步基本状态
SHOW SLAVE STATUS\G

-- 重点关注:
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 0
Last_IO_Error:
Last_SQL_Error:

-- GTID模式专用监控
SELECT * FROM performance_schema.replication_group_member_stats;

-- 建立同步复制
CHANGE MASTER TO
MASTER_HOST='192.168.1.102',
MASTER_USER='repl',
MASTER_PASSWORD='Repl@secure123',
MASTER_AUTO_POSITION = 1;  # 使用GTID模式


-- 检查复制冲突
SHOW STATUS LIKE 'slave%conflict%';

-- 监控GTID执行进度
SELECT @@global.gtid_executed;

MySQL-MHA 高可用:主从同步

mermaid
graph TD
    %% 颜色定义
    classDef manager fill:#ffcccc,stroke:#ff0000
    classDef master fill:#ffcccc,stroke:#ff0000
    classDef slave fill:#000000,stroke:#333,color:white

    %% 架构图主体
    MHA_manager[MHA manager]:::manager
    Master1[Master]:::master
    Master2[Master]:::master
    Slave01_1[Slave01]:::slave
    Slave02_1[Slave02]:::slave
    Slave01_2[Slave01]:::slave
    Slave02_2[Slave02]:::slave

    %% 连接关系
    MHA_manager --> Master1
    MHA_manager --> Master2
    Master1 --> Slave01_1
    Master1 --> Slave02_1
    Master2 --> Slave01_2
    Master2 --> Slave02_2

    %% 排版调整
    subgraph "MySQL-MHA高可用方案架构图"
        MHA_manager
        Master1
        Master2
        Slave01_1
        Slave02_1
        Slave01_2
        Slave02_2
    end

MySQL Master High Availability (MHA)是一套开源的 MySQL 高可用性解决方案,能够在主库发生故障时自动进行故障转移,将从库提升为新主库,从而保证 MySQL 服务的高可用性和可靠性。以下将从 MHA 的核心原理、架构组成、工作流程、部署实施以及与其他高可用方案的对比等方面进行全面解析。

MHA 概述与核心原理

MHA(Master High Availability)是专门为解决 MySQL 单点故障问题而设计的高可用解决方案。在传统主从复制架构中,当主库发生故障时,MHA 能够在10-30 秒内自动完成故障切换操作,最大程度保证数据一致性。

MHA 的核心原理基于以下几个关键机制:

  1. 故障检测机制:MHA Manager 通过心跳检测持续监控主库状态,一旦发现主库不可用(如 SSH 连接失败、MySQL 服务停止等),立即触发故障转移流程。

  2. 数据同步保障:MHA 会尝试从宕机的主服务器上保存二进制日志,并利用半同步复制技术确保数据尽可能少丢失。如果只有一个从库已接收最新二进制日志,MHA 会将这些日志应用到其他从库上。

  3. 自动拓扑重构:故障转移后,MHA 会自动重新配置所有从库的复制关系,使其指向新的主库,整个过程对应用程序完全透明。

MHA 架构与组件

MHA 架构由两个核心组件组成:MHA ManagerMHA Node

MHA Manager(管理节点)

MHA Manager 是控制中心,通常部署在独立的服务器上,也可以部署在某个从节点上。其主要功能包括:

  • 监控所有 MySQL 节点的健康状态
  • 协调故障转移过程
  • 管理虚拟 IP(VIP)的漂移
  • 提供管理工具如masterha_check_sshmasterha_check_repl

MHA Node(数据节点)

MHA Node 运行在每台 MySQL 服务器上(包括主库和从库),负责:

  • 保存和恢复主库的二进制日志
  • 应用差异的中继日志到其他从库
  • 清除中继日志而不阻塞 SQL 线程
  • 提供save_binary_logsapply_diff_relay_logs等工具

典型部署架构

一个标准的 MHA 环境至少需要三台服务器:一主两从。这种配置确保了即使一个从库也发生故障,系统仍然可以继续运行。

MHA 工作流程详解

MHA 的故障转移过程可以分为以下几个关键阶段:

  1. 故障检测阶段

    • MHA Manager 定期检查主节点的心跳信号
    • 如果发现异常,会尝试通过 SSH 连接等方式二次确认主节点状态
  2. 新主库选举阶段

    • 根据从库的复制位置(position/GTID)选择数据最接近原主库的从库
    • 如果数据一致,则按配置文件顺序选择
    • 可通过设置candidate_master=1强制指定优先候选主库
  3. 数据同步阶段

    • 从原主库保存未传输的二进制日志(如果可能)
    • 确保新主库应用所有可用的二进制日志
    • 将差异数据应用到其他从库
  4. VIP 漂移与拓扑重构阶段

    • 将虚拟 IP(VIP)切换到新主库
    • 重新配置所有从库指向新主库
    • 更新 MHA 配置文件,移除故障主库信息

整个故障转移过程对应用程序透明,应用程序只需连接 VIP 即可,无需修改连接配置。

MHA 部署实施指南

环境准备

部署 MHA 需要满足以下基本条件:

  • 至少三台服务器:一主两从+Manager 节点(可共用)
  • 所有节点间配置 SSH 免密登录
  • MySQL 版本一致(推荐 5.7 或更高)
  • 时间同步(如 NTP 服务)
  • 关闭防火墙和 SELinux
主机名IP 地址配置系统
mysql-master192.168.148.1704C8GCentOS-7.9
mysql-slave01192.168.148.1714C8GCentOS-7.9
mysql-slave02192.168.148.1724C8GCentOS-7.9

主库配置

修改配置文件my.cnf

ini
[mysqld]
server-id=1               			# 唯一ID,主库建议设为1
log-bin=master-bin 					# 指定生成二进制文件,可加目录
log-slave-updates 					# 指定开启slave角色的更新
innodb_flush_log_at_trx_commit=2
master_info_repository=table
relay_log_info_repository=TABLE

# 开启gtid
gtid_mode = ON
log_slave_updates
enforce_gtid_consistency

重启 MySQL 服务

bash
systemctl restart mysqld

创建同步专用账户

sql
-- 创建同步用户
CREATE USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY '123456';

-- 设置权限
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
# 修改连接验证规则
ALTER USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY '123456';

-- 刷新权限
FLUSH PRIVILEGES;

从库配置

修改配置文件my.cnf

ini
[mysqld]
server-id = 2               			# 唯一ID,必须与主库不同
log_bin = slave01-bin         			# 建议开启,便于级联复制
relay_log = mysql-relay01-bin 			# 中继日志文件前缀
read_only = ON              			# 从库设为只读(超级用户仍可写)

# 复制线程配置
slave_parallel_workers = 4  			# 并行复制线程数(5.6+)
slave_parallel_type = LOGICAL_CLOCK  	# 基于事务的并行复制(5.7+)
slave_preserve_commit_order = 1  		# 保持事务提交顺序(并行复制时)
# 复制可靠性配置
relay_log_info_repository = TABLE  		# 中继日志信息存表(mysql.slave_relay_log_info)
master_info_repository = TABLE     		# 主库信息存表(mysql.slave_master_info)
sync_relay_log = 1                 		# 每次事务提交都同步中继日志
sync_relay_log_info = 10000        		# 每10000次事务同步一次复制元数据
# 开启gtid
gtid_mode = ON
log_slave_updates
enforce_gtid_consistency

重启 MySQL 服务

bash
systemctl restart mysqld

配置复制链路

sql
CHANGE MASTER TO
MASTER_HOST='192.168.148.171',
MASTER_USER='repl',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='master-bin.000001',
MASTER_LOG_POS=157;

启动复制并检查状态

sql
START SLAVE;
SHOW SLAVE STATUS\G;

确认Slave_IO_RunningSlave_SQL_Running均为Yes

SSH 免密配置

所有节点都执行,因为需要所有节点都需要免密互相通信,不止单向通信

bash
# 生成密钥
ssh-keygen -t dsa -P "" -f ~/.ssh/id_dsa

# 将公钥分发到所有MySQL节点
ssh-copy-id -i ~/.ssh/id_dsa.pub root@192.168.148.170
ssh-copy-id -i ~/.ssh/id_dsa.pub root@192.168.148.171
ssh-copy-id -i ~/.ssh/id_dsa.pub root@192.168.148.172

MHA 组件部署

创建 mha 用户

sql
grant all privileges on *.* to mha@'%' identified by '123456';

部署 MHA Node

所有 MySQL 节点安装 MHA Node 组件

bash
yum -y install perl-DBD-MySQL
rpm -ivh mha4mysql-node-0.57-0.el7.noarch.rpm

部署 MHA Manager

MHA 管理组件尽量单独部署或者非 master 节点

安装依赖
bash
yum -y install epel* --nogpgcheck
yum -y install perl-Config-Tiny epel-release perl-Log-Dispatch perl-Parallel-ForkManager perl-Time-HiRes --nogpgcheck
rpm -ivh mha4mysql-manager-0.57-0.el7.noarch.rpm
创建配置文件
bash
# 创建日志目录
mkdir -p /var/log/mha/mha1

cat > /etc/mha/mha1.cnf << EOF
[server default]
manager_log=/var/log/mha/mha1/manager.log		#manager管理日志存放路径
manager_workdir=/var/log/mha/mha1				#manager管理日志的目录路径
master_binlog_dir=/data/mysql/data				#binlog日志的存放路径
user=mha										#管理账户
password=123456									#管理账户密码
ping_interval=2									#存活检查的间隔时间
repl_user=repl									#主从复制的授权账户
repl_password=123456							#主从复制的授权账户密码
ssh_user=root									#用于ssh连接的账户

[server1]
hostname=192.168.148.173
port=3306
candidate_master=1								# 标记为优先候选

[server2]
hostname=192.168.148.176
port=3306
candidate_master=1								# 标记为优先候选

[server3]
hostname=192.168.148.177
port=3306
no_master=1  # 禁止被选为主库

EOF
  • 建议为 1 个核心从库设置candidate_master=1
  • 为备份/异地从库设置no_master=1

ssh 检查检测

bash
masterha_check_ssh --conf=/etc/mha/mha1.cnf

主从复制检测

bash
masterha_check_repl --conf=/etc/mha/mha1.cnf

创建 service 管理文件

这里设置的自动重启服务,并且不会清除 amoeba 配置文件宕机的节点配置。增加--remove_dead_master_conf会自动删除 MHA 配置文件中宕机的配置信息,Restart可以设置不自动重启

bash
cat > /usr/lib/systemd/system/mha_manager.service << EOF
[Unit]
Description=MHA for MySQL Master Failover
After=mysqld.service network.target

[Service]
Type=simple
User=root
ExecStart=/usr/bin/masterha_manager \
    --conf=/etc/mha/mha1.cnf \
    --ignore_last_failover
Restart=always
RestartSec=15s
StartLimitInterval=0

StandardOutput=file:/var/log/mha/mha1/manager.log
StandardError=file:/var/log/mha/mha1/manager.log

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable mha_manager
systemctl start mha_manager

自动切换 master 的测试

bash
# 1.在mysql-slave节点查看masterIP地址
mysql -uroot -p'123456' -e'show slave status\G'
# 2.停止mysql-master节点停止MySQL服务
systemctl stop mysqld
# 3.检测mysql-master自动切换状态
mysql -uroot -p'123456' -e'show slave status\G'

MHA 故障还原

sql
-- 重新启动刚才停掉的mysql节点
-- 指定master节点地址
CHANGE MASTER TO MASTER_HOST='192.168.148.173', \
MASTER_PORT=3306,MASTER_AUTO_POSITION=1, MASTER_USER='repl', \
MASTER_PASSWORD='123456';
-- 开启同步
start slave;
-- 查看同步状态
show slave status\G

MHA 配置 VIP 漂移

设置 perl 脚本

需要提前安装 ifconfig:yum -y install net-tools

修改 my $vip 变量的值,设置虚拟 IP 地址

修改 ens32 网卡名

perl
#!/usr/bin/env perl

use strict;
use warnings FATAL => 'all';
use Getopt::Long;

# 声明变量
my (
    $command, $ssh_user, $orig_master_host,
    $orig_master_ip, $orig_master_port, $new_master_host,
    $new_master_ip, $new_master_port
);

# VIP 配置
my $vip = '192.168.148.160/24';  # 虚拟IP地址和子网掩码
my $key = '0';                  # 网络接口别名后缀
my $ssh_start_vip = "/sbin/ifconfig ens32:$key $vip";  # 启动VIP的命令
my $ssh_stop_vip = "/sbin/ifconfig ens32:$key down";   # 停止VIP的命令

# 解析命令行参数
GetOptions(
    'command=s'          => \$command,
    'ssh_user=s'         => \$ssh_user,
    'orig_master_host=s' => \$orig_master_host,
    'orig_master_ip=s'   => \$orig_master_ip,
    'orig_master_port=i' => \$orig_master_port,
    'new_master_host=s'  => \$new_master_host,
    'new_master_ip=s'    => \$new_master_ip,
    'new_master_port=i'  => \$new_master_port,
);

exit &main();

# 主函数
sub main {
    if ($command eq "stop" || $command eq "stopssh") {
        # 停止VIP的逻辑
        my $exit_code = 1;
        eval {
            print "Disabling the VIP on old master: $orig_master_host \n";
            &stop_vip();
            $exit_code = 0;
        };
        if ($@) {
            warn "Got Error: $@\n";
            exit $exit_code;
        }
        exit $exit_code;
    }
    elsif ($command eq "start") {
        # 启动VIP的逻辑
        my $exit_code = 10;
        eval {
            print "Enabling the VIP - $vip on the new master - $new_master_host \n";
            &start_vip();
            $exit_code = 0;
        };
        if ($@) {
            warn $@;
            exit $exit_code;
        }
        exit $exit_code;
    }
    elsif ($command eq "status") {
        # 检查脚本状态
        print "Checking the Status of the script..OK \n";
        `ssh $ssh_user\@$orig_master_host \" $ssh_start_vip \"`;
        exit 0;
    }
    else {
        # 显示用法信息
        &usage();
        exit 1;
    }
}

# 启动VIP的子程序
sub start_vip() {
    `ssh $ssh_user\@$new_master_host \" $ssh_start_vip \"`;
}

# 停止VIP的子程序
sub stop_vip() {
    `ssh $ssh_user\@$orig_master_host \" $ssh_stop_vip \"`;
}

# 显示使用帮助的子程序
sub usage {
    print "Usage: master_ip_failover --command=start|stop|stopssh|status "
        . "--orig_master_host=host --orig_master_ip=ip --orig_master_port=port "
        . "--new_master_host=host --new_master_ip=ip --new_master_port=port\n";
}

添加执行权限:chmod +x /usr/local/bin/master_ip_failover

修改 mha 配置文件

/etc/mha/mha1.cnf

ini
[server default]
master_ip_failover_script=/usr/local/bin/master_ip_failover #添加脚本位置

重启动 mha 管理端

bash
systemctl restart mha_manager

提示:

如果启动 mha 进程失败,需要进行 mha 的连接检测

masterha_check_ssh --conf=/etc/mha/mha1.cnf ssh 连接检测

masterha_check_repl --conf=/etc/mha/mha1.cnf 主从复制检测

VIP 漂移测试

bash
# 在master节点测试虚拟VIP是否生成
ip a
# 停止mysql服务
systemctl stop mysqld
# 到slave节点查看是否生成虚拟VIP

# mysql集群恢复
systemctl start mysqld
# 连接mysql开启同步
CHANGE MASTER TO MASTER_HOST='192.168.148.176', \
MASTER_PORT=3306,MASTER_AUTO_POSITION=1, MASTER_USER='repl', \
MASTER_PASSWORD='123456';

start slave;

MHA 的优势与局限性

优势

  1. 快速自动故障转移:能在秒级完成故障检测和转移,大幅减少停机时间
  2. 数据一致性保障:通过半同步复制和二进制日志应用,最大程度减少数据丢失
  3. 架构简单成本低:相比商业解决方案,MHA 开源免费且架构相对简单
  4. 对应用透明:通过 VIP 机制,应用无需修改连接配置
  5. 灵活的部署选项:Manager 可单独部署也可与从节点共用

局限性

  1. 主从架构限制:只支持一主多从,不支持多主写入
  2. 数据延迟风险:异步复制下可能存在数据不一致窗口期
  3. 环境要求较高:需要 SSH 免密登录、网络稳定等前提条件
  4. 维护复杂度:需要定期检查复制状态和监控系统
  5. 版本兼容性:不同 MySQL 版本可能需要特定 MHA 版本支持

MySQL-主主复制+Keepalived

MySQL 双主架构 中,仅部署 Keepalived 是一种 轻量级高可用方案,适用于中小规模业务。

架构概述

  • 两台 MySQL 互为主从(双主复制)。
  • Keepalived 管理 VIP,当主节点 MySQL 故障时,VIP 漂移到备节点,实现 秒级故障切换

环境准备

服务IP 地址配置系统
mysql-masterA+Keepalived192.168.148.1704C8GopenEuler 24.03(LTS)
mysql-masterB+Keepalived192.168.148.1784C8GopenEuler 24.03(LTS)
Keepalived+HAProxy192.168.148.1752C4GopenEuler 24.03(LTS)

MySQL 主主复制配置

my.cn 配置

sql
-- 节点1配置
[mysqld]
server-id = 1							# 保持和备节点不同
log-bin = mysql-bin
binlog-format = ROW
binlog-row-image = FULL
auto-increment-increment = 2
auto-increment-offset = 1				# 自增ID奇数和备节点不同
log-slave-updates = ON
sync_binlog = 1
innodb_flush_log_at_trx_commit = 1
slave_parallel_workers = 4
gtid_mode = ON
enforce_gtid_consistency = ON
relay_log_info_repository=TABLE  		# 使用表存储更可靠
master_info_repository=TABLE
sync_relay_log_info=1            		# 每次事务提交都同步

-- 节点2配置
[mysqld]
server-id = 2							# 和主节点不同
log-bin = mysql-bin
binlog-format = ROW
binlog-row-image = FULL
auto-increment-increment = 2
auto-increment-offset = 2				# 自增ID偶数和主库不同
log-slave-updates = ON
sync_binlog = 1
innodb_flush_log_at_trx_commit = 1
slave_parallel_workers = 4
gtid_mode = ON
enforce_gtid_consistency = ON
relay_log_info_repository=TABLE  		# 使用表存储更可靠
master_info_repository=TABLE
sync_relay_log_info=1            		# 每次事务提交都同步

关键配置说明表

配置项节点 1 值节点 2 值作用说明
server-id101102集群内唯一标识,必须不同
auto-increment-offset12双主架构下避免自增 ID 冲突
sync_binlog11确保 binlog 不丢失,但影响 IO 性能
gtid_modeONON启用 GTID 简化复制管理
slave_parallel_workers44提升从库应用日志速度

创建同步账号

sql
-- 在两个节点上分别执行(密码建议不同)
CREATE USER 'repl'@'192.168.148.%' IDENTIFIED BY '123456';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'192.168.148.%';
FLUSH PRIVILEGES;

建立复制关系

主节点 1 配置同步主节点 2

sql
CHANGE MASTER TO
MASTER_HOST='192.168.148.178',  -- 主节点2的IP
MASTER_USER='repl',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='mysql-bin.000001',  -- 主节点2的binlog文件名
MASTER_LOG_POS=154;                 -- 主节点2的binlog位置
START SLAVE;

-- 查看基本状态
SHOW SLAVE STATUS\G

-- 重点关注:
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 0
Last_IO_Error:
Last_SQL_Error:

主节点 2 配置主同步节点 1

sql
CHANGE MASTER TO
MASTER_HOST='192.168.148.170',  -- 主节点1的IP
MASTER_USER='repl',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='mysql-bin.000001',  -- 主节点1的binlog文件名
MASTER_LOG_POS=154;                 -- 主节点1的binlog位置
START SLAVE;

-- 查看基本状态
SHOW SLAVE STATUS\G

-- 重点关注:
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 0
Last_IO_Error:
Last_SQL_Error:
  1. Binlog 位置一致性

    • 初始配置时,需确保两个主节点的数据完全一致(可通过mysqldump备份恢复)。
    • 使用SHOW MASTER STATUS命令分别在两个节点上获取准确的FilePosition值。

Keepalived 虚拟 VIP 漂移

官网地址:https://www.keepalived.org/

Keepalived 核心作用

  1. 虚拟 IP(VIP)管理:提供192.168.148.160作为应用统一接入点
  2. 健康检测:通过自定义脚本监控 MySQL 服务状态
  3. 自动故障转移:主节点故障时 VIP 自动漂移到备用节点

服务搭建

安装服务(两个节点均需执行)

bash
# CentOS/RHEL
dnf install -y keepalived

# Ubuntu/Debian
apt-get install -y keepalived

# 验证安装
keepalived --version

环境准备

bash
# 创建用户
useradd -Ms /sbin/nologin keepalived_script

# 创建日志目录
mkdir -p /var/log/keepalived
touch /var/log/keepalived/mysql_health.log
touch /var/log/keepalived/keepalived_notify.log
chown -R keepalived_script:keepalived_script /var/log/keepalived

# 创建脚本目录
mkdir -p /opt/script
chown -R keepalived_script:keepalived_script /opt/script

# visudo 授权
keepalived_script ALL= NOPASSWD: /usr/local/mysql/bin/*,/usr/bin/*

主节点配置

yaml
sudo tee /etc/keepalived/keepalived.conf <<'EOF'
! Configuration File for keepalived

global_defs {
    router_id MYSQL_HA_MASTER  				# 唯一标识,建议用主机名
    enable_script_security
    script_user keepalived_script   		# 统一运行脚本用户
}

vrrp_script chk_mysql {
    script "/opt/script/chk_mysql.sh"  # 脚本路径
    interval 2       						# 检测间隔(秒)
    timeout 2        						# 脚本执行超时时间
    rise 2           						# 成功2次即认为恢复
    fall 2           						# 失败2次即认为宕机
    weight -20        						# 检测失败时优先级降低值
    user keepalived_script 					# 指定运行用户
}

vrrp_instance VI_1 {
    state BACKUP              				# 两台配置此处均是BACKUP,设为BACKUP将根据优先级决定主或从
    interface ens32           				# 绑定网卡(需根据实际调整)
    virtual_router_id 51      				# 虚拟路由ID(集群内唯一)
    priority 100              				# 初始优先级(0-255)
    advert_int 1              				# 心跳间隔(秒)
    nopreempt								# 非抢占模式
	
    authentication {
        auth_type PASS
        auth_pass 1111        				# 集群通信密码
    }
    # VIP配置
    virtual_ipaddress {
        192.168.148.160/24 dev ens32 label ens32:0  # VIP配置
    }
   
    track_script {
        chk_mysql            				# 绑定健康检查脚本
    }
    # 单播模式防ARP广播问题
    unicast_src_ip 192.168.148.170  		# 主节点IP
    unicast_peer {
        192.168.148.178             		# 备节点IP
    }
    # 状态通知脚本
    notify "/opt/script/notify.sh"
}
EOF

备节点配置

bash
sudo tee /etc/keepalived/keepalived.conf <<'EOF'
! Configuration File for keepalived

global_defs {
    router_id MYSQL_HA_BACKUP				# 唯一标识,建议用主机名
    enable_script_security
    script_user keepalived_script   		# 统一运行脚本用户
}

vrrp_script chk_mysql {
    script "/opt/script/chk_mysql.sh"  # 脚本路径
    interval 2       						# 检测间隔(秒)
    timeout 2        						# 脚本执行超时时间
    rise 2           						# 成功2次即认为恢复
    fall 2           						# 失败2次即认为宕机
    weight -20        						# 检测失败时优先级降低值
    user keepalived_script  				# 指定运行用户
}

vrrp_instance VI_1 {
    state BACKUP             				# 两台配置此处均是BACKUP,设为BACKUP将根据优先级决定主或从
    interface ens32							# 网卡名
    virtual_router_id 51     				# 必须与主节点相同
    priority 90              				# 优先级低于主节点
    advert_int 1
    nopreempt								# 非抢占模式
	
    authentication {
        auth_type PASS
        auth_pass 1111
    }
	# 虚拟VIP
    virtual_ipaddress {
        192.168.148.160/24 dev ens32 label ens32:0
    }
    
    track_script {
        chk_mysql
    }
    # 防止脑裂
    unicast_src_ip 192.168.148.178  # 主节点IP
    unicast_peer {
        192.168.148.170             # 备节点IP
    }
    # 状态通知脚本
    notify "/opt/script/notify.sh"
}

EOF

检测MySQL状态脚本

bash
sudo tee /opt/script/chk_mysql.sh <<'EOF'
#!/bin/bash
# MySQL健康检查脚本(带自定义日志)
# 返回值:0=健康,1=异常

# 配置区
LOG_DIR="/var/log/keepalived"
LOG_FILE="$LOG_DIR/mysql_health.log"
MYSQL_USER="keepalived_check"
MYSQL_PASS="Check@123"  # 建议使用加密密码
MYSQL_HOST="localhost"
MAX_LOG_SIZE=10  # 日志文件大小上限(MB)

# 自定义日志函数
log() {
    local timestamp=$(sudo date "+%Y-%m-%d %H:%M:%S.%3N")
    local status=${2:-"INFO"}
    echo "[$timestamp] [PID:$$] [$status] $1" >> "$LOG_FILE"
}

# 日志轮转检查
rotate_log() {
    [ -f "$LOG_FILE" ] || return
    local size=$(sudo du -m "$LOG_FILE" | sudo awk '{print $1}')
    [ "$size" -ge $MAX_LOG_SIZE ] && {
        sudo mv "$LOG_FILE" "${LOG_FILE}.$(sudo date +%Y%m%d%H%M)"
        log "触发日志轮转"
    }
}
check_service (){
   # 级别1:系统服务状态
    if ! systemctl is-active --quiet mysqld; then
        log "MySQL服务未运行" "ERROR"
        # sudo systemctl start mysqld || return 1
    fi
    # 级别2:TCP端口检测
    sudo timeout 1 bash -c "cat < /dev/null > /dev/tcp/127.0.0.1/3306" || {
        log "3306端口无响应" "ERROR"
        return 1
    }
    # 级别3:MySQL协议检测
    if ! /usr/local/mysql/bin/mysqladmin ping -h"$MYSQL_HOST" -u"$MYSQL_USER" -p"$MYSQL_PASS" --silent 2>/dev/null; then
        log "MySQL协议级检测失败" "ERROR"
        return 1
    fi
    # 级别4:复制状态检测
    repl_status=$(/usr/local/mysql/bin/mysql -h"$MYSQL_HOST" -u"$MYSQL_USER" -p"$MYSQL_PASS" -e "SHOW SLAVE STATUS\G")
    if [ -n "$repl_status" ]; then
        if ! sudo grep -q "Slave_IO_Running: Yes" <<< "$repl_status"; then
            log "Slave_IO_Running 线程停止" "ERROR"
            #return 1
        fi
        if ! sudo grep -q "Slave_SQL_Running: Yes" <<< "$repl_status"; then
            log "Slave_SQL_Running 线程停止" "ERROR"
            #mysql -h"$MYSQL_HOST" -u"$MYSQL_USER" -p"$MYSQL_PASS" -e "STOP SLAVE; START SLAVE" 
            #return $?
        fi
    fi
    return 0
}

# 主流程
rotate_log
# 主执行流程
if ! check_service; then
    # 连续3次失败后触发故障转移
    FAIL_COUNT=$(sudo grep -c "ERROR" $LOG_FILE | sudo tail -n1)
    [ ${FAIL_COUNT:-0} -ge 3 ] && {
        log "触发故障转移" "ACTION"
        sudo systemctl restart keepalived
    }
    exit 1
fi
exit 0
EOF

chmod +x /opt/script/chk_mysql.sh
chown keepalived_script:keepalived_script /opt/script/chk_mysql.sh

VIP状态切换通知脚本

bash
sudo tee /opt/script/notify.sh <<'EOF'
#!/bin/bash
# Keepalived
# 仅做日志记录和外部告警,不操作 HAProxy 或 VIP

STATE=$3                       # MASTER / BACKUP / FAULT
VIP="192.168.148.160"
PRIO=$4
IP=$(hostname -I | awk '{print $1}')
LOG_FILE="/var/log/keepalived/keepalived_notify.log"
TIMESTAMP=$(date "+%F %T")

log() {
    local LEVEL=${2:-INFO}
    echo "${TIMESTAMP} [${LEVEL}] [${IP}] [${STATE}] [${PRIO}] ${1}" >> "$LOG_FILE"
}

# 记录状态变化
case "$STATE" in
    MASTER)
        log "节点成为 MASTER,VIP 已绑定"
        ;;
    BACKUP)
        log "节点成为 BACKUP,VIP 已释放"
        ;;
    FAULT)
        log "节点进入 FAULT 状态"
        ;;
    STOP)
        log "节点进入 STOP 状态"
        ;;
    *)
        log "未知状态: $STATE" "ERROR"
        exit 1
        ;;
esac

# 可选:钉钉告警(取消注释并替换 token 即可)
# curl -s -H 'Content-Type: application/json' \
#      -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"[$HOST] Keepalived $STATE $VIP\"}}" \
#      https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN >/dev/null &

exit 0
EOF

sudo chmod +x /opt/script/notify.sh
sudo chown keepalived_script:keepalived_script /opt/script/notify.sh

MySQL-主主复制+Keepalived+HAProxy

本案例采用MYSQL双主+(Keepalived+HAProxy)集群架构。

架构概述

该方案通过以下组件实现高可用数据库服务:

  1. MySQL 主主复制:双主节点数据同步
  2. HAProxy:对数据库读写分离负载均衡
  3. Keepalived:虚拟 IP(VIP)漂移管理,它可以管理 haproxy,也可以单独管理 mysql

环境准备

服务IP 地址配置系统
mysql-masterA192.168.148.1704C8GopenEuler 24.03(LTS)
mysql-masterB192.168.148.1784C8GopenEuler 24.03(LTS)
Keepalived+HAProxy192.168.148.1792C4GopenEuler 24.03(LTS)
Keepalived+HAProxy192.168.148.1752C4GopenEuler 24.03(LTS)

MySQL 主主复制配置

my.cn 配置

sql
-- 节点1配置
[mysqld]
server-id = 1							# 保持和备节点不同
log-bin = mysql-bin
binlog-format = ROW
binlog-row-image = FULL
auto-increment-increment = 2
auto-increment-offset = 1				# 自增ID奇数和备节点不同
log-slave-updates = ON
sync_binlog = 1
innodb_flush_log_at_trx_commit = 1
slave_parallel_workers = 4
gtid_mode = ON
enforce_gtid_consistency = ON
relay_log_info_repository=TABLE  		# 使用表存储更可靠
master_info_repository=TABLE
sync_relay_log_info=1            		# 每次事务提交都同步

-- 节点2配置
[mysqld]
server-id = 2							# 和主节点不同
log-bin = mysql-bin
binlog-format = ROW
binlog-row-image = FULL
auto-increment-increment = 2
auto-increment-offset = 2				# 自增ID偶数和主库不同
log-slave-updates = ON
sync_binlog = 1
innodb_flush_log_at_trx_commit = 1
slave_parallel_workers = 4
gtid_mode = ON
enforce_gtid_consistency = ON
relay_log_info_repository=TABLE  		# 使用表存储更可靠
master_info_repository=TABLE
sync_relay_log_info=1            		# 每次事务提交都同步

关键配置说明表

配置项节点 1 值节点 2 值作用说明
server-id101102集群内唯一标识,必须不同
auto-increment-offset12双主架构下避免自增 ID 冲突
sync_binlog11确保 binlog 不丢失,但影响 IO 性能
gtid_modeONON启用 GTID 简化复制管理
slave_parallel_workers44提升从库应用日志速度

创建同步账号

sql
-- 在两个节点上分别执行(密码建议不同)
CREATE USER 'repl'@'192.168.148.%' IDENTIFIED BY '123456';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'192.168.148.%';
FLUSH PRIVILEGES;

建立复制关系

主节点 1 配置同步主节点 2

sql
CHANGE MASTER TO
MASTER_HOST='192.168.148.178',  -- 主节点2的IP
MASTER_USER='repl',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='mysql-bin.000001',  -- 主节点2的binlog文件名
MASTER_LOG_POS=154;                 -- 主节点2的binlog位置
START SLAVE;

-- 查看基本状态
SHOW SLAVE STATUS\G

-- 重点关注:
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 0
Last_IO_Error:
Last_SQL_Error:

主节点 2 配置主同步节点 1

sql
CHANGE MASTER TO
MASTER_HOST='192.168.148.170',  -- 主节点1的IP
MASTER_USER='repl',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='mysql-bin.000001',  -- 主节点1的binlog文件名
MASTER_LOG_POS=154;                 -- 主节点1的binlog位置
START SLAVE;

-- 查看基本状态
SHOW SLAVE STATUS\G

-- 重点关注:
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 0
Last_IO_Error:
Last_SQL_Error:
  1. Binlog 位置一致性

    • 初始配置时,需确保两个主节点的数据完全一致(可通过mysqldump备份恢复)。
    • 使用SHOW MASTER STATUS命令分别在两个节点上获取准确的FilePosition值。

HAProxy 故障自动转移恢复

官网:https://www.haproxy.com/

HAProxy提供高可用性、负载均衡以及基于TCP和HTTP的应用代理,支持虚拟主机,它是免费、快速并且可靠的一种负载均衡解决方案。适合处理高负载站点的七层数据请求。类似的代理服务可以屏蔽内部真实服务器,防止内部服务器遭受攻击。

  • 资源隔离:避免 MySQL 与 HAProxy 竞争 CPU/内存资源
  • 安全分层:前端流量不直接接触数据库服务器
  • 扩展灵活:可独立扩展 HAProxy 集群(如使用 Keepalived+多 HAProxy 节点)
  • 网络优化:可部署在 DMZ 区与内网数据库区之间

安装服务

bash
# CentOS/RHEL
sudo dnf install haproxy -y

# Ubuntu/Debian
sudo apt-get install haproxy -y

配置文件

基本配置

bash
sudo tee /etc/haproxy/haproxy.cfg <<'EOF'
# ======================
# HAProxy 2.9+ MySQL负载均衡配置模板
# ======================

# ======================
# Global 全局配置段
# ======================
global
    # 日志配置(需配合syslog使用)
    log 127.0.0.1 local0 info          			# 主日志(info级别)
    log 127.0.0.1 local1 notice        			# 重要通知日志

    # 安全隔离配置
    # chroot /var/lib/haproxy           		# 将进程锁定在指定目录(防目录遍历攻击)
    user haproxy                      			# 以低权限用户运行
    group haproxy                     			# 以低权限组运行
    daemon                            			# 以守护进程方式运行
    insecure-fork-wanted     		  			# 允许执行外部程序
    external-check					  			# 开启自定义检测脚本

# ======================
# Defaults 默认参数段
# ======================
defaults
    log global                        			# 继承全局日志配置
    mode tcp                          			# TCP四层代理模式(MySQL必须使用)
    option tcplog                     			# 启用TCP协议日志记录
    option dontlognull                			# 忽略空连接日志(防日志洪水攻击)
    retries 3         							# 后端失败后的重试次数(防雪崩),对非幂等操作(如POST)建议设为0

    # 超时控制(单位毫秒)
    timeout connect 3000ms            			# 连接后端超时(内网建议2000-3000ms)
    timeout client 50000ms            			# 客户端空闲超时(长连接场景需延长)
    timeout server 50000ms            			# 服务端响应超时(OLTP建议>30s)
    maxconn 3000         						# 每个frontend的独立连接数限制,应小于global的maxconn

# ======================
# Frontend 前端配置段
# ======================
frontend mysql_frontend
    bind *:3306                       			# 监听所有IP的3306端口
    mode tcp
    default_backend mysql_cluster      			# 默认转发到后端组

# ======================
# Backend 后端配置段
# ======================
backend mysql_cluster
    mode tcp                          			# TCP代理模式
    balance leastconn                 			# 轮询负载均衡算法
    #option tcp-check  				  			#开启tcp检查
    # option mysql-check user haproxy_check		# 默认mysql检查,需要创建mysql无密码的账号
    option external-check			  			# 启用外部检查
    external-check command "/etc/haproxy/check_mysql.sh"
    # 后端服务器定义
    server mysql_master_a 192.168.148.170:3306 inter 2000 check rise 2 fall 3
    server mysql_master_b 192.168.148.178:3306 inter 2000 check rise 2 fall 3 backup
    # backup参数是备用

# ======================
# stats 统计界面
# ======================
listen stats
    bind *:9020
    mode http
    stats enable
    stats hide-version
    stats uri /haproxy?stats
    stats realm "HAProxy Statistics"
    stats auth admin:admin  						# 建议修改为复杂密码
    stats refresh 30s
    stats admin if TRUE
    http-request deny if !{ src 192.168.148.0/24 }  # 限制管理界面访问IP
EOF

mysql 健康检查脚本

bash
# 安装mysql客户端(如果使用TCP检查不用安装)
dnf -y install mysql

# 创建检查脚本
sudo tee /opt/script/check_mysql.sh <<'EOF'
#!/bin/bash
# 使用HAProxy内置变量传递参数
IP="${3:-$1}"  # 兼容旧版HAProxy参数位置
PORT="${4:-$2}"
/bin/mysqladmin ping -u haproxy_check -p"123456" -h "$IP" -P "$PORT" --connect-timeout=1 &>/dev/null
exit $?
EOF
# 设置权限
chmod +x /opt/script/check_mysql.sh
chown haproxy:haproxy /opt/script/check_mysql.sh


# 创建HAProxy健康检查用户
CREATE USER 'haproxy_check'@'192.168.148.%' IDENTIFIED BY '123456';
GRANT ALL PRIVILEGES ON *.* TO 'haproxy_check'@'192.168.1.%' WITH GRANT OPTION;
FLUSH PRIVILEGES


## 默认mysql检查:option mysql-check user haproxy_check
CREATE USER 'haproxy_check'@'%' IDENTIFIED BY '';
FLUSH PRIVILEGES;

语法验证

bash
# 配置文件语法验证
haproxy -c -f /etc/haproxy/haproxy.cfg

# 日志
journalctl -u haproxy

启动服务

bash
# 开机自启并立即启动
systemctl enable haproxy --now
# 查看状态
systemctl status haproxy

Keepalived 虚拟 VIP 漂移

官网地址:https://www.keepalived.org/

Keepalived 核心作用

  1. 虚拟 IP(VIP)管理:提供192.168.148.160作为应用统一接入点
  2. 健康检测:通过自定义脚本监控 MySQL 服务状态
  3. 自动故障转移:主节点故障时 VIP 自动漂移到备用节点

服务搭建

安装服务(两个节点均需执行)

bash
# CentOS/RHEL
dnf install -y keepalived

# Ubuntu/Debian
apt-get install -y keepalived

# 验证安装
keepalived --version

环境准备

bash
useradd -Ms /sbin/nologin keepalived_script

touch /var/log/keepalived_notify.log
chown keepalived_script:keepalived_script /var/log/keepalived_notify.log

# 创建脚本目录
mkdir -p /opt/script
chown -R keepalived_script:keepalived_script /opt/script

# visudo 授权
keepalived_script ALL= NOPASSWD: /usr/bin/systemctl *,/usr/sbin/ip *

VIP 抢占模式

keepalive 通过组播,单播等方式(自定义),实现 keepalive 主备推选。工作模式分为抢占和非抢占(通过参数 nopreempt 来控制)。

主服务正常工作时,虚拟 IP 会在主上,备不提供服务,当主服务优先级低于备的时候,备会自动抢占虚拟 IP,这时,主不提供服务,备提供服务。也就是说,工作在抢占模式下,不分主备,只管优先级。

不管 keepalived.conf 里的 state 配置成 master 还是 backup,只看谁的 priority 优先级高(一般而言,state 为 MASTER 的优先级要高于 BACKUP)。priority 优先级高的那一个在故障恢复后,会自动将 VIP 资源再次抢占回来!!

当 state 状态都设置成 backup,如果不配置 nopreempt 参数,那么也是看 priority 优先级决定谁抢占 vip 资源,即也是抢占模式。

主节点配置

bash
sudo tee /etc/keepalived/keepalived.conf <<'EOF'
! Configuration File for keepalived

global_defs {
    router_id MYSQL_HA_MASTER  		# 唯一标识,建议用主机名
    enable_script_security
    script_user keepalived_script   # 统一运行脚本用户
}

# 检测haproxy
vrrp_script chk_haproxy {
    script "systemctl is-active --quiet haproxy" # 检查 HAProxy
    interval 2                    	# 检查间隔(秒)
    timeout 2                     	# 脚本超时时间
    weight -10                    	# 确保主节点失败后优先级低于备节点
    rise 2                        	# 成功2次即恢复
    fall 2                        	# 失败2次即判定宕机
    user keepalived_script
}

vrrp_instance VI_1 {
    state MASTER              		# 主节点
    interface ens32           		# 绑定网卡(需根据实际调整)
    virtual_router_id 51      		# 虚拟路由ID(集群内唯一)
    priority 100              		# 初始优先级(0-255)高于备节点
    advert_int 1              		# 心跳间隔(秒)
    preempt							# 开启抢占
    preempt_delay 5  				# 延迟5秒抢占,防止抖动
    
    authentication {
        auth_type PASS
        auth_pass 1111        		# 集群通信密码
    }

    virtual_ipaddress {
        192.168.148.160/24 dev ens32 label ens32:0  # VIP配置
    }
	# 仅检测HAProxy进程存活,不检测MySQL
    track_script {
        chk_haproxy
    }
    # 防止脑裂
    unicast_src_ip 192.168.148.179  # 本机IP
    unicast_peer {
        192.168.148.175             # 对端IP
    }
    notify "/opt/script/notify.sh"
}

EOF

备节点配置

bash
sudo tee /etc/keepalived/keepalived.conf <<'EOF'
! Configuration File for keepalived

global_defs {
    router_id MYSQL_HA_BACKUP  		# 唯一标识,建议用主机名
    enable_script_security
    script_user keepalived_script
}

# 检测haproxy
vrrp_script chk_haproxy {
    script "/usr/bin/systemctl is-active --quiet haproxy" # 检查 HAProxy
    interval 2                    	# 检查间隔(秒)
    timeout 2                     	# 脚本超时时间
    weight -10                    	# 确保主节点失败后优先级低于备节点
    rise 2                        	# 成功2次即恢复
    fall 2                        	# 失败2次即判定宕机
    user keepalived_script
}

vrrp_instance VI_1 {
    state BACKUP              		# 主节点
    interface ens32           		# 绑定网卡(需根据实际调整)
    virtual_router_id 51      		# 虚拟路由ID(集群内唯一)
    priority 99               		# 初始优先级(0-255)低于主节点
    advert_int 1              		# 心跳间隔(秒)
    preempt							# 开启抢占
    preempt_delay 5  				# 延迟5秒抢占,防止抖动 
    authentication {
        auth_type PASS
        auth_pass 1111        		# 集群通信密码
    }

    virtual_ipaddress {
        192.168.148.160/24 dev ens32 label ens32:0  # VIP配置
    }
	# 仅检测HAProxy进程存活,不检测MySQL
    track_script {
        chk_haproxy
    }
    # 防止脑裂
    unicast_src_ip 192.168.148.175  # 本机IP
    unicast_peer {
        192.168.148.179             # 对端IP
    }
    # 状态通知脚本
    notify "/opt/script/notify.sh"
}
EOF

状态通知脚本(双节点)

bash
sudo tee /opt/script/notify.sh <<'EOF'
#!/bin/bash
# Keepalived状态通知脚本

STATE=$3
VIP="192.168.148.160"
PRIO=$4
IP=$(hostname -I | awk '{print $1}')
INTERFACE="ens32"

# 日志文件
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S.%3N")

log() {
    local LEVEL=${2:-INFO}
    [ ! -e ${LOG_FILE} ] && touch ${LOG_FILE} 
    echo "${TIMESTAMP} [${LEVEL}] [${INTERFACE}:${IP}] [${STATE}] [${PRIO}] ${1}" >> ${LOG_FILE}
}
# 状态处理
case "$STATE" in
    MASTER)
        # 启动HAProxy(如果未运行)
        if ! sudo systemctl is-active --quiet haproxy; then
            sudo systemctl start haproxy && log "HAProxy已启动" || log "HAProxy启动失败" "ERROR"
        fi
        ;;
    BACKUP)
        # 先停止HAProxy(如果运行中)
        if sudo systemctl is-active --quiet haproxy; then
            sudo systemctl stop haproxy && log "HAProxy已停止" || log "HAProxy停止失败" "ERROR"
        fi
        ;;
    STOP)
        log "进入FAULT状态!紧急处理" "ERROR"
        
        # 先停止HAProxy(如果运行中)
        if sudo systemctl is-active --quiet haproxy; then
            sudo systemctl stop haproxy && log "HAProxy已停止" || log "HAProxy停止失败" "ERROR"
        fi
        
        # 移除VIP(如果存在)
        if sudo ip a show $INTERFACE | grep -q $VIP; then
            sudo ip addr del $VIP/24 dev $INTERFACE
            log "VIP已移除: $VIP"
        fi
        ;;
    *)
        log "未知状态: $STATE" "ERROR"
        exit 1
        ;;
esac
exit 0

EOF

chmod +x /opt/script/notify.sh
chown keepalived_script:keepalived_script /opt/script/notify.sh

VIP 禁止抢占模式

这种方式通过参数 nopreempt来控制。不管 priority 优先级,只要 MASTER 机器发生故障,VIP 资源就会被切换到 BACKUP 上。并且当 MASTER 机器恢复后,也不会去将 VIP 资源抢占回来,直至 BACKUP 机器发生故障时,才能自动切换回来。

nopreempt 这个参数只能用于 state 为 backup 的情况,所以在配置的时候要把 master 和 backup 的 state 都设置成 backup,这样才会实现 keepalived 的非抢占模式!

主节点配置

bash
sudo tee /etc/keepalived/keepalived.conf <<'EOF'
! Configuration File for keepalived

global_defs {
    router_id MYSQL_HA_MASTER  		# 唯一标识,建议用主机名
    enable_script_security
    script_user keepalived_script   # 统一运行脚本用户
}

# 检测haproxy
vrrp_script chk_haproxy {
    script "systemctl is-active --quiet haproxy" # 检查 HAProxy
    interval 2                    	# 检查间隔(秒)
    timeout 2                     	# 脚本超时时间
    weight -10                    	# 确保主节点失败后优先级低于备节点
    rise 2                        	# 成功2次即恢复
    fall 2                        	# 失败2次即判定宕机
    user keepalived_script
}

vrrp_instance VI_1 {
    state BACKUP              		# 两个节点保持一致
    interface ens32           		# 绑定网卡(需根据实际调整)
    virtual_router_id 51      		# 虚拟路由ID(集群内唯一)
    priority 100              		# 初始优先级(0-255)高于备节点
    advert_int 1              		# 心跳间隔(秒)
    nopreempt						# 关闭抢占
    
    authentication {
        auth_type PASS
        auth_pass 1111        		# 集群通信密码
    }

    virtual_ipaddress {
        192.168.148.160/24 dev ens32 label ens32:0  # VIP配置
    }
	# 仅检测HAProxy进程存活,不检测MySQL
    track_script {
        chk_haproxy
    }
    # 防止脑裂
    unicast_src_ip 192.168.148.179  # 本机IP
    unicast_peer {
        192.168.148.175             # 对端IP
    }
    notify "/opt/script/notify.sh"
}

EOF

备节点配置

bash
sudo tee /etc/keepalived/keepalived.conf <<'EOF'
! Configuration File for keepalived

global_defs {
    router_id MYSQL_HA_BACKUP  		# 唯一标识,建议用主机名
    enable_script_security
    script_user keepalived_script
}

# 检测haproxy
vrrp_script chk_haproxy {
    script "/usr/bin/systemctl is-active --quiet haproxy" # 检查 HAProxy
    interval 2                    	# 检查间隔(秒)
    timeout 2                     	# 脚本超时时间
    weight -10                    	# 确保主节点失败后优先级低于备节点
    rise 2                        	# 成功2次即恢复
    fall 2                        	# 失败2次即判定宕机
    user keepalived_script
}

vrrp_instance VI_1 {
    state BACKUP              		# 两个节点保持一致
    interface ens32           		# 绑定网卡(需根据实际调整)
    virtual_router_id 51      		# 虚拟路由ID(集群内唯一)
    priority 100               		# 初始优先级(0-255)低于主节点
    advert_int 1              		# 心跳间隔(秒)
    nopreempt						# 关闭抢占
    
    authentication {
        auth_type PASS
        auth_pass 1111        		# 集群通信密码
    }

    virtual_ipaddress {
        192.168.148.160/24 dev ens32 label ens32:0  # VIP配置
    }
	# 仅检测HAProxy进程存活,不检测MySQL
    track_script {
        chk_haproxy
    }
    # 防止脑裂
    unicast_src_ip 192.168.148.175  # 本机IP
    unicast_peer {
        192.168.148.179             # 对端IP
    }
    # 状态通知脚本
    notify "/opt/script/notify.sh"
}
EOF

状态通知脚本(双节点)

bash
sudo tee /opt/script/notify.sh <<'EOF'
#!/bin/bash
# Keepalived 非抢占模式通知脚本
# 仅做日志记录和外部告警,不操作 HAProxy 或 VIP

STATE=$3                       # MASTER / BACKUP / FAULT
VIP="192.168.148.160"
PRIO=$4
IP=$(hostname -I | awk '{print $1}')
LOG_FILE="/var/log/keepalived_notify.log"
TIMESTAMP=$(date "+%F %T")

log() {
    local LEVEL=${2:-INFO}
    echo "${TIMESTAMP} [${LEVEL}] [${IP}] [${STATE}] [${PRIO}] ${1}" >> "$LOG_FILE"
}

# 记录状态变化
case "$STATE" in
    MASTER)
        log "节点成为 MASTER,VIP 已绑定"
        ;;
    BACKUP)
        log "节点成为 BACKUP,VIP 已释放"
        ;;
    FAULT)
        log "节点进入 FAULT 状态"
        ;;
    *)
        log "未知状态: $STATE" "ERROR"
        exit 1
        ;;
esac

# 可选:钉钉告警(取消注释并替换 token 即可)
# curl -s -H 'Content-Type: application/json' \
#      -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"[$HOST] Keepalived $STATE $VIP\"}}" \
#      https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN >/dev/null &

exit 0
EOF

sudo chmod +x /opt/script/notify.sh
sudo chown keepalived_script:keepalived_script /opt/script/notify.sh

系统优化

系统参数调优(keepalived两节点)

bash
# 防止ARP冲突
echo "net.ipv4.conf.all.arp_ignore = 1" >> /etc/sysctl.conf
echo "net.ipv4.conf.all.arp_announce = 2" >> /etc/sysctl.conf
sysctl -p

故障转移测试

bash
# 1.通过VIP连接MySQL
mysql -uroot -p'123456' -h192.168.148.160 -e'select @@server_id'

# 2.到查询到的mysql服务器关闭mysql
systemctl stop mysqld

# 3.在查询检测故障迁移是否成功
mysql -uroot -p'123456' -h192.168.148.160 -e'select @@server_id'

# 4.将刚才停止的mysql服务重启
systemctl start mysqld

# 5.检测MySQL故障自动恢复
mysql -uroot -p'123456' -h192.168.148.160 -e'select @@server_id'

# 6.检测VIP漂移
systemctl stop keepalived

故障排查指南

VIP 无法漂移

bash
# 检查VRRP报文是否被拦截
tcpdump -i ens32 vrrp -n

# 验证健康检查脚本
mysql -h127.0.0.1 -uroot -pYourPassword -e 'SELECT 1'

脑裂问题处理

bash
# 强制释放VIP
ip addr del 192.168.148.160/24 dev ens32

# 检查哪个节点应持有VIP
ps aux | grep -E '[m]ysql|[k]eepalived'

状态监控指标

bash
# 检查Keepalived进程状态
keepalived -v

# 查看VIP绑定情况
ip -br addr show

异常关机(重置源数据)

bash
-- 1. 停止复制
STOP SLAVE;

-- 2. 重置所有复制信息(危险操作!确保主库数据完整)
RESET SLAVE ALL;

-- 3. 重新配置复制
CHANGE MASTER TO
  MASTER_HOST='192.168.148.170',
  MASTER_USER='repl',
  MASTER_PASSWORD='your_password',
  MASTER_LOG_FILE='mysql-bin.000001',
  MASTER_LOG_POS=1184;

-- 4. 启动复制
START SLAVE;

-- 5. 验证状态
SHOW SLAVE STATUS\G

MySQL-高可用:MyCAT

mermaid
flowchart TB
    %% 客户端层
    C1[客户端01]
    C2[客户端02]
    C3[客户端03]
    
    %% 代理层
    subgraph HAProxy+Keepalived集群
        H1[HAProxy\nkeepalived\n192.168.148.179]:::red
        H2[HAProxy\nkeepalived\n192.168.148.175]:::red
        VIP[VIP\n192.168.148.160\nVRRP]:::vip
    end
    
    %% MyCat层
    subgraph MyCat集群
        M1[MyCat\n192.168.148.150]:::blue
        M2[MyCat\n192.168.148.151]:::blue
        M3[MyCat\n192.168.148.152]:::blue
    end
    
    %% MySQL层
    subgraph MySQL主主复制
        DB1[MySQL Master\n192.168.148.170\n写]:::green
        DB2[MySQL Master\n192.168.148.178\n写]:::green
    end
    
    %% 连接关系
    C1 --> VIP
    C2 --> VIP
    C3 --> VIP
    VIP --> H1 & H2
    H1 --> M1 & M2 & M3
    H2 --> M1 & M2 & M3
    M1 -->|写| DB1
    M1 -->|读| DB2
    M2 -->|写| DB2
    M2 -->|读| DB1
    M3 -->|写| DB1
    M3 -->|读| DB2
    
    %% 样式定义
    classDef red fill:#ffcccc,stroke:#ff0000
    classDef blue fill:#cce5ff,stroke:#0066cc
    classDef green fill:#ccffcc,stroke:#009900
    classDef vip fill:#ffffcc,stroke:#ffcc00

架构概述

  1. 客户端层:通过VIP访问系统
  2. 代理层:HAProxy+keepalived实现MyCat负载均衡
  3. 数据层:MySQL主主复制+MyCat读写分离

环境准备

服务IP 地址配置系统
mysql-masterA192.168.148.1704C8GopenEuler 24.03(LTS)
mysql-masterB192.168.148.1784C8GopenEuler 24.03(LTS)
Keepalived+HAProxy192.168.148.1792C4GopenEuler 24.03(LTS)
Keepalived+HAProxy192.168.148.1752C4GopenEuler 24.03(LTS)
VIP192.168.148.160--
mycat-01192.168.148.1504C8GopenEuler 24.03(LTS)
mycat-02192.168.148.1514C8GopenEuler 24.03(LTS)
mycat-03192.168.148.1534C8GopenEuler 24.03(LTS)

MySQL 主主复制配置

my.cn 配置

sql
-- 节点1配置
[mysqld]
server-id = 1							# 保持和备节点不同
log-bin = mysql-bin
binlog-format = ROW
binlog-row-image = FULL
auto-increment-increment = 2
auto-increment-offset = 1				# 自增ID奇数和备节点不同
log-slave-updates = ON
sync_binlog = 1
innodb_flush_log_at_trx_commit = 1
slave_parallel_workers = 4
gtid_mode = ON
enforce_gtid_consistency = ON
relay_log_info_repository=TABLE  		# 使用表存储更可靠
master_info_repository=TABLE
sync_relay_log_info=1            		# 每次事务提交都同步

-- 节点2配置
[mysqld]
server-id = 2							# 和主节点不同
log-bin = mysql-bin
binlog-format = ROW
binlog-row-image = FULL
auto-increment-increment = 2
auto-increment-offset = 2				# 自增ID偶数和主库不同
log-slave-updates = ON
sync_binlog = 1
innodb_flush_log_at_trx_commit = 1
slave_parallel_workers = 4
gtid_mode = ON
enforce_gtid_consistency = ON
relay_log_info_repository=TABLE  		# 使用表存储更可靠
master_info_repository=TABLE
sync_relay_log_info=1            		# 每次事务提交都同步

关键配置说明表

配置项节点 1 值节点 2 值作用说明
server-id101102集群内唯一标识,必须不同
auto-increment-offset12双主架构下避免自增 ID 冲突
sync_binlog11确保 binlog 不丢失,但影响 IO 性能
gtid_modeONON启用 GTID 简化复制管理
slave_parallel_workers44提升从库应用日志速度

创建同步账号

sql
-- 在两个节点上分别执行(密码建议不同)
CREATE USER 'repl'@'192.168.148.%' IDENTIFIED BY '123456';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'192.168.148.%';
FLUSH PRIVILEGES;

建立复制关系

主节点 1 配置同步主节点 2

sql
CHANGE MASTER TO
MASTER_HOST='192.168.148.178',  -- 主节点2的IP
MASTER_USER='repl',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='mysql-bin.000001',  -- 主节点2的binlog文件名
MASTER_LOG_POS=154;                 -- 主节点2的binlog位置
START SLAVE;

-- 查看基本状态
SHOW SLAVE STATUS\G

-- 重点关注:
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 0
Last_IO_Error:
Last_SQL_Error:

主节点 2 配置主同步节点 1

sql
CHANGE MASTER TO
MASTER_HOST='192.168.148.170',  -- 主节点1的IP
MASTER_USER='repl',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='mysql-bin.000001',  -- 主节点1的binlog文件名
MASTER_LOG_POS=154;                 -- 主节点1的binlog位置
START SLAVE;

-- 查看基本状态
SHOW SLAVE STATUS\G

-- 重点关注:
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 0
Last_IO_Error:
Last_SQL_Error:
  1. Binlog 位置一致性

    • 初始配置时,需确保两个主节点的数据完全一致(可通过mysqldump备份恢复)。
    • 使用SHOW MASTER STATUS命令分别在两个节点上获取准确的FilePosition值。

MyCat配置

官网:http://www.mycat.org.cn/

Github:https://github.com/MyCATApache/Mycat-Server

mycat是阿里开源的一个分布式数据库中间层。MyCat的读写分离是基于后端MySQL集群的主从同步来实现的,而MyCat提供语句的分发功能。MyCat1.4开始支持MySQL主从复制状态绑定的读写分离机制,让读更加安全可靠。

安装JDK

bash
# 添加执行权限
chmod +x jdk-8u131-linux-x64.rpm
# rpm安装
rpm -ivh jdk-8u131-linux-x64.rpm

# 编辑环境变量配置文件
vim /etc/profile
 
 # 添加以下代码
export JAVA_HOME=/usr/java/jdk1.8.0_131
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib:$CLASSPATH
export JAVA_PATH=${JAVA_HOME}/bin:${JRE_HOME}/bin
export PATH=$PATH:${JAVA_PATH}

# 让profile立即生效
source /etc/profile

# 查看java版本
java -version

安装MyCAT

进入官网 http://www.mycat.org.cn/ 可以看到当前最新版本为 Mycat2,企业中一般不会选择最新版本,一是不够稳定,二是如果出现问题,解决方案不是太容易找到。

建议选择 1.6 版本下载和使用。

bash
tar -zxf Mycat-server-1.6.7.6-release-20220524173810-linux.tar.gz -C /usr/local/

进入到mycat解压后的目录下,可以看到有以下几个目录:

bin:主要存放二进制可执行文件

conf:mycat的配置文件

lib:mycat需要依赖的第三库文件

logs:打印mycat日志输出的地方

version.txt:存放mycat的版本信息

环境变量配置

将mycat的二进制可执行文件配置到 /etc/profile 的PATH环境变量中。

bash
# 环境变量配置文件
echo "export PATH=$PATH:/usr/local/mycat/bin" >> /etc/profile

# 使配置生效
source /etc/profile

修改配置信息

user用户

mycat的核心配置文件在 conf 目录下的 server.xml。

该配置文件中可以配置访问 mycat 的用户名、密码,管理端口及数据端口等信息。

xml
    <!-- 超级用户root配置 -->
    <user name="root" defaultAccount="true">
        <property name="password">123456</property>
        <property name="schemas">TESTDB</property>
        <property name="defaultSchema">TESTDB</property>
        <!-- 
            No MyCAT Database selected 错误前会尝试使用该schema
            如果为null,会报错 
        -->
        
        <!-- 表级DML权限设置(示例) -->
        <privileges check="false">
            <schema name="TESTDB" dml="0110">
                <table name="tb01" dml="0000"/>
                <table name="tb02" dml="1111"/>
            </schema>
        </privileges>
    </user>

    <!-- 普通用户user配置 -->
    <user name="user">
        <property name="password">user</property>
        <property name="schemas">TESTDB</property>
        <property name="readOnly">true</property>
        <property name="defaultSchema">TESTDB</property>
    </user>

schema节点

节点中各属性配置:

  • name 在 server.xml 中进行配置,应与该配置名称包括大小写在内,完全一致。
  • checkSQLschema 是否自动检查数据表的名字
  • sqlMaxLimit 一次最大读取的数据量
  • dataNode 数据的分片节点名称
xml
<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100" dataNode="dn1"> </schema>

dataNode节点

在该节点中主要配置的是分片信息。

节点中各属性配置:

  • name 应与 schema 节点中的 dataNode 属性值相同
  • dataHost 用于在 dataHost 节点中 name 属性的配置,以此关联;
  • database 在数据库中真实存在的database名称

在之前验证主从复制功能时,创建了数据库 test,这里也配置上该数据库.

xml
<dataNode name="dn1" dataHost="db_host" database="test" />

物理数据库配置

主要是配置的dataHost节点。

  • dataHost:节点中各属性配置:

  • name:与 dataNode节点中 dataHost 属性值相同;

  • maxCon:最大连接数

  • minCon:最小连接数

  • balance:读操作的负载均衡类型,取值范围主含义如下:

    取值含义
    0所有读操作都将发送到 writeHost 服务器中
    1所有读操作都将发送到 readHost 服务器中
    2读操作将随机发送到 writeHost 或 readHost 服务器中
  • writeType:写操作的类型,取值范围及含义如下: | 取值 | 含义 | | --- | --- | | 0 | 所有写操作都将发送到 writeHost 服务器中 | | 1 | 所有写操作都将发送到 readHost 服务器中 | | 2 | 写操作将随机发送到 writeHost 或 readHost 服务器中 |

  • dbType:数据库类型

  • dbDriver:数据库驱动

  • switchType:自动切换

    xml
    <dataHost name="db_host" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">

writeHost、readHost节点

节点中各属性配置:

  • host 服务名称,应有唯一性
  • url 数据库服务器的IP和端口,如果是采用容器启动的数据库,这里的IP和端口都需要配置为容器所在物理机的IP和端口
  • user 访问数据库的用户名
  • password 访问数据库的密码

使用之前配置的主从数据库,master 节点用于写,slave 节点用于读。

需要注意的是,readHost 节点被包裹在 writeHost 节点中。

xml
<writeHost host="hostM1" url="192.168.148.170:3306" user="root" password="123456">
    <readHost host="hostM2" url="192.168.148.178:3306" user="root" password="123456"/>
</writeHost>

完整配置结构

xml
<!-- conf/schema.xml -->
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100">
    <table name="tb_test" primaryKey="id" dataNode="dn1,dn2" rule="mod-long"/>
</schema>

<dataNode name="dn1" dataHost="dh1" database="db1"/>
<dataNode name="dn2" dataHost="dh2" database="db1"/>

<dataHost name="dh1" maxCon="1000" minCon="10" balance="1"
          writeType="0" dbType="mysql" dbDriver="native">
    <heartbeat>select user()</heartbeat>
    <writeHost host="hostM1" url="master1_ip:3306" user="root" password="123456">
        <readHost host="hostS1" url="master2_ip:3306" user="root" password="123456"/>
    </writeHost>
</dataHost>

<dataHost name="dh2" maxCon="1000" minCon="10" balance="1"
          writeType="0" dbType="mysql" dbDriver="native">
    <heartbeat>select user()</heartbeat>
    <writeHost host="hostM2" url="master2_ip:3306" user="root" password="123456">
        <readHost host="hostS2" url="master1_ip:3306" user="root" password="123456"/>
    </writeHost>
</dataHost>

重新启动mycat

修改完配置信息后,通过指令重启mycat服务。

bash
mycat restart

mycat服务的命令

mycat可以支持以下命令:

console:以交互控制台的方式启动mycat,当要停止服务时,可以直接输入 ctrl + c;

start :以后台服务的方式启动mycat;

stop :停止mycat服务;

restart:重新启动mycat服务;

status:查看mycat服务的状态;

dump:使用mycat备份数据

连接mycat

mycat提供了类似数据库的管理方式,可以使用 MySQL 命令连接mycat,登录到mycat的9066(默认管理端口)执行相应的SQL语句,也可以通过JDBC方式进行远程连接,来管理Mycat。

输入以下指令,即可进入到mycat控制台。

  • -u 访问mycat的用户名为,默认为 root;
  • -p 访问mycat的密码,默认使用 123456,均与mysql登录参数及含义相同;
  • -h 是启动mycat的服务器IP,这里不能使用locathost,必须是IP地址;
  • -P 是mycat的管理端口,默认为 9066;
  • -D mycat中的默认数据库 TESTDB。

mysql -uroot -p123456 -h 192.168.137.3 -P9066 -DTESTDB

该端口主要用于管理mycat,不能用于如增加、删除、修改、查询等操作

查看所有的可用命令

sql
show @@help;

查看所有的数据节点

sql
show @@datanode

HAProxy

安装服务

bash
# CentOS/RHEL
sudo dnf install haproxy -y

# Ubuntu/Debian
sudo apt-get install haproxy -y

HAProxy配置

ini
# /etc/haproxy/haproxy.cfg
frontend mysql_front
    bind *:3306
    mode tcp
    default_backend mysql_back

backend mysql_back
    mode tcp
    balance roundrobin
    server mycat1 192.168.148.150:8066 check
    server mycat2 192.168.148.151:8066 check
    server mycat3 192.168.148.152:8066 check

keepalived

安装服务

bash
# CentOS/RHEL
dnf install -y keepalived

# Ubuntu/Debian
apt-get install -y keepalived

# 验证安装
keepalived --version

keepalived配置

ini
# keepalived配置(/etc/keepalived/keepalived.conf)
vrrp_script chk_haproxy {
    script "killall -0 haproxy"
    interval 2
    weight 2
}

vrrp_instance VI_1 {
    state MASTER  # 另一台设为BACKUP
    interface eth0
    virtual_router_id 51
    priority 100  # 另一台设为90
    virtual_ipaddress {
        192.168.148.160/24
    }
    track_script {
        chk_haproxy
    }
}

启动服务

bash
# 启动顺序
1. 启动两台MySQL并验证主主复制
   SHOW SLAVE STATUS\G

2. 启动两台MyCat
   ./mycat start

3. 启动HAProxy+keepalived
   systemctl start haproxy
   systemctl start keepalived