基于Keepalived的MySQL双活架构实战指南(超详细)

一、前言

想象一下,电商大促正进行得如火如荼,用户们疯狂下单,支付请求如潮水般涌来。就在这时,数据库突然宕机,瞬间引发服务雪崩,订单系统无法处理新订单,支付系统也陷入瘫痪。用户们看到的是不断刷新却始终无法完成操作的页面,商家们则焦急地看着订单量停滞不前。这并非危言耸听,而是在容器化场景下高可用架构必须直面的挑战。在传统的主从架构中,一旦主数据库出现故障,从数据库需要一定时间来进行切换,这个过程中服务可能会中断,给用户带来极差的体验。而在容器环境中,由于容器的动态性和资源的共享性,传统主从架构暴露出漂移延迟高、状态同步难等痛点。​

在这种情况下,Keepalived 应运而生。Keepalived 的 “心跳监测 + VIP 漂移” 机制,就像一辆智能救护车,时刻守护着 MySQL 集群。它通过心跳监测,能够实时感知数据库的状态,一旦发现主数据库出现故障,就会立即触发 VIP 漂移,将服务快速切换到备用数据库上,为 MySQL 集群提供毫秒级故障转移能力,确保服务的连续性。接下来,让我们深入了解 Keepalived 的核心原理,看看它是如何实现这一神奇功能的。

二、Keepalived介绍及核心原理

2.1 Keepalived基础介绍

Keepalived 是一款专为 Linux 系统打造的轻量级高可用解决方案,旨在确保服务和网络的稳定运行。它的核心设计理念是通过虚拟路由冗余协议(VRRP),为系统提供高效、可靠的高可用性保障。

与 HeartBeat 相比,Keepalived 虽然在功能丰富度上稍逊一筹,但它以其简洁的部署和使用方式脱颖而出。用户只需一个配置文件,就能轻松完成所有必要的设置,大大降低了运维成本和复杂度。

Keepalived 最初是为 Linux 虚拟服务器(LVS)量身定制的,专注于监控集群系统中各个服务节点的运行状态。一旦检测到某个服务器节点出现故障,Keepalived 会迅速做出响应,自动将该节点从集群中移除,确保服务不受影响。而当故障节点恢复正常后,它又能自动将其重新纳入集群,整个过程无需人工干预,极大地提高了系统的容错能力和恢复效率。用户仅需专注于修复出现故障的节点,无需担心复杂的集群管理操作。

从本质上来说Keepalived是专为 LVS(Linux Virtual Server)负载均衡体系深度定制的高可用解决方案,其设计初衷是为 IPVS(IP Virtual Server)内核模块提供动态规则管理与节点冗余能力,且整个运行过程无需依赖共享存储或复杂的分布式协调组件

与 corosync 这类通用型高可用框架(可支持数据库、中间件等多类型资源)不同,Keepalived 的功能具有强业务针对性—— 它的核心使命是通过调用ipvsadm命令动态生成 IPVS 规则,将用户请求的虚拟服务地址(如 VIP)智能导向后端可用的 LVS 节点,最终实现LVS 负载均衡层的高可用性闭环

随着技术的不断发展,Keepalived 集成了 VRRP(VritrualRouterRedundancyProtocol,虚拟路由冗余协议)功能。VRRP 作为一种专门用于解决静态路由单点故障问题的协议,通过在多个路由器之间建立虚拟路由器,实现了网络的不间断稳定运行。这使得 Keepalived 不仅具备强大的服务器状态检测和故障隔离能力,还能构建高可用集群,为企业级应用提供坚实的基础。我们简单介绍一下这个协议~

2.2 VRRP 协议介绍

众所周知,在我们实际网络环境中,主机间通信通常依赖静态路由(默认网关)配置,但若承担转发功能的路由器出现故障,将直接导致通信中断,形成单点瓶颈问题。为解决这一痛点,VRRP 协议(虚拟路由冗余协议)应运而生。

VRRP(Virtual Router Redundancy Protocol)作为 Keepalived 的核心协议,本质上是一种分布式节点状态协调机制,通过模拟 "虚拟路由器" 概念实现 IP 地址的高可用。VRRP 协议采用主备容错模式,其核心目标是在主机下一跳路由故障时,通过另一台路由器无缝接管服务,确保网络故障时设备切换对主机数据通信透明无感知。该协议通过将两台或多台物理路由器虚拟为一个逻辑实体 ——虚拟路由器,以虚拟 IP(支持一个或多个)对外提供服务,内部由多台物理路由器协同工作。

在一个 VRRP 组内,节点通过周期性发送VRRP 通告报文(默认间隔 1 秒)声明自身状态,竞争成为 "Master" 角色(唯一持有 VIP 的节点),其余节点为 "BACKUP" 并处于热备状态。所有节点平等参与竞选,通过优先级(0-255,默认 100)IP 地址拥有权(配置真实 IP 与 VIP 一致的节点优先级自动 + 200)决定角色归属,避免单点集权风险。

VRRP可以将两台或多台物理路由器设备虚拟成一个虚拟路由器,这个虚拟路由器通过虚拟IP(一个或多个)对外提供服务,而在虚拟路由器内部是多个物理路由器协同工作,VRRP的所有角色如下:

  • 虚拟路由器:VRRP组中所有的路由器,拥有虚拟的IP+MAC(00-00-5e-00-01-VRID)地址
  • 主路由器(master):虚拟路由器内部通常只有一台物理路由器对外提供服务,主路由器是由选举算法产生,对外提供各种网络功能
  • 备份路由器(backup):VRRP组中除主路由器之外的所有路由器,不对外提供任何服务,只接受主路由的通告,当主路由器挂掉之后,重新进行选举算法接替master路由器。

master路由器由选举算法产生,它拥有对外服务的VIP,提供各种网络服务,如ARP请求、数据转发、ICMP等等,而backup路由器不拥有VIP,也不对外提供网络服务

当master发生故障时,backup将重新进行选举,产生一个新的master继续对外提供服务。

2.3 VRRP工作模式

VRRP 协议通过状态机驱动的节点角色管理与优先级选举机制,实现虚拟路由的高可用性。其核心逻辑围绕状态转换选举策略展开,具体工作模式如下:

VRRP的节点状态转换机制

VRRP 节点状态遵循严格的状态机逻辑,包含三个核心阶段:

  1. 初始状态(Initialize)
    节点启动后读取配置信息,完成 VRRP 组初始化前的参数加载,此时不参与路由竞争。
  2. BACKUP 状态
    • 持续监听 Master 节点发送的 VRRP 通告报文(默认每秒 1 次),实时监测主节点存活状态。
    • 若在 ** 抢占延迟(Skew Time)** 内未收到有效报文(如 Master 故障),触发竞选流程。
      • 计算公式:Skew Time = (255 - 节点优先级) / 256,优先级越高(数值越大,范围 0-255),等待时间越短,确保高优先级节点优先接管。
  3. Master 状态
  • 周期性广播 VRRP 报文,维护主节点身份,同时负责响应虚拟 IP(VIP)的网络请求(如 ARP 解析、数据转发)。
  • 实时监控自身及后端服务状态,若检测到故障或发现新加入节点优先级更高,主动释放 VIP 并退化为 BACKUP 状态。

VRRP的选举机制与优先级策略 

优先级是 VRRP 竞选的核心指标,通过数值大小决定角色归属,支持静态配置与动态调整。其基础规则如下:

  • 优先级数值越大(最大 255),竞争力越强,默认值为 100。
  • 典型配置场景
    • 主备集群:固定 Master 优先级为 150,BACKUP 为 100,确保故障时高优先级节点优先接管。
    • 多活集群:结合weight参数动态调整优先级,例如节点负载超过 80% 时自动降低优先级,触发 VIP 漂移至低负载节点。

VRRP的选举机制包含抢占模式与非抢占模式:

抢占模式(默认)

  • 开启preempt后,高优先级节点恢复或新加入时,立即抢占 Master 角色(无论原 Master 是否存活),适用于严格主从控制场景(如数据库主从切换)。
  • 示例:Server1(优先级 150)与 Server2(优先级 100)组成集群,Server1 故障后 Server2 接管;Server1 恢复后,因优先级更高,立即夺回 Master 角色,Server2 退化为 BACKUP。

非抢占模式

  • 配置nopreempt后,即使高优先级节点恢复或加入,也不会主动抢占当前 Master 角色,仅在原 Master 故障时通过选举接管。
  • 示例:Server1(优先级 150)与 Server2(优先级 100)均配置非抢占模式,Server1 初始为 Master;若 Server1 故障后 Server2 接管,当 Server1 恢复时,保持 BACKUP 状态,由 Server2 继续提供服务。

抢占模式下,一旦有优先级高的路由器加入,立即成为Master,默认。而非抢占模式下,只要Master不挂掉,优先级高的路由器只能等待。

简单点说抢占模式就是,当master宕机后,backup 接管服务。后续当master恢复后,vip漂移到master上,master重新接管服务,多了一次多余的vip切换,而在实际生产中是不需要这样。

实际生产中是,当原先的master恢复后,状态变为backup,不接管服务,这是非抢占模式。

接下来分4种情况说明:

  • 俩台都为master时,比如server1的优先级大于server2,keepalived启动后server1获得master,server2自动降级为backup。此时server1宕机的话,server2接替 服务,当server1恢复后,server1又变为master,重新接管服务,server2变为backup。属于抢占式。
  • server1为master,server2位backup,且master优先级大于backup。keepalived启动后server1获得master,server2为backup。当server1宕机后, server2接管服务。当server1恢复后,server1重新接管服务变为master,而server2变为backup。属于抢占式
  • server1为master,server2位backup,且master优先级低于backup。keepalived启动后server2获得master,server1为backup。当server2宕机后, server1接管服务。此时server2恢复后抢占服务,获得master,server1降级将为backup。属于抢占式

以上3种,只要级别高就会获取master,与state状态是无关的

  • server1和server2都为backup。我们要注意启动server服务的启动顺序,先启动的升级为master,与优先级无关。且配置nopreempt

    • 比如server1获得master权限,server2为backup。此时server1宕机后,server2接管服务升级为master。当server1恢复后权限将为backup,不会争抢 server2的master权限,server2将会继续master权限。属于非抢占式
    • 重点:第4种非抢占式俩节点state必须为bakcup,且必须配置nopreempt

需要注意的是,这样配置后,我们要注意启动服务的顺序,优先启动的获取master权限,与优先级没有关系了。

整体上VRRP的工作过程如下:

  1. 虚拟路由器中的路由器根据优先级选举出Master,Master路由器通过发送免费ARP报文,将自己的虚拟MAC地址通告给与它连接的设备。
  2. Master路由器周期性发送VRRP报文,以公布自己的配置信息(优先级等)和工作状态
  3. 如果Master故障,虚拟路由器中的Backup路由器将根据优先级重新选举新的Master
  4. 虚拟路由器状态切换时,Master路由器由一台设备切换会另外一台设备,新的Master路由器只是简单的发送一个携带虚拟MAC地址和虚拟IP的免费ARP报文,这样就可以更新其他设备中缓存的ARP信息
  5. Backup路由器的优先级高于Master时,由Backup的工作方式(抢占式或者非抢占式)决定是否重新选举Master。

2.4 Keepalived的工作原理

 keepalived工作在TCP/IP参考模型的第三、四和第五层,也就是网络层、传输层个和应用层

  • 网络层:通过ICMP协议向集群每个节点发送一个ICMP数据包(类似于ping功能),如果某个节点没有返回响应数据包,那么认定此节点发生了故障,Keepalived将报告此节点失效,并从集群中剔除故障节点
  • 传输层:通过TCP协议的端口连接和扫描技术来判断集群节点是否正常,keepalived一旦在传输层探测到这些端口没有响应数据返回,就认为这些端口所对应的节点发生故障,从集群中剔除故障节点
  • 应用层:用户可以通过编写程序脚本来运行keepalived,keepalived根据脚本来检测各种程序或者服务是否正常,如果检测到有故障,则把对应的服务从服务器中删除

基于Keepalived的MySQL双活架构实战指南(超详细)_第1张图片

结合keepalived的组件架构图我们可以将整个体系结构分层用户层和内核层:

  • Scheduler I/O Multiplexer:I/O复用分发调用器,负责安排Keepalived所有的内部的任务请求
  • Memory Management:内存管理机制,提供了访问内存的一下通用方法Keepalived
  • Control Plane:控制面板,实现对配置文件的编译和解析,Keepalived的配置文件解析比较特殊,它并不是一次解析所有模块的配置,而是只有在用到某模块时才解析相应的配置
  • Core components:Keepalived的核心组件,包含了一系列功能模块,主要有watch dog、Checkers、VRRP Stack、IPVS wrapper、Netlink Reflector
  1. watch dog:这个是Linux系统内核的一个模块,是一个极为简单又非常有效的检测工具,针对被监视目标设置一个计数器和阈值,watch dog会自己增加此计数值,然后等待被监视目标周期性的重置该数值,一旦被监控目标发生错误,就无法重置该数值,watch dog就会检测到。Keepalived是通过它来监控Checkers和VRRP进程。因为主进程并不负责具体工作,具体工作都是子进程完成的。如果子进程挂了,那Keepalived就不完整了,所以这2个子进程会定期的向主进程打开的一个内部Unix Socket文件写心跳信息。如果有某个子进程不写信息了,它就会重启子进程,主进程就是让WatchDog来监控子进程的。
  2. Checkers:实现对服务器运行状态检测和故障隔离
  3. VRRP Stack:实现HA集群中失败切换功能,通过VRRP功能再结合LVS负载均衡软件即可部署一个高性能的负载均衡集群
  4. IPVS wrapper:实现IPVS功能,该模块可以将设置好的IPVS规则发送到内核空间并提交给IPVS模块,最终实现负载均衡功能
  5. Netlink Reflector:实现VIP的设置和切换

Keepalived启动后以后会有一个主进程Master,它会生成还有2个子进程,一个是VRRP Stack负责VRRP(也就是VRRP协议的实现)、一个是Checkers负责IPVS的后端的应用服务器的健康检查,当检测失败就会调用IPVS规则删除后端服务器的IP地址,检测成功了再加回来。当检测后端有失败的情况可以使用SMTP通知管理员。另外VRRP如果检测到另外一个Keepalive失败也可以通过SMTP通知管理员。

Control Plane就是主进程,主进程的功能是分析配置文件,读取、配置和生效配置文件,指挥那2个子进程工作。

从服务的双活架构整体而言,Keepalived 更多是以服务形式部署在多台 LVS 主机节点上,构建主备架构,其中工作角色和核心机制与流程包括以下几点:

角色分工

  • Master 节点:当前活跃的流量入口,持有虚拟 IP(VIP)并负责将用户请求按 IPVS 规则分发到后端服务器。
  • Backup 节点:处于热备状态的冗余节点,实时同步 Master 节点的 IPVS 规则,但不处理用户流量。

心跳通信

基于VRRP 协议(虚拟路由冗余协议),Master 节点通过组播方式定期向 Backup 节点发送心跳通告(默认每秒 1 次),宣告自身存活状态。

故障切换流程

当 Backup 节点在连续多个心跳周期(默认 3 秒)未收到 Master 通告时,会触发以下动作:
① VIP 接管:通过内核机制将虚拟 IP 绑定到自身网卡,成为新的流量入口;
② 规则同步:复制并激活 Master 节点的 IPVS 规则,确保流量分发策略一致;
③ 服务接管:无缝承接用户请求,替代原 Master 节点提供负载均衡服务。

2.5 Keepalived的两种工作模式 

Keepalived 主要有两种工作模式:

VRRP 模式(基础高可用)

用于实现服务器的主备切换,确保服务的高可用性,不涉及负载均衡。此时 Keepalived 的作用是监控节点状态,当主节点故障时,备节点接管虚拟 IP(VIP),但不会处理流量转发,可以不需要定义 virtual_server。

在 VRRP 模式中,若需自定义健康检查(如监控应用进程),可通过 vrrp_script 定义脚本,并关联到 vrrp_instance 中(无需 virtual_server)。

  • 配置核心:通过 vrrp_instance 定义实例,指定主备节点的优先级、接口等,无需定义 virtual_server
  • 典型场景:两台服务器组成主备集群,当主节点故障时,备节点接管 IP 地址和服务。 
global_defs {
   router_id LVS_DEVEL
}

vrrp_instance VI_1 {
    state MASTER          # 主节点为 MASTER,备节点为 BACKUP
    interface eth0        # 绑定虚拟 IP 的物理接口
    virtual_router_id 51  # VRRP 组 ID(主备需一致)
    priority 100          # 主节点优先级(备节点需低于主节点,如 90)
    advert_int 1          # 心跳间隔(秒)
    authentication {
        auth_type PASS
        auth_pass 1111    # 认证密码(主备需一致)
    }
    virtual_ipaddress {
        192.168.1.100     # 虚拟 IP(VIP)
    }
}

LVS+VRRP 模式(负载均衡 + 高可用)

结合 LVS(Linux Virtual Server)实现负载均衡,同时通过 VRRP 实现 LVS 节点的高可用,必须定义 virtual_server,用于配置负载均衡规则。在 LVS 模式中,健康检查通常通过 virtual_server 下的 TCP_CHECK/HTTP_GET 等模块实现。

  • 配置核心
    • 通过 vrrp_instance 定义 VRRP 实例(主备逻辑)。
    • 通过 virtual_server 定义虚拟服务(负载均衡规则,如 IP、端口、转发策略等)。
  • 典型场景:多台服务器组成负载均衡集群,通过 virtual_server 分配流量到后端真实服务器(Real Server)。
global_defs {
   router_id LVS_DEVEL
}

vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.1.100
    }
}

# 定义虚拟服务(负载均衡规则)
virtual_server 192.168.1.100 80 {
    delay_loop 6          # 健康检查间隔(秒)
    lb_algo rr           # 负载均衡算法(轮询)
    lb_kind NAT           # 负载均衡模式(NAT/DR/TUN)
    protocol TCP          # 协议类型

    # 后端真实服务器(Real Server)
    real_server 192.168.1.101 80 {
        weight 1          # 服务器权重
        TCP_CHECK {       # 健康检查方式(TCP 端口检查)
            connect_port 80
            connect_timeout 3
            retry 3
            delay_before_retry 3
        }
    }

    real_server 192.168.1.102 80 {
        weight 1
        TCP_CHECK {
            connect_port 80
            connect_timeout 3
            retry 3
            delay_before_retry 3
        }
    }
}

三、Keepalived搭建MySQL双活架构

在双节点服务器(Node1: 192.168.1.200,Node2: 192.168.1.201)上部署 Keepalived,实现 MySQL 主从集群的高可用。当主节点 MySQL 故障时,VIP 自动漂移至从节点,确保业务连接不中断。

Step1:安装Keepalived

有网络条件的情况下大家可以直接通过yum或者apt-get在服务器上进行安装,离线情况可在keepalived官网下载最新安装包,但是速度受网络影响会比较慢,当然这时候也可以考虑一下国内镜像源进行下载,比如keepalived华为云镜像站,但是版本会更新不及时。这里我们使用离线安装,将下载好的keepalived-2.3.3.tar.gz上传至服务器进行安装:

root@master01:/tmp# ls -l
总计 1208
-rw-r--r-- 1 root root 1236713  4月 21 16:10 keepalived-2.3.3.tar.gz

然后安装一下keepalived安装必要的环境依赖。

Ubuntu22.04参考

# 安装基础编译工具
root@master01:/tmp/keepalived-2.3.3# apt update && sudo apt install -y build-essential autoconf automake pkg-config
  •   build-essential          # 包含gcc、make等基础工具
  •   autoconf automake        # 自动配置工具链
  •   pkg-config               # 库依赖检测工具 
# 核心依赖库
root@master01:/tmp/keepalived-2.3.3# apt install -y libssl-dev libnl-3-dev libnl-genl-3-dev libpcre2-dev libnftnl-dev libmnl-dev
  •   libssl-dev               # SSL/TLS加密库(用于VRRP认证)
  •   libnl-3-dev libnl-genl-3-dev # Netlink库(VRRP协议核心)
  •   libpcre2-dev             # PCRE正则表达式库(配置文件解析)
  •   libnftnl-dev libmnl-dev  # NFTables支持(高级防火墙规则)
# 安装扩展功能支持
root@master01:/tmp/keepalived-2.3.3# apt install -y libsystemd-dev libkmod-dev  libmagic-dev
  •   libsystemd-dev           # systemd服务集成
  •   libkmod-dev              # 内核模块加载支持
  •   libmagic-dev             # 文件类型检测(健康检查脚本)

CentOS参考

注意:keepalived-2.3.3版的发布声明文档中,官方已经明确申明,Keepalived 2.3.3版本不再支持CentOS 7系统。

启用扩展源: 

# 安装EPEL源以获取额外包
[root@node01 tmp]# yum install -y epel-release
已加载插件:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
 * base: mirrors.aliyun.com
 * extras: mirrors.aliyun.com
 * updates: mirrors.aliyun.com
正在解决依赖关系
--> 正在检查事务
---> 软件包 epel-release.noarch.0.7-11 将被 安装
--> 解决依赖关系完成

依赖关系解决

====================================================================================================================================================================================
 Package                                         架构                                      版本                                     源                                         大小
====================================================================================================================================================================================
正在安装:
 epel-release                                    noarch                                    7-11                                     extras                                     15 k

事务概要
====================================================================================================================================================================================
安装  1 软件包

总下载量:15 k
安装大小:24 k
Downloading packages:
epel-release-7-11.noarch.rpm                                                                                                                                 |  15 kB  00:00:05     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  正在安装    : epel-release-7-11.noarch                                                                                                                                        1/1 
  验证中      : epel-release-7-11.noarch                                                                                                                                        1/1 

已安装:
  epel-release.noarch 0:7-11                                                                                                                                                        

完毕!

安装基础编译工具:

[root@node01 tmp]# yum install -y gcc gcc-c++ make autoconf automake libtool
  •   gcc gcc-c++ make               # 编译工具链
  •   autoconf automake libtool     # 自动配置工具

安装核心依赖库和开发包:

[root@node01 tmp]# yum install -y openssl-devel libnl3-devel pcre2-devel libnftnl-devel libmnl-devel kernel-devel kernel-headers
  • openssl-devel                  # SSL/TLS加密库
  • libnl3-devel                   # Netlink库
  • pcre2-devel                    # PCRE正则表达式库
  • libnftnl-devel libmnl-devel    # NFTables支持  
  • kernel-devel                    #开发包
  • kernel-headers                #开发包

安装扩展功能:

[root@node01 tmp]# yum install -y systemd-devel kmod-devel file-devel 
  •   systemd-devel                  # systemd服务集成
  •   kmod-devel                     # 内核模块加载支持
  •   file-devel                     # 文件类型检测 

然后将keepalived的安装包进行解压编译安装:

root@master01:/tmp# tar -zxf keepalived-2.3.3.tar.gz 
root@master01:/tmp# cd keepalived-2.3.3/
# 指定安装路径并支持systemd管理
root@master01:/tmp/keepalived-2.3.3# ./configure --prefix=/opt/keepalived --with-systemd
checking for a BSD-compatible install... /usr/bin/install -c
……
# 编译安装
root@master01:/tmp/keepalived-2.3.3# make install
# 验证安装情况
root@master01:/tmp/keepalived-2.3.3# /opt/keepalived/sbin/keepalived --version
Keepalived v2.3.3 (03/30,2025)

Copyright(C) 2001-2025 Alexandre Cassen, 

Built with kernel headers for Linux 5.15.168
Running on Linux 6.8.0-52-generic #53~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Wed Jan 15 19:18:46 UTC 2
Distro: Ubuntu 22.04.4 LTS

configure options: --prefix=/opt/keepalived

Config options:  NFTABLES LVS VRRP VRRP_AUTH VRRP_VMAC OLD_CHKSUM_COMPAT IPROUTE_ETC_DIR=/etc/iproute2 IPROUTE_USR_DIR=/etc/iproute2 INIT=systemd SYSTEMD_NOTIFY

System options:  VSYSLOG MEMFD_CREATE IPV6_FREEBIND IPV6_MULTICAST_ALL LIBKMOD IPV4_DEVCONF LIBNL3 RTA_ENCAP RTA_EXPIRES RTA_NEWDST RTA_PREF FRA_SUPPRESS_PREFIXLEN FRA_SUPPRESS_IFGROUP FRA_TUN_ID RTAX_CC_ALGO RTAX_QUICKACK RTEXT_FILTER_SKIP_STATS FRA_L3MDEV FRA_UID_RANGE RTAX_FASTOPEN_NO_COOKIE RTA_VIA FRA_PROTOCOL FRA_IP_PROTO FRA_SPORT_RANGE FRA_DPORT_RANGE RTA_TTL_PROPAGATE IFA_FLAGS F_OFD_SETLK LWTUNNEL_ENCAP_MPLS LWTUNNEL_ENCAP_ILA NET_LINUX_IF_H_COLLISION LIBIPTC_LINUX_NET_IF_H_COLLISION LIBIPVS_NETLINK IPVS_DEST_ATTR_ADDR_FAMILY IPVS_SYNCD_ATTRIBUTES IPVS_64BIT_STATS IPVS_TUN_TYPE IPVS_TUN_CSUM IPVS_TUN_GRE VRRP_IPVLAN IFLA_LINK_NETNSID GLOB_BRACE GLOB_ALTDIRFUNC INET6_ADDR_GEN_MODE VRF SO_MARK

安装完成后为了方便查看keepalived的运行问题跟踪日志记录,建议将keepalived日志配置加上,因为一般默认它只能通过系统systemctl status keepalived或者journalctl查看,很不方便。

在安装目录或者系统/etc/sysconfig/keepalived下找到keepalived文件,修改部分内容:

# Options for keepalived. See `keepalived --help' output and keepalived(8) and
# keepalived.conf(5) man pages for a list of all options. Here are the most
# common ones :
#
# --vrrp               -P    Only run with VRRP subsystem.
# --check              -C    Only run with Health-checker subsystem.
# --dont-release-vrrp  -V    Dont remove VRRP VIPs & VROUTEs on daemon stop.
# --dont-release-ipvs  -I    Dont remove IPVS topology on daemon stop.
# --dump-conf          -d    Dump the configuration data.
# --log-detail         -D    Detailed log messages.
# --log-facility       -S    0-7 Set local syslog facility (default=LOG_DAEMON)
#

# KEEPALIVED_OPTIONS="-D"

KEEPALIVED_OPTIONS="-D -S 0"		#改这一句,添加-S 0

然后直接编辑/etc/rsyslog.conf文件,添加keepalived的日志记录后重启rsyslog和keepalived服务即可:

[root@node01 ~]# vim /etc/rsyslog.conf
……
# Save news errors of level crit and higher in a special file.
uucp,news.crit                                          /var/log/spooler

# Save boot messages also to boot.log
local7.*                                                /var/log/boot.log

#添加这一行
## save keepalived log to keepalived.log
local0.*                                                /var/log/keepalived.log

Step2:创建健康检测脚本

分别在master01和node1上创建MySQL检测脚本,用于检查MySQL服务状态。这里我们以node01(从节点)存放在/data目录下:

[root@node01 data]# vim /etc/keepalived/mysql-check.sh </dev/null 2>&1
if [ $? -ne 0 ]; then
    echo "无法连接到 MySQL 服务"
    exit 1
fi

# 如果上述检查都通过,则表示 MySQL 服务正常
exit 0
EOF  

然后 赋予脚本执行权限:

root@master01:/data# chmod +x mysql-check.sh 

Step3:主节点配置keepalived

我这里的主节点地址是192.168.1.200,首先修改配置文件,自定义路径安装一般在keepalived安装目录的etc/keepalived下即可找到keepalived.conf.sample,在线yum或者apt安装可将官方示例模版复制出来并修改:

cp /usr/share/doc/keepalived/samples/keepalived.conf.sample /etc/keepalived/keepalived.conf

当然了,也有可能在模版路径中出现带版本的情况:/usr/share/doc/keepalived-1.3.5/samples,自己注意下即可。这里我们直接修改:

root@master01:/opt/keepalived/etc/keepalived# cp keepalived.conf.sample keepalived.conf
root@master01:/opt/keepalived/etc/keepalived# vim keepalived.conf
root@master01:/opt/keepalived/etc/keepalived# cat keepalived.conf
! Configuration File for keepalived

global_defs {
   # 解决Script user 'keepalived_script' does not exist
   script_user root
   # 邮件通知信息
   notification_email {
#     [email protected]
#     [email protected]
#     [email protected]
   }
   # 定义发件人
   notification_email_from [email protected]
   # SMTP服务器地址
   # smtp_server 192.168.1.200
   # smtp_connect_timeout 30
   # 路由标识,一般不用改,也可以写成每个主机自己的主机名
   router_id MYSQL_MASTER
   vrrp_skip_check_adv_addr

}

# 检测MySQL状态  
vrrp_script chk_mysql {  
    script "/data/mysql-check.sh"  # 调用检测脚本  
    interval 2  # 每2秒检测一次  
    weight -30  # 检测失败时优先级减30  
}  

# 一个vrrp_instance就是定义一个虚拟路由器的,实例配置
vrrp_instance VI_1 {
    # 定义初始状态,可以是MASTER或者BACKUP
    state MASTER
	# 绑定VIP的网卡(根据实际网卡名称修改,如ens33)
    interface ens33
	# 虚拟路由ID(双节点需一致)
    virtual_router_id 51
	# 主节点优先级(高于从节点)
    priority 120
	# 心跳间隔1秒
    advert_int 1
	# 认证密码(双节点需一致)
    authentication {
        auth_type PASS
        auth_pass 1111
    }
	# 工作模式,nopreempt表示工作在非抢占模式,默认是抢占模式 preempt
    # nopreempt
	# 设置虚拟VIP地址,一般就设置一个,在LVS中这个就是为LVS主机设置VIP的,这样你就不用自己手动设置了
    virtual_ipaddress {
	     #VIP地址 IP/掩码
        192.168.1.100/24
    }
	# 追踪脚本,通常用于去执行上面的vrrp_script定义的脚本内容
	track_script {
	   # 绑定健康检测脚本
	   chk_mysql
	}
	

    # Allow packets addressed to the VIPs above to be received
    accept
}


# 检查配置文件有效性
root@master01:/opt/keepalived/etc/keepalived# /opt/keepalived/sbin/keepalived -t -f /opt/keepalived/etc/keepalived/keepalived.conf
SECURITY VIOLATION - scripts are being executed but script_security not enabled.

这里需要注意的是VIP 必须在所在的网络环境中是唯一的,不能与网络内其他设备的 IP 地址冲突。如果 VIP 与其他设备的 IP 冲突,会导致网络通信异常,可能会出现 IP 地址冲突错误,影响服务的正常访问。比如在我们的局域网中已经有一台设备使用了 192.168.1.100,那么就不能将这个地址作为 VIP。或者我当前数据库服务在192.168.1.200下,keepalived也安装在这个地址,那么就不能设置为192.168.1.200,否则就会导致网卡异常了。

同时VIP 的网段要和 Keepalived 服务器所在的网络网段一致。在我们的配置中,使用的是 192.168.1.100/24,这意味着该 IP 地址属于 192.168.1.0/24 网段。Keepalived 主备服务器也必须在这个网段内,否则 VIP 无法正常工作。

检查keepalived配置,这里我们只有一个脚本安全风险可暂时忽略不计,没问题后启动keepalived服务并设置开机自启:

root@master01:/opt/keepalived# root@master01:~# systemctl start keepalived.service && systemctl enable keepalived.service
root@master01:/opt# systemctl status keepalived.service 
● keepalived.service - LVS and VRRP High Availability Monitor
     Loaded: loaded (/lib/systemd/system/keepalived.service; disabled; vendor preset: enabled)
     Active: active (running) since Tue 2025-04-22 17:43:35 CST; 2min 18s ago
       Docs: man:keepalived(8)
             man:keepalived.conf(5)
             man:genhash(1)
             https://keepalived.org
   Main PID: 445892 (keepalived)
      Tasks: 2 (limit: 4544)
     Memory: 1.5M
        CPU: 2.413s
     CGroup: /system.slice/keepalived.service
             ├─445892 /opt/keepalived/sbin/keepalived --dont-fork
             └─445893 /opt/keepalived/sbin/keepalived --dont-fork

4月 22 17:43:35 master01 Keepalived[445892]: (Line 11) Warning - empty notification_email block
4月 22 17:43:35 master01 Keepalived[445892]: NOTICE: setting config option max_auto_priority should result in better keepalived performance
4月 22 17:43:35 master01 Keepalived[445892]: Starting VRRP child process, pid=445893
4月 22 17:43:35 master01 Keepalived_vrrp[445893]: Unsafe permissions found for script '/data/mysql-check.sh'.
4月 22 17:43:35 master01 Keepalived_vrrp[445893]: SECURITY VIOLATION - scripts are being executed but script_security not enabled. There are insecure scripts.
4月 22 17:43:35 master01 Keepalived[445892]: Startup complete
4月 22 17:43:35 master01 systemd[1]: Started LVS and VRRP High Availability Monitor.
4月 22 17:43:35 master01 Keepalived_vrrp[445893]: (VI_1) Entering BACKUP STATE (init)
4月 22 17:43:35 master01 Keepalived_vrrp[445893]: VRRP_Script(chk_mysql) succeeded
4月 22 17:43:38 master01 Keepalived_vrrp[445893]: (VI_1) Entering MASTER STATE

## ✅ 验证VIP绑定(主节点应显示VIP)
root@master01:/opt/keepalived# ip addr show ens33 | grep "192.168.1.100"
    inet 192.168.1.100/24 scope global secondary ens33
[root@node01 opt]# ip a show ens33
2: ens33:  mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:58:e6:1c brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.201/24 brd 192.168.1.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet 192.168.1.100/24 scope global secondary ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe58:e61c/64 scope link 
       valid_lft forever preferred_lft forever

Step4:从节点配置keepalived

从节点对应的服务地址为192.168.1.201,我们修改从节点配置(仅需调整state和priority):

[root@node01 data]# cat /etc/keepalived/keepalived.conf 
! Configuration File for keepalived

global_defs {
   script_user root
   # 邮件通知信息
   notification_email {
     [email protected]
     [email protected]
     [email protected]
   }
   # 定义发件人
   notification_email_from [email protected]
   # SMTP服务器地址
   # smtp_server 192.168.1.200
   # smtp_connect_timeout 30
   # 路由标识,一般不用改,也可以写成每个主机自己的主机名
   router_id MYSQL_NODE
   vrrp_skip_check_adv_addr

}

# 检测MySQL状态  
vrrp_script chk_mysql {  
    script "/data/mysql-check.sh"  # 调用检测脚本  
    interval 2  # 每2秒检测一次  
    weight -20  # 检测失败时优先级减20  
}  

# 一个vrrp_instance就是定义一个虚拟路由器的,实例配置
vrrp_instance VI_1 {
    # 定义初始状态,可以是MASTER或者BACKUP
    state BACKUP
	# 绑定VIP的网卡(根据实际网卡名称修改,如ens33)
    interface ens33
	# 虚拟路由ID(双节点需一致)
    virtual_router_id 51
	# 从节点优先级(低于主节点)
    priority 100
	# 心跳间隔1秒
    advert_int 1
	# 认证密码(双节点需一致)
    authentication {
        auth_type PASS
        auth_pass 1111
    }
	# 工作模式,nopreempt表示工作在非抢占模式,默认是抢占模式 preempt
    # nopreempt
	# 设置虚拟VIP地址,一般就设置一个,在LVS中这个就是为LVS主机设置VIP的,这样你就不用自己手动设置了
    virtual_ipaddress {
	     #VIP地址 IP/掩码
        192.168.1.100/24
    }
	# 追踪脚本,通常用于去执行上面的vrrp_script定义的脚本内容
	track_script {
	   # 绑定健康检测脚本
	   chk_mysql
	}

    # Allow packets addressed to the VIPs above to be received
    accept
}


[root@node01 opt]# systemctl start keepalived.service && systemctl enable keepalived.service
[root@node01 ~]# systemctl status keepalived.service 
● keepalived.service - LVS and VRRP High Availability Monitor
   Loaded: loaded (/usr/lib/systemd/system/keepalived.service; disabled; vendor preset: disabled)
   Active: active (running) since 二 2025-04-22 17:45:25 CST; 2min 31s ago
  Process: 102111 ExecStart=/usr/sbin/keepalived $KEEPALIVED_OPTIONS (code=exited, status=0/SUCCESS)
 Main PID: 102112 (keepalived)
    Tasks: 3
   Memory: 8.8M
   CGroup: /system.slice/keepalived.service
           ├─102112 /usr/sbin/keepalived -D -S 0# -S 0
           ├─102113 /usr/sbin/keepalived -D -S 0# -S 0
           └─102114 /usr/sbin/keepalived -D -S 0# -S 0

4月 22 17:45:25 node01 systemd[1]: Started LVS and VRRP High Availability Monitor.
4月 22 17:45:25 node01 Keepalived_vrrp[102114]: Registering Kernel netlink command channel
4月 22 17:45:25 node01 Keepalived_vrrp[102114]: Registering gratuitous ARP shared channel
4月 22 17:45:25 node01 Keepalived_vrrp[102114]: Opening file '/etc/keepalived/keepalived.conf'.
4月 22 17:45:25 node01 Keepalived_vrrp[102114]: SECURITY VIOLATION - scripts are being executed but script_security not enabled.
4月 22 17:45:25 node01 Keepalived_vrrp[102114]: VRRP_Instance(VI_1) removing protocol VIPs.
4月 22 17:45:25 node01 Keepalived_vrrp[102114]: Using LinkWatch kernel netlink reflector...
4月 22 17:45:25 node01 Keepalived_vrrp[102114]: VRRP_Instance(VI_1) Entering BACKUP STATE
4月 22 17:45:25 node01 Keepalived_vrrp[102114]: VRRP sockpool: [ifindex(2), proto(112), unicast(0), fd(10,11)]
4月 22 17:45:25 node01 Keepalived_vrrp[102114]: VRRP_Script(chk_mysql) succeeded

完成后我们可以发现原本用户连接的mysql客户端就可以直接通过keepalived配置的虚拟VIP地址:192.168.1.100进行连接了:

基于Keepalived的MySQL双活架构实战指南(超详细)_第2张图片

Step5:⚙️配置 MySQL 主从复制

这里我的mysql都使用的是docker运行的,因此在这首先在Mater01中创建一个主节点的配置文件,例如 /data/mysql/conf/master.cnf,内容如下:

[mysqld]
server-id = 1
log-bin = mysql-bin
# binlog-do-db = your_database

这里的 server-id 必须是唯一的,log-bin 开启了二进制日志,binlog-do-db 可指定仅复制特定的数据库。接着,修改 docker-compose.yml 文件,将配置文件挂载到容器内:

version: '3.7'
services:
  mysql-master:
    container_name: mysql-master
    image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/mysql:8.4.4
    ports:
      - 3306:3306
    restart: unless-stopped
    environment:
      TZ: Asia/Shanghai
      MYSQL_ROOT_PASSWORD: 123456
    volumes:
      - /data/mysql/log:/var/log/mysql
      - /data/mysql/data:/var/lib/mysql
      - /data/mysql/conf/master.cnf:/etc/mysql/conf.d/master.cnf    

 然后以相同的方式在Node01中创建一个从节点的配置文件,例如 /data/mysql/conf/slave.cnf,内容如下:

[mysqld]
server-id = 2
relay-log = mysql-relay-bin
log-bin = mysql-bin

这里的 server-id 要与主节点不同,relay-log 用于记录从主节点接收的二进制日志。然后,在Node2中的docker-compose.yml 里添加从节点的配置:

version: '3.7'
services:
  mysql-slave:
    container_name: mysql-slave
    image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/mysql:8.4.4
    ports:
      - 3307:3306
    restart: unless-stopped
    environment:
      TZ: Asia/Shanghai
      MYSQL_ROOT_PASSWORD: 123456
    volumes:
      - /data/mysql/log-slave:/var/log/mysql
      - /data/mysql/data-slave:/var/lib/mysql
      - /data/mysql/conf/slave.cnf:/etc/mysql/conf.d/slave.cnf
    extra_hosts:
      - "mysql-master:192.168.1.200"    

分别运行容器后,在主节点创建复制用户:在master01(192.168.1.200) 上,进入主节点容器,创建复制用户:

root@master01:/opt# docker exec -it mysql-master mysql -uroot -p123456 -e "
> CREATE USER 'copyuser'@'%' IDENTIFIED BY '654321';
> GRANT REPLICATION SLAVE ON *.* TO 'copyuser'@'%';
> FLUSH PRIVILEGES;
> "
mysql: [Warning] Using a password on the command line interface can be insecure.

查询实际的二进制日志文件名称用户在从节点配置:

root@master01:/opt# docker exec -it mysql-master mysql -uroot -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 18
Server version: 8.4.4 MySQL Community Server - GPL

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> show binary log status;
+------------------+----------+--------------+------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000004 |     158 |              |                  |                   |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)

接着我们需要让从节点连接主节点并启动复制,配置从节点连接主节点:在 node01(192.168.1.201)上,进入从节点容器,配置从节点连接主节点,填入查询到的log_file和log_pos值:

# 配置主从复制链路
mysql> CHANGE REPLICATION SOURCE TO
    -> SOURCE_HOST = '192.168.1.200',
    -> SOURCE_PORT = 3306,
    -> SOURCE_USER = 'copyuser',
    -> SOURCE_PASSWORD = '654321',
    -> SOURCE_LOG_FILE = 'mysql-bin.000004',
    -> SOURCE_LOG_POS = 158,
    -> SOURCE_AUTO_POSITION = 0;
Query OK, 0 rows affected, 2 warnings (0.08 sec)
# 启动复制进程
mysql> START REPLICA;
Query OK, 0 rows affected (0.12 sec)

这里要注意:8.4之前的部分主从复制的相关命令比如show master status;不能用了,改为了SHOW BINARY LOG STATUS;从节点相关操作也发生了变化,使用start replica;而不是之前的start slave;具体的大家可参考mysql文档。

然后检查一下复制状态看看是否正常:

mysql> START REPLICA;
Query OK, 0 rows affected (0.12 sec)

mysql> SHOW REPLICA STATUS\G
*************************** 1. row ***************************
             Replica_IO_State: Connecting to source
                  Source_Host: 192.168.1.200
                  Source_User: copyuser
                  Source_Port: 3306
                Connect_Retry: 60
              Source_Log_File: mysql-bin.000003
          Read_Source_Log_Pos: 5857
               Relay_Log_File: mysql-relay-bin.000001
                Relay_Log_Pos: 4
        Relay_Source_Log_File: mysql-bin.000003
           Replica_IO_Running: Connecting
          Replica_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Source_Log_Pos: 5857
              Relay_Log_Space: 158
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Source_SSL_Allowed: No
           Source_SSL_CA_File: 
           Source_SSL_CA_Path: 
              Source_SSL_Cert: 
            Source_SSL_Cipher: 
               Source_SSL_Key: 
        Seconds_Behind_Source: NULL
Source_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 2061
                Last_IO_Error: Error connecting to source '[email protected]:3306'. This was attempt 1/10, with a delay of 60 seconds between attempts. Message: Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection.
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Source_Server_Id: 0
                  Source_UUID: 
             Source_Info_File: mysql.slave_master_info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
    Replica_SQL_Running_State: Replica has read all relay log; waiting for more updates
           Source_Retry_Count: 10
                  Source_Bind: 
      Last_IO_Error_Timestamp: 250422 13:32:30
     Last_SQL_Error_Timestamp: 
               Source_SSL_Crl: 
           Source_SSL_Crlpath: 
           Retrieved_Gtid_Set: 
            Executed_Gtid_Set: 
                Auto_Position: 0
         Replicate_Rewrite_DB: 
                 Channel_Name: 
           Source_TLS_Version: 
       Source_public_key_path: 
        Get_Source_public_key: 0
            Network_Namespace: 
1 row in set (0.00 sec)

确认以下字段:

  • Replica_IO_Running: Yes

  • Replica_SQL_Running: Yes

  • Last_IO_Error: 无报错

但是这里我们明显有问题: Replica_IO_Running: Connecting,并且Last_IO_Error出现错误: Error connecting to source '[email protected]:3306'. This was attempt 1/10, with a delay of 60 seconds between attempts. Message: Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection.

原因是从节点在连接主节点时,因为使用了 caching_sha2_password 认证插件,而该插件默认要求使用安全连接(SSL/TLS),但当前连接并非安全连接,从而引发了认证错误。

MySQL 8.0+ 默认使用 caching_sha2_password,它要求客户端通过以下两种方式之一连接:

  • SSL 加密通道:通过 TLS 加密传输密码(推荐生产环境使用)。

  • RSA 公钥交换:在不启用 SSL 的情况下,客户端需从服务端获取 RSA 公钥加密密码(需配置 GET_MASTER_PUBLIC_KEY=1)。

所以我们要么得配置 SSL/TLS 安全连接,或者就得修改主节点复制用户的认证插件,比如使用mysql_native_password。作为一个测试环境我们可以选择后者。

mysql> SELECT plugin_name, plugin_status 
    -> FROM information_schema.plugins 
    -> WHERE plugin_name = 'mysql_native_password';
+-----------------------+---------------+
| plugin_name           | plugin_status |
+-----------------------+---------------+
| mysql_native_password | DISABLED      |
+-----------------------+---------------+
1 row in set (0.00 sec)

但是实际中mysql_native_password是旧的插件,可能没有被默认加载。特别是在某些安装或容器镜像中,为了减少体积或提高安全性,可能没有包含这个插件,或者配置文件中禁用了它。

这里我们看到mysql容器中确实未启用旧版插件,再启用得话可能的改配置文件,比较麻烦。因此我们采取第三种办法直接关闭SSL采用RSA 公钥方式。

如果主节点未配置 SSL 证书,需确保 sha256_password_auto_generate_rsa_keys 和 caching_sha2_password_auto_generate_rsa_keys 参数为 ON(默认值),MySQL 会自动生成 RSA 密钥对。我们先在主节点上确认一下:

mysql> SHOW VARIABLES LIKE 'sha256_password_auto_generate_rsa_keys';
+----------------------------------------+-------+
| Variable_name                          | Value |
+----------------------------------------+-------+
| sha256_password_auto_generate_rsa_keys | ON    |
+----------------------------------------+-------+
1 row in set (0.00 sec)

mysql> SHOW VARIABLES LIKE 'caching_sha2_password_auto_generate_rsa_keys';
+----------------------------------------------+-------+
| Variable_name                                | Value |
+----------------------------------------------+-------+
| caching_sha2_password_auto_generate_rsa_keys | ON    |
+----------------------------------------------+-------+
1 row in set (0.01 sec)

然后在从节点调整复制链路参数,重新配置replice:

mysql> STOP REPLICA;
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> RESET REPLICA ALL;
Query OK, 0 rows affected (0.02 sec)

mysql> CHANGE REPLICATION SOURCE TO
    -> SOURCE_HOST = '192.168.1.200',
    -> SOURCE_USER = 'copyuser',
    -> SOURCE_PASSWORD = '654321',
    -> SOURCE_LOG_FILE = 'mysql-bin.000004',
    -> SOURCE_LOG_POS = 158,
    -> SOURCE_AUTO_POSITION = 0,
    -> SOURCE_SSL = 0,                 -- 禁用 SSL
    -> GET_SOURCE_PUBLIC_KEY = 1;      -- 启用 RSA 公钥交换
Query OK, 0 rows affected, 2 warnings (0.01 sec)

mysql> START REPLICA;
Query OK, 0 rows affected (0.04 sec)
mysql> start replica;
Query OK, 0 rows affected (0.03 sec)

mysql> show replica status \G;
*************************** 1. row ***************************
             Replica_IO_State: Waiting for source to send event
                  Source_Host: 192.168.1.200
                  Source_User: copyuser
                  Source_Port: 3306
                Connect_Retry: 60
              Source_Log_File: mysql-bin.000004
          Read_Source_Log_Pos: 158
               Relay_Log_File: mysql-relay-bin.000002
                Relay_Log_Pos: 328
        Relay_Source_Log_File: mysql-bin.000004
           Replica_IO_Running: Yes
          Replica_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Source_Log_Pos: 158
              Relay_Log_Space: 539
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Source_SSL_Allowed: No
           Source_SSL_CA_File: 
           Source_SSL_CA_Path: 
              Source_SSL_Cert: 
            Source_SSL_Cipher: 
               Source_SSL_Key: 
        Seconds_Behind_Source: 0
Source_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Source_Server_Id: 1
                  Source_UUID: 69b7ac8b-ef76-11ef-b33d-0242ac130002
             Source_Info_File: mysql.slave_master_info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
    Replica_SQL_Running_State: Replica has read all relay log; waiting for more updates
           Source_Retry_Count: 10
                  Source_Bind: 
      Last_IO_Error_Timestamp: 
     Last_SQL_Error_Timestamp: 
               Source_SSL_Crl: 
           Source_SSL_Crlpath: 
           Retrieved_Gtid_Set: 
            Executed_Gtid_Set: 
                Auto_Position: 0
         Replicate_Rewrite_DB: 
                 Channel_Name: 
           Source_TLS_Version: 
       Source_public_key_path: 
        Get_Source_public_key: 1
            Network_Namespace: 
1 row in set (0.00 sec)

至此就搞定了两台数据库的主从复制了。如果主库添加一个数据表或者更新信息,从库就能同步过来了。但这并不是我们的重点,我们需要测试如果主库挂机了,VIP是否漂移到从库节点。

四、Keeplived的故障切换实战测试 

场景1:模拟 Keepalived 主节点服务宕机

在主节点停止 Keepalived 服务:

root@master01:/opt# systemctl stop keepalived

此时我们可以看到:

  • 虚拟 IP 在 1-3 秒内漂移到从节点。

  • 从节点日志 /var/log/keepalived.log 出现 Transition to MASTER STATE

  • 应用通过虚拟 IP 可正常访问 MySQL 服务。

……
Apr 22 17:49:42 node01 Keepalived_vrrp[117490]: Using LinkWatch kernel netlink reflector...
Apr 22 17:49:42 node01 Keepalived_vrrp[117490]: VRRP_Instance(VI_1) Entering BACKUP STATE
Apr 22 17:49:42 node01 Keepalived_vrrp[117490]: VRRP sockpool: [ifindex(2), proto(112), unicast(0), fd(10,11)]
Apr 22 17:49:42 node01 Keepalived_vrrp[117490]: VRRP_Script(chk_mysql) succeeded
Apr 22 17:50:14 node01 Keepalived_vrrp[117490]: VRRP_Instance(VI_1) Transition to MASTER STATE
Apr 22 17:50:15 node01 Keepalived_vrrp[117490]: VRRP_Instance(VI_1) Entering MASTER STATE
Apr 22 17:50:15 node01 Keepalived_vrrp[117490]: VRRP_Instance(VI_1) setting protocol VIPs.
Apr 22 17:50:15 node01 Keepalived_vrrp[117490]: Sending gratuitous ARP on ens33 for 192.168.1.100
Apr 22 17:50:15 node01 Keepalived_vrrp[117490]: VRRP_Instance(VI_1) Sending/queueing gratuitous ARPs on ens33 for 192.168.1.100
……

在从节点上检查虚拟 IP 绑定:

[root@node01 ~]# ip addr show ens33 | grep 192.168.1.100
    inet 192.168.1.100/24 scope global secondary ens33

而当我们重启主节点后,由于默认是抢占模式,因此主节点会拿回VIP,此时我们可以看到从节点的日志变化:

Apr 22 17:50:15 node01 Keepalived_vrrp[117490]: VRRP_Instance(VI_1) Entering MASTER STATE
Apr 22 17:50:15 node01 Keepalived_vrrp[117490]: VRRP_Instance(VI_1) setting protocol VIPs.
Apr 22 17:50:15 node01 Keepalived_vrrp[117490]: Sending gratuitous ARP on ens33 for 192.168.1.100
Apr 22 17:50:15 node01 Keepalived_vrrp[117490]: VRRP_Instance(VI_1) Sending/queueing gratuitous ARPs on ens33 for 192.168.1.100
Apr 22 17:50:15 node01 Keepalived_vrrp[117490]: Sending gratuitous ARP on ens33 for 192.168.1.100
Apr 22 17:50:15 node01 Keepalived_vrrp[117490]: Sending gratuitous ARP on ens33 for 192.168.1.100
Apr 22 17:50:15 node01 Keepalived_vrrp[117490]: Sending gratuitous ARP on ens33 for 192.168.1.100
Apr 22 17:50:15 node01 Keepalived_vrrp[117490]: Sending gratuitous ARP on ens33 for 192.168.1.100
Apr 22 17:50:20 node01 Keepalived_vrrp[117490]: Sending gratuitous ARP on ens33 for 192.168.1.100
Apr 22 17:50:20 node01 Keepalived_vrrp[117490]: VRRP_Instance(VI_1) Sending/queueing gratuitous ARPs on ens33 for 192.168.1.100
Apr 22 17:50:20 node01 Keepalived_vrrp[117490]: Sending gratuitous ARP on ens33 for 192.168.1.100
Apr 22 17:50:20 node01 Keepalived_vrrp[117490]: Sending gratuitous ARP on ens33 for 192.168.1.100
Apr 22 17:50:20 node01 Keepalived_vrrp[117490]: Sending gratuitous ARP on ens33 for 192.168.1.100
Apr 22 17:50:20 node01 Keepalived_vrrp[117490]: Sending gratuitous ARP on ens33 for 192.168.1.100
Apr 22 17:50:56 node01 Keepalived_vrrp[117490]: VRRP_Instance(VI_1) Received advert with higher priority 120, ours 80
Apr 22 17:50:56 node01 Keepalived_vrrp[117490]: VRRP_Instance(VI_1) Entering BACKUP STATE
Apr 22 17:50:56 node01 Keepalived_vrrp[117490]: VRRP_Instance(VI_1) removing protocol VIPs.

由原来的刚得到的MASTER变为了BACKUP  ,并且虚拟ip也重新回到主节点,从节点失去虚拟ip:

[root@node01 opt]# ip addr show ens33
2: ens33:  mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:58:e6:1c brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.201/24 brd 192.168.1.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe58:e61c/64 scope link 
       valid_lft forever preferred_lft forever

场景2:模拟主节点 MySQL 容器崩溃

验证 Keepalived 对 MySQL 服务健康检查的触发切换,先在主节点强制终止 MySQL 容器:

root@master01:/opt# docker kill mysql-master
mysql-master

此时Keepalived 检测到 MySQL 服务不可用,触发虚拟 IP 释放。此时可以看到优先级根据配置由原来的120降为90:

4月 22 18:49:29 master01 Keepalived_vrrp[562704]: (VI_1) Entering BACKUP STATE (init)
4月 22 18:49:29 master01 Keepalived_vrrp[562704]: VRRP_Script(chk_mysql) succeeded
4月 22 18:49:32 master01 Keepalived_vrrp[562704]: (VI_1) Entering MASTER STATE
4月 22 18:52:27 master01 Keepalived_vrrp[562704]: Script `chk_mysql` now returning 1
4月 22 18:52:27 master01 Keepalived_vrrp[562704]: VRRP_Script(chk_mysql) failed (exited with status 1)
4月 22 18:52:27 master01 Keepalived_vrrp[562704]: (VI_1) Changing effective priority from 120 to 90

 而原来的从节点原来优先级是100,此时因为从节点优先级更高,所以它会获得VIP,而主节点会丢失:

Apr 22 18:56:28 node01 Keepalived_vrrp[74870]: VRRP_Instance(VI_1) Received advert with lower priority 90, ours 100, forcing new election
Apr 22 18:56:28 node01 Keepalived_vrrp[74870]: Sending gratuitous ARP on ens33 for 192.168.1.100
Apr 22 18:56:28 node01 Keepalived_vrrp[74870]: VRRP_Instance(VI_1) Sending/queueing gratuitous ARPs on ens33 for 192.168.1.100
Apr 22 18:56:28 node01 Keepalived_vrrp[74870]: Sending gratuitous ARP on ens33 for 192.168.1.100
Apr 22 18:56:28 node01 Keepalived_vrrp[74870]: Sending gratuitous ARP on ens33 for 192.168.1.100
Apr 22 18:56:28 node01 Keepalived_vrrp[74870]: Sending gratuitous ARP on ens33 for 192.168.1.100
Apr 22 18:56:28 node01 Keepalived_vrrp[74870]: Sending gratuitous ARP on ens33 for 192.168.1.100
[root@node01 opt]# ip addr show ens33
2: ens33:  mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:58:e6:1c brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.201/24 brd 192.168.1.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet 192.168.1.100/24 scope global secondary ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe58:e61c/64 scope link 
       valid_lft forever preferred_lft forever

当我们重启主节点的Mysql后由于健康检查通过,优先级又会由原来的90回到120,重新拿回VIP。

root@master01:/opt# docker start mysql-master
mysql-master
root@master01:~# systemctl status keepalived.service 
……
4月 22 18:52:27 master01 Keepalived_vrrp[562704]: Script `chk_mysql` now returning 1
4月 22 18:52:27 master01 Keepalived_vrrp[562704]: VRRP_Script(chk_mysql) failed (exited with status 1)
4月 22 18:52:27 master01 Keepalived_vrrp[562704]: (VI_1) Changing effective priority from 120 to 90
4月 22 18:57:37 master01 Keepalived_vrrp[562704]: Script `chk_mysql` now returning 0
4月 22 18:57:37 master01 Keepalived_vrrp[562704]: VRRP_Script(chk_mysql) succeeded
4月 22 18:57:37 master01 Keepalived_vrrp[562704]: (VI_1) Changing effective priority from 90 to 120

从节点相应的又会回到BACKUP状态了。

# 主节点mysql失败后
[root@node01 opt]# ip addr show ens33
2: ens33:  mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:58:e6:1c brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.201/24 brd 192.168.1.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet 192.168.1.100/24 scope global secondary ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe58:e61c/64 scope link 
       valid_lft forever preferred_lft forever
# 主节点mysql重启后
[root@node01 opt]# ip addr show ens33
2: ens33:  mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:58:e6:1c brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.201/24 brd 192.168.1.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe58:e61c/64 scope link 
       valid_lft forever preferred_lft forever

这里我们要理解一下健康检查状态的方式,虽然设置的是每隔2秒检查一次,但是实际上:当健康检查脚本首次返回失败状态(退出码非 0)时,Keepalived 会根据配置的 fall 参数一次性减少优先级不会持续累积减少

而当脚本从失败恢复为成功后(即状态切换),若再次出现失败,Keepalived 会重新计数连续失败次数,并在满足 fall 条件时再次调整优先级。

示例流程如下

  1. 初始状态:连续成功 → 优先级 120(假设初始优先级为 120)。
  2. 首次失败(第 1 次):未达 fall=2,不调整优先级。
  3. 第二次失败(第 2 次):触发调整,优先级变为 120 + weight = 120 - 10 = 110
  4. 后续持续失败:优先级保持 110,不再减少。
  5. 脚本恢复成功:优先级回调至 120(若配置了 rise 参数)。
  6. 再次失败:重新从第 1 次连续失败开始计数,达 fall=2 后再次调整为 110。

并且日志上也只会显示一次,Keepalived 仅在脚本执行结果发生变化时(成功→失败或失败→成功)才会记录日志,而不是每次执行都记录。虽然期间由于服务未启动,即使检测健康检查一直未通过,Keepalived 也不会重复记录相同结果的日志,仅在状态切换时记录。

场景3:模拟主节点网络隔离(谨慎尝试)

验证网络中断时的脑裂防护及切换。我们先在主节点丢弃所有网络流量(模拟网络故障): 

root@master01:/opt# iptables -A INPUT -j DROP
root@master01:/opt# iptables -A OUTPUT -j DROP

 此时由于原主节点因无法通信,自动降级为 BACKUP 状态。我们可以从主节点日志查看:

root@master01:~# systemctl status keepalived.service 
● keepalived.service - LVS and VRRP High Availability Monitor
     Loaded: loaded (/lib/systemd/system/keepalived.service; disabled; vendor preset: enabled)
     Active: active (running) since Tue 2025-04-22 18:49:29 CST; 19min ago
       Docs: man:keepalived(8)
             man:keepalived.conf(5)
             man:genhash(1)
             https://keepalived.org
   Main PID: 562703 (keepalived)
      Tasks: 2 (limit: 4544)
     Memory: 1.3M
        CPU: 35.865s
     CGroup: /system.slice/keepalived.service
             ├─562703 /opt/keepalived/sbin/keepalived --dont-fork
             └─562704 /opt/keepalived/sbin/keepalived --dont-fork

4月 22 19:09:04 master01 Keepalived_vrrp[562704]: (VI_1): send advert error 1 (Operation not permitted)
4月 22 19:09:05 master01 Keepalived_vrrp[562704]: (VI_1): send advert error 1 (Operation not permitted)
4月 22 19:09:06 master01 Keepalived_vrrp[562704]: (VI_1): send advert error 1 (Operation not permitted)
4月 22 19:09:07 master01 Keepalived_vrrp[562704]: (VI_1): send advert error 1 (Operation not permitted)

主节点已经报错了,此时从节点因收不到 VRRP 组播报文,触发虚拟 IP 接管变为了MASTER。

[root@node01 opt]# journalctl -u keepalived -n 50 | grep "Entering MASTER STATE"
4月 22 19:08:41 node01 Keepalived_vrrp[74870]: VRRP_Instance(VI_1) Entering MASTER STATE

 最后我们恢复主节点网络后,检查抢占行为(若配置 nopreempt 需忽略):

root@master01:~# ip addr show ens33
2: ens33:  mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:0c:29:a3:e7:0e brd ff:ff:ff:ff:ff:ff
    altname enp2s1
    inet 192.168.1.200/24 brd 192.168.1.255 scope global noprefixroute ens33
       valid_lft forever preferred_lft forever
    inet 192.168.1.100/24 scope global secondary ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fea3:e70e/64 scope link 
       valid_lft forever preferred_lft forever

主节点重新恢复到持有VIP的状态了。

五、Keepalived安装出现的脑裂现象及解决

脑裂(Brain Split) 是指 Keepalived 主备节点因心跳通信中断,导致双方都认为自己是主节点,同时持有 VIP(虚拟 IP)并提供服务。这会引发以下问题:

  • 双主节点同时响应请求,导致业务混乱(如会话丢失、数据冲突)。
  • 网络中出现两个相同的 VIP,引发 ARP 欺骗或路由冲突。

也就是说这种情况我们不管是在主节点还是从节点,在进行虚拟ip绑定查看的时候,两边都会查到虚拟ip地址。

root@master01:~# ip addr show ens33
2: ens33:  mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:0c:29:a3:e7:0e brd ff:ff:ff:ff:ff:ff
    altname enp2s1
    inet 192.168.1.200/24 brd 192.168.1.255 scope global noprefixroute ens33
       valid_lft forever preferred_lft forever
    inet 192.168.1.100/24 scope global secondary ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fea3:e70e/64 scope link 
       valid_lft forever preferred_lft forever

正常情况下在主备双机的情况下只有一个服务器才会绑定虚拟ip,但是如果出现脑裂,就会导致,主从节点都能查询到这个虚拟ip绑定的情况。这里我们在配置时也出现过这个问题,因此记下一笔!

而其核心原因就是主备节点之间的 VRRP 心跳包(组播包)无法正常传输,导致状态同步失败

这里就涉及到VRRP 协议与组播通信了,上面我们也详细介绍过VRRP协议,Keepalived也是基于 VRRP(Virtual Router Redundancy Protocol,虚拟路由冗余协议) 实现主备切换。而主节点则是通过 组播地址 224.0.0.18 定期发送心跳包(目的端口 112,协议类型 VRRP),备节点通过监听该组播包判断主节点状态。

但是组播包有一些自己的传输要求

  • 组播地址224.0.0.18 属于 IPv4 链路本地组播地址,仅在局域网内传播。
  • 协议类型:VRRP 使用 IP 协议号 112(非 UDP/TCP,直接封装在 IP 报文中)。
  • 通信接口:心跳包通过指定网卡(如 ens33)发送和接收。

因此当我们服务器上使用防火墙时,如果不加以配置,那么防火墙就很容易对 VRRP 通信的干扰 。

所以在使用 firewalld 作为防火墙的系统(如 CentOS/RHEL)中,默认策略可能拦截 VRRP 组播包,从而导致:

  • 主节点发送的心跳包无法到达备节点。
  • 备节点无法感知主节点状态,超时后自提升为主节点,引发脑裂。

防火墙拦截的原因

  • firewalld 默认未预定义 VRRP 规则,不会自动放行 IP 协议号为 112 的组播包。
  • 传统的端口放行规则(如 --add-port=112/udp对 VRRP 无效,因为 VRRP 不使用 UDP/TCP 端口,而是直接使用 IP 协议号。

因此我们如果我们有使用firewalld,切记需要放行 VRRP 组播包:

[root@node01 opt]# firewall-cmd --direct --permanent --add-rule ipv4 filter INPUT 0 \
  --in-interface ens33 \
  --destination 224.0.0.18 \
  --protocol vrrp \
  -j ACCEPT
  • --direct:直接操作防火墙底层规则(绕过 firewalld 预定义的区域策略)。
  • --protocol vrrp:指定放行 VRRP 协议(对应 IP 协议号 112)。
  • --destination 224.0.0.18:限定目标为 VRRP 组播地址。
  • --in-interface ens33:仅放行通过指定网卡(ens33)接收的包(避免全局放行,增强安全性)。

这样我们就可以允许主节点通过 ens33 发送的 VRRP 组播包进入备节点,确保备节点能正常接收心跳,维持主备状态同步。避免放行无关流量,仅允许 VRRP 通信通过,符合最小权限原则。

最后我们需要验证一下规则有效性:

查看防火墙规则

[root@node01 opt]# firewall-cmd --direct --get-all-rules  # 确认规则已添加
[root@node01 opt]# iptables -nL INPUT | grep vrrp         # 查看底层 iptables 规则是否生效

测试心跳通信

  • 在备节点使用 tcpdump 抓包,确认能收到主节点发送的 VRRP 包:

    bash

    [root@node01 opt]# tcpdump -i ens33 -n 'host 224.0.0.18 and proto 112'
    

  • 观察 Keepalived 日志(/var/log/keepalived.log),确认主备状态正常同步。

当然了,除防火墙拦截外,脑裂还可能由以下原因导致,大家可以结合具体场景排查,这里仅做一个简单汇总:

原因 解决方案
网络链路故障 检查物理网卡、交换机端口、网线是否正常,确保心跳链路冗余(如双网卡绑定)。
VRRP 优先级冲突 确保主备节点优先级唯一(主节点 priority > 备节点)。
内核参数未配置 启用 IP 转发:echo 1 > /proc/sys/net/ipv4/ip_forward
SELinux 限制 使用 semanage 允许 Keepalived 绑定 VIP(见前文 SELinux 配置)。

 六、总结

虽然咱们通过keepalived完成了整个mysql双活架构配置,但是这种方式也仅仅是一种伪双活的配置方法,虽然两台 MySQL 服务器(节点 A 和节点 B)通过 主从复制 保持数据同步,Keepalived 负责虚拟 IP(VIP)的浮动,正常情况下 VIP 绑定在主节点(Active 节点),从节点(Passive 节点)仅作为备份。但是严格来说,这属于主备架构(Active-Passive),并非真正的双活。

真正的双活架构(Active-Active)应满足如下特点:

  • 两台 MySQL 服务器(节点 A 和节点 B)同时提供读写服务,数据需要在两个节点之间双向同步。
  • Keepalived 通常用于负载均衡(如配合 LVS 或 DNS 轮询),而非单纯的 VIP 浮动。
  • 实现难度高,需解决数据冲突(如主键冲突、事务顺序冲突)、延迟同步等问题。

这种情况下我们就不需要传统的主从复制了,但需要双向同步机制(如使用 MySQL Group Replication、Galera Cluster、Tungsten Replicator 等分布式协议)。 而真正的双活也需要解决以下问题:

  • 主键冲突:两个节点生成相同主键(需使用 UUID 或全局唯一 ID 生成器)。
  • 循环复制:避免 A → B → A 的无限复制循环(需配置 server_id 和过滤规则)。
  • 事务一致性:确保跨节点的事务最终一致(需借助分布式事务或冲突检测机制)。

考虑到咱们服务硬件环境以及知识储备的局限性,这里就不做深层说明了,有能力的小伙伴可自行去研究一下,这里仅做简单说明一下~ 

你可能感兴趣的:(服务器与运维,数据库,mysql,架构,数据库,keepalived)