生产实战:Keepalived + Nginx + MySQL 双向复制高可用架构部署与原理详解

前言

在生产环境中,为了避免数据库访问入口出现单点故障,常采用 Keepalived + 双 Nginx + MySQL 双向复制 的架构。

整套架构实际上分为三层:

  • Keepalived: 负责 Nginx 代理入口的高可用,控制 VIP 归属。

  • Nginx: 负责 MySQL 的 TCP 连接转发,指定首选和备用数据库。

  • MySQL: 负责数据存储和双向复制。

本文将结合实际部署经验,详细介绍组件之间的工作机制、三个核心配置文件、故障切换过程,以及生产环境可能存在的风险。

安全说明: 所有服务器 IP 均使用 192.0.2.0/24 文档专用地址段,认证信息使用示例值,不包含真实生产环境 IP 和密码。


一、整体架构设计

1.1 服务器规划

角色 示例 IP 软件 / 功能
代理节点 01 192.0.2.101 Nginx + Keepalived
代理节点 02 192.0.2.102 Nginx + Keepalived
统一入口 VIP 192.0.2.100 Keepalived 管理
数据库节点 01 192.0.2.103 MySQL,首选后端
数据库节点 02 192.0.2.104 MySQL,备用后端

本篇通用部署示例统一使用:

  • 应用访问 Nginx:13306

  • Nginx 访问 MySQL:3306

注意端口差异: 原始环境的 Nginx 配置把两个数据库后端也写成了 13306,说明实际 TCP 目标端口并非默认的 3306。这可能是 MySQL 自定义监听端口,也可能是另一层数据库代理或端口映射,不能仅凭 Nginx 配置确定。本文为了方便新环境部署,使用后端 3306 的标准示例。实际部署必须以数据库真实监听端口为准。

1.2 整体架构拓扑

以下以 VIP 当前位于 Nginx 节点 02 为例:

复制代码
                   +----------------------+
                   |  Application / APP   |
                   +----------------------+
                              |
                              v
                  VIP: 192.0.2.100:13306
                              |
                              v
                    Keepalived / VRRP
                              |
               +--------------+--------------+
               |                             |
               |                             |
       +-------v--------+           +--------v-------+
       |  Nginx Node-01 |           | Nginx Node-02  |
       |  192.0.2.101   |           | 192.0.2.102    |
       |  BACKUP        |           | MASTER         |
       |  No VIP        |           | VIP Owner      |
       +----------------+           +--------+-------+
                                             |
                                             v
                                   Nginx Stream :13306
                                             |
                               +-------------+-------------+
                               |                           |
                               v                           v
                      +----------------+          +----------------+
                      |    MySQL 01    |          |    MySQL 02    |
                      | 192.0.2.103    |          | 192.0.2.104    |
                      | Port: 3306     |          | Port: 3306     |
                      | PRIMARY TARGET |          | BACKUP TARGET  |
                      +----------------+          +----------------+
                               ^                           ^
                               |                           |
                               +------ Replication --------+

拓扑解释:

  1. 应用统一连接 192.0.2.100:13306。

  2. Keepalived 决定 VIP 当前归属哪个 Nginx 节点。

  3. 持有 VIP 的 Nginx 接收数据库 TCP 连接。

  4. Nginx 正常情况下优先连接 MySQL 01。

  5. 首选数据库出现符合条件的 TCP 建连故障时,Nginx 可以尝试 MySQL 02。

  6. 两台 MySQL 通过双向复制同步数据。

图中的 MASTER/BACKUP 指的是 Keepalived 的 VRRP 角色;PRIMARY TARGET/BACKUP TARGET 指的是 Nginx 的代理路由角色,并不自动等同于 MySQL 的数据库主从身份。


二、为什么需要 Keepalived?

2.1 单台 Nginx 的问题

假设只有一台 Nginx:

复制代码
          Application
               |
               v
      Nginx 192.0.2.101
               |
               v
           MySQL Group
               |
        +------+------+
        |             |
     MySQL 01      MySQL 02

这种架构可以实现 MySQL TCP 连接的首选和备用转发。

但如果 Nginx 所在服务器故障,即使两台 MySQL 都正常,应用仍可能无法连接数据库。

因此,单台 Nginx 存在代理入口单点故障。

2.2 双 Nginx + Keepalived 的改进

部署两台 Nginx,并通过 Keepalived 管理 VIP。

正常情况下只有一个节点持有 VIP,另一个节点等待接管。

当持有 VIP 的服务器停止正常参与 VRRP 时,备用节点可以通过选举接管 VIP。

2.3 VIP 故障漂移示意图

正常状态:

复制代码
APP
 |
 v
VIP 192.0.2.100
 |
 v
Nginx-02 (MASTER)
 |
 v
MySQL-01 (Primary Target)

Nginx-01 = BACKUP / No VIP

代理节点 02 故障后:

复制代码
APP
 |
 v
VIP 192.0.2.100
 |
 v
Nginx-01 (NEW MASTER)
 |
 v
MySQL-01 (Primary Target)

Nginx-02 = DOWN

可以看到:

  • VIP 地址没有变化。

  • 应用无需修改数据库连接地址。

  • 处理新连接的 Nginx 节点发生变化。

  • MySQL 的首选后端不因 VIP 漂移而自动改变。

已有 TCP 连接可能中断,需要由应用连接池重新建立连接。


三、三个最核心的配置文件

代理层核心配置只有三个:

文件 作用
/etc/keepalived/keepalived.conf 控制 VIP 归属与 VRRP 选举
/app/nginx1.20/conf/nginx.conf Nginx 主配置,加载 Stream 模块配置
/app/nginx1.20/conf/tcp.d/mysql-tcp-13306.conf 数据库 TCP 转发与主备后端选择

两个 Nginx 节点均需要这三个配置文件。

其中 Keepalived 的节点地址与优先级不同,Nginx 的代理配置通常保持一致。


四、Keepalived 配置详解

4.1 节点 01 配置

文件:

/etc/keepalived/keepalived.conf

复制代码
global_defs {
    router_id proxy-node-01
}

vrrp_instance VI_1 {
    state BACKUP
    interface ens192
    virtual_router_id 42
    priority 100

    nopreempt

    unicast_src_ip 192.0.2.101
    unicast_peer {
        192.0.2.102
    }

    advert_int 1

    authentication {
        auth_type PASS
        auth_pass DEMOPASS
    }

    virtual_ipaddress {
        192.0.2.100/24
    }
}

4.2 节点 02 配置

复制代码
global_defs {
    router_id proxy-node-02
}

vrrp_instance VI_1 {
    state BACKUP
    interface ens192
    virtual_router_id 42
    priority 99

    nopreempt

    unicast_src_ip 192.0.2.102
    unicast_peer {
        192.0.2.101
    }

    advert_int 1

    authentication {
        auth_type PASS
        auth_pass DEMOPASS
    }

    virtual_ipaddress {
        192.0.2.100/24
    }
}

DEMOPASS 是演示值,部署时需替换为符合环境要求的认证配置。VRRP PASS 认证不是强加密认证,仍需做好网络隔离与访问控制。

4.3 核心参数解释

参数 说明
state BACKUP 节点启动时进入 BACKUP 初始状态
interface ens192 VIP 所绑定的网卡
virtual_router_id 42 VRRP 实例标识,两节点保持一致
priority 100/99 节点优先级
nopreempt 禁止优先级高的节点主动抢占正常 MASTER
unicast_src_ip 本机 VRRP 通信源 IP
unicast_peer 对端 Keepalived 地址
advert_int 1 VRRP 通告间隔约为 1 秒
virtual_ipaddress 配置需要漂移的 VIP

router_id 是实例标识,并不是 VIP 选举使用的 virtual_router_id。

4.4 Keepalived 如何判断 VIP 何时漂移?

Keepalived 主要依靠 VRRP 通告判断当前 MASTER 是否仍正常参与选举。

正常情况下:

复制代码
Nginx-02 / MASTER
        |
        | VRRP advertisement
        | approximately every 1s
        v
Nginx-01 / BACKUP

如果 BACKUP 持续收不到有效的 MASTER 通告,满足超时条件后,就会尝试接管 VIP。

以 advert_int=1、BACKUP 优先级 100 为例,标准 VRRP 主节点失联超时理论值约为:

3+\\frac{256-100}{256}\\approx3.61\\text{秒}

实际业务恢复时间还取决于网络邻居表更新、应用重连等因素。

4.5 为什么 priority=100 的节点可能没有 VIP?

因为启用了:

复制代码
nopreempt

这意味着当优先级为 99 的节点已经正常成为 MASTER 时,优先级为 100 的节点不会仅因为优先级更高就主动抢占它。

但 nopreempt 并不意味着永远不会进行故障接管。

4.6 Keepalived 是否监控 Nginx?

这里必须重点说明:

本文参考的原始架构中,没有配置 Nginx 健康检查脚本。

因此:

  • Nginx 所在服务器宕机:通常可以通过 VRRP 超时触发接管。

  • Keepalived 停止正常参与 VRRP:备用节点通常可以接管。

  • 只有 Nginx 进程停止:Keepalived 不会仅凭现有配置直接感知该故障。

后续若希望 Nginx 进程故障也触发 VIP 接管,需要另外设计 vrrp_script、track_script 等健康检查联动机制,并在测试环境验证。


五、Nginx 主配置文件

5.1 配置路径

/app/nginx1.20/conf/nginx.conf

两台服务器通常使用一致的 Nginx 主配置。

以下是精简后的通用参考配置:

复制代码
worker_processes auto;
worker_rlimit_nofile 20960;

error_log logs/error.log crit;

events {
    worker_connections 4096;
    multi_accept on;
    accept_mutex on;
    accept_mutex_delay 500ms;
}

http {
    server_tokens off;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    access_log off;

    include mime.types;
    default_type application/octet-stream;

    keepalive_timeout 300;
    client_max_body_size 10240M;

    gzip on;
    gzip_min_length 1k;
    gzip_buffers 4 16k;
    gzip_comp_level 6;

    gzip_types text/plain text/css
               text/javascript application/json
               application/javascript application/xml;

    include /app/nginx1.20/conf/http.d/*.conf;
}

stream {
    log_format proxy
        '$remote_addr [$time_local] '
        '$protocol $status '
        '$bytes_sent $bytes_received '
        '$session_time "$upstream_addr" '
        '"$upstream_bytes_sent" '
        '"$upstream_bytes_received" '
        '"$upstream_connect_time"';

    access_log
        /app/nginx1.20/logs/tcp-access.log proxy;

    include /app/nginx1.20/conf/tcp.d/*.conf;
}

client_max_body_size 等参数沿用了原架构设计思路,实际部署应根据业务需求和安全基线重新评估,不必机械照搬。

5.2 HTTP 和 Stream 的区别

http 主要处理:

  • Web 应用。

  • HTTP/HTTPS 反向代理。

  • 基于 HTTP 协议的负载均衡。

stream 主要处理:

  • TCP 代理。

  • UDP 代理。

  • MySQL 等非 HTTP 协议连接。

MySQL 使用 TCP 协议,因此需要使用 stream 进行透明 TCP 转发。

5.3 最关键的是 include

复制代码
stream {
    include /app/nginx1.20/conf/tcp.d/*.conf;
}

这表示 Nginx 将 tcp.d 目录下符合条件的 .conf 文件作为 Stream 配置加载。

MySQL 首选后端、备用后端、代理监听端口等,都在这个目录下配置。


六、Nginx MySQL 主备代理配置

6.1 核心配置

文件:

/app/nginx1.20/conf/tcp.d/mysql-tcp-13306.conf

复制代码
upstream mysql_backend {
    server 192.0.2.103:3306
           weight=5
           max_fails=2
           fail_timeout=2s;

    server 192.0.2.104:3306
           weight=5
           backup;
}

server {
    listen 13306 so_keepalive=on;

    proxy_connect_timeout 8s;
    proxy_timeout 300s;

    proxy_pass mysql_backend;
}

这是标准 MySQL 后端 3306 的示例。

6.2 端口转发关系

复制代码
APP
 |
 | TCP :13306
 v
Nginx
 |
 | proxy_pass mysql_backend
 |
 +---- MySQL-01 192.0.2.103:3306
 |
 +---- MySQL-02 192.0.2.104:3306
                   (backup only)

这里有两个不同概念:

  • listen 13306:Nginx 对应用开放的端口。

  • server IP:3306:Nginx 连接后端 MySQL 时使用的端口。

前端端口和后端端口不要求相同。

如果数据库实际监听 13306,则需要把 upstream 中的 3306 替换为真实后端端口。

6.3 Nginx 怎么知道哪个 MySQL 是首选?

关键就在:

复制代码
server 192.0.2.103:3306 ...;
server 192.0.2.104:3306 backup;

backup 是备用后端标记。

正常情况下,MySQL 01 作为普通后端参与调度,MySQL 02 不参与正常新连接分配。

因此即使两个节点都配置 weight=5,也不会因为权重相同而在正常情况下各分到 50% 连接。

6.4 Nginx 如何判断 MySQL 01 不可用?

关键参数:

复制代码
max_fails=2
fail_timeout=2s
proxy_connect_timeout 8s
参数 功能
max_fails=2 达到指定失败次数时临时标记后端不可用
fail_timeout=2s 失败统计窗口及临时不可用时长
proxy_connect_timeout=8s TCP 后端建连超时
backup 将第二个数据库设为备用

当前使用的是被动故障检测。

Nginx 不会主动登录 MySQL,也不会定期执行 SQL 判断复制状态。

它根据真实业务连接过程中发生的 TCP 建连错误和超时记录失败。

6.5 首选 MySQL 故障切换流程

正常状态:

复制代码
APP
 |
 v
Nginx
 |
 v
MySQL-01 :3306
 |
 +-- TCP CONNECTED

首选后端 TCP 建连失败:

复制代码
APP
 |
 v
Nginx
 |
 +----X----> MySQL-01 :3306
 |           CONNECT FAILED
 |
 +---------> MySQL-02 :3306
             BACKUP ATTEMPT

特别注意:由于 Stream 的 proxy_next_upstream 默认启用,某次连接 MySQL 01 失败时,就可以尝试 MySQL 02。

不一定需要先累计两次失败。

max_fails=2 的作用是临时不可用判定,而不是每次请求切换所必需的失败次数。

没有配置 upstream zone 时,Nginx 多个 Worker 的失败状态也可能不是统一共享的。

6.6 重要限制

Nginx 判断的是 TCP 建连能力。

它不能仅凭当前配置判断:

  • MySQL 是否允许写入。

  • SQL 是否执行正常。

  • 数据库复制是否中断。

  • MySQL 主从角色是否变化。

  • 两台数据库的数据是否已经同步。

因此,该配置实现的是 TCP 连接主备代理,而不是数据库层面的自动主库选举。


七、MySQL 双向复制与单写模式

7.1 双向复制架构

复制代码
 +------------------+      +------------------+
 |     MySQL-01     |      |     MySQL-02     |
 | 192.0.2.103:3306 |<---->| 192.0.2.104:3306 |
 +------------------+      +------------------+
         Replication in both directions

双向复制说明两个数据库之间具有相互复制关系,但不意味着已经实现自动主库仲裁。

7.2 为什么需要单写?

两台 MySQL 即使都允许写入,也可能因为同时修改相同数据而产生复制冲突。

因此,在采用单写设计的业务系统中,需要确保正常业务写入集中到被认可的写入节点。

Nginx 可以实现正常情况下的单目标代理:

复制代码
Normal:
APP -> Nginx -> MySQL-01

TCP failover:
APP -> Nginx -> MySQL-02

但这只是代理路由规则。

7.3 为什么不能单靠 Nginx 保证严格单写?

考虑以下故障恢复场景:

  1. MySQL 01 故障,Nginx 开始将部分新连接发往 MySQL 02。

  2. MySQL 02 上建立了业务连接。

  3. MySQL 01 恢复 TCP 连接能力。

  4. Nginx 再次把部分新连接优先发送到 MySQL 01。

  5. MySQL 02 上的旧连接可能仍未结束。

此时如果两个数据库都允许写入,就可能同时存在向不同数据库写入的业务会话。

因此还需要数据库层面的写入约束、故障隔离、数据一致性检查和切换管理。


八、生产环境故障场景

故障场景 处理组件 结果
当前持有 VIP 的服务器宕机 Keepalived 备用节点通常尝试接管 VIP
Keepalived 无法继续发送有效 VRRP 通告 Keepalived 可能触发主备切换
Nginx 进程单独停止 当前架构未配置直接检测 VIP 不一定漂移
MySQL 01 TCP 建连失败 Nginx 可以尝试 MySQL 02
MySQL 01 SQL 逻辑执行失败 数据库 / 应用 Nginx 不会仅凭 SQL 错误切换
MySQL 01 恢复 TCP 服务 Nginx 新连接可能重新优先访问 MySQL 01
MySQL 双向复制中断 数据库监控与运维机制 Nginx 不负责复制恢复

九、生产环境常用只读检查命令

9.1 查看 VIP 当前在哪个节点

分别在两台 Nginx 上执行:

复制代码
ip -br addr

确认 VIP 只绑定在预期节点。

9.2 查看 Keepalived 状态

复制代码
systemctl status keepalived --no-pager

9.3 查看 VRRP 切换日志

复制代码
journalctl -u keepalived --no-pager \
  | grep -Ei 'MASTER|BACKUP|FAULT|Entering'

9.4 查看 Nginx 进程

复制代码
ps -ef | grep '[n]ginx'

9.5 查看代理端口

复制代码
ss -lntp | grep 13306

9.6 查看 Nginx TCP 代理日志

复制代码
tail -n 50 /app/nginx1.20/logs/tcp-access.log

日志中的 $upstream_addr 可以帮助判断已记录的 TCP 会话究竟连接到哪个后端。

9.7 查看数据库实际监听端口

在数据库服务器上:

复制代码
ss -lntp | grep -E ':3306|:13306'

也可以通过已经建立的 MySQL 管理连接执行:

复制代码
SHOW VARIABLES LIKE 'port';

必须以实际监听端口为准,不可仅凭 MySQL 默认端口推断现网端口。


十、从零部署时的检查顺序

推荐按以下顺序搭建:

  1. 准备两台代理服务器,确认网络和软件版本。

  2. 安装支持 Stream 模块的 Nginx。

  3. 部署两台 Nginx 的主配置和数据库 TCP 代理配置。

  4. 确认后端 MySQL 实际监听端口,以及数据库复制和写入角色机制。

  5. 在新环境校验 Nginx 配置。

  6. 部署两台 Keepalived,设置 VRRP 单播对端、优先级和 VIP。

  7. 校验 VRRP 通信与 VIP 归属。

  8. 通过测试客户端验证数据库连接流向。

  9. 在测试环境演练入口故障和数据库连接失败。

  10. 确认应用重连、故障恢复、数据库单写和复制一致性机制。

在新部署环境中,可以使用:

复制代码
/app/nginx1.20/sbin/nginx -t \
  -c /app/nginx1.20/conf/nginx.conf

校验 Nginx 配置。

但在已有生产环境中,应遵循变更流程,不应随意通过停止服务模拟故障。


十一、常见问题

Q1:为什么已经有 Nginx,还需要 Keepalived?

因为普通 Nginx 本身不管理 VRRP VIP 漂移。Keepalived 为代理入口提供节点级故障接管能力。

Q2:两台 Nginx 是否必须组成双活集群?

不需要。本文采用主备模式,正常情况下只有一个节点处理通过 VIP 进入的连接。

Q3:两台 Nginx 配置为什么一样?

这样无论 VIP 在哪个节点,后端 MySQL 调度规则都保持一致。

Q4:没有 VIP 的那台 Nginx 是否停止工作?

不是。它仍然运行,只是不处理通过这个 VIP 进入的新连接;通过真实 IP 直接访问的业务仍可能由它处理。

Q5:MySQL 默认是 3306,为什么 Nginx 使用 13306?

因为 Nginx 对客户端提供的代理端口可以独立设置。应用访问 Nginx 的 13306,Nginx 可以转发到 MySQL 的 3306。若后端也配置为 13306,则以该后端实际服务端口为准。

Q6:Nginx 如何知道哪个数据库是主库?

它并不会自动识别 MySQL 主库,而是按照 upstream 中的普通后端和 backup 标记选择 TCP 连接目标。

Q7:MySQL 01 恢复后,Nginx 是否会自动重新使用它?

后续新连接可能重新优先选择 MySQL 01,但原先连接到 MySQL 02 的会话不会自动迁移,数据库写入安全需要独立保证。


十二、总结

这套架构的核心可以用三句话概括:

Keepalived 管入口: 决定 VIP 当前位于哪个 Nginx 节点。

Nginx 管连接: 按照 upstream 中的 backup 和失败判定规则,决定新连接使用哪个 MySQL 后端。

MySQL 管数据: 双向复制负责数据同步,但写入角色仲裁和数据一致性保障需要单独设计。

最重要的是明确两类切换:

  • VIP 漂移: 发生在 Nginx 代理入口层,由 Keepalived 负责。

  • 数据库连接转发: 发生在 MySQL 代理层,由 Nginx Stream 负责。

这两层高可用相互独立。即使全部配置正常,也不能将 TCP 自动重连等同于数据库无损切换。

未来部署类似架构时,首先保存 Keepalived 主配置、Nginx 主配置和 MySQL Stream 子配置,再结合数据库复制管理、应用重连和生产网络条件完成整体高可用设计。


官方参考资料

相关推荐
迪康Defender1 小时前
PHP 高危漏洞应急分析:从风险原理到端点侧纵深防护
运维·开发语言·安全·自动化·php·运维开发·远程工作
数字营销观察1 小时前
geo管理软件选型指南:从技术架构到场景落地的核心考量
架构
代钦塔拉1 小时前
【C++/Qt 进阶】设计模式系列(7):过滤器模式——把“筛选条件“变成可组合的积木
数据库·系统架构
TDengine (老段)1 小时前
TDengine IDMP 十大亮点功能(一)
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
为思念酝酿的痛1 小时前
MySQL的基本查询
数据库·mysql
青峰讲运维1 小时前
OpenStack Nova 计算服务深度解析|原理 + 架构 + 完整实操
架构·openstack
花间相见2 小时前
【计算基础|Redis01】【上手】安装连接、配置与通用命令
数据库·后端·面试
技术小事2 小时前
Surface 装 Ubuntu 双系统
linux·运维·ubuntu