Nginx 反向代理、负载均衡与 Keepalived 高可用
一、Nginx 反向代理与负载均衡
Nginx 作为高性能反向代理服务器,可实现后端服务的请求分发(负载均衡)与动静资源分离,有效提升系统并发能力与可用性。
1.1 核心概念辨析
1.1.1 正向代理 vs 反向代理
|------|---------------|-------------------|--------------|
| 类型 | 核心角色 | 典型场景 | 隐藏对象 |
| 正向代理 | 代理客户端访问目标服务 | 客户端突破网络限制(如访问外网) | 隐藏真实客户端 IP |
| 反向代理 | 代理后端服务接收客户端请求 | 后端服务集群负载均衡、隐藏服务地址 | 隐藏真实后端服务器 IP |
生产场景结合:正向代理(客户端侧)→ 反向代理(服务端侧),既保障客户端隐私,又隐藏后端服务架构,提升整体安全性。
1.1.2 负载均衡核心价值
- 请求分发:将客户端请求按规则分配到多个后端节点,避免单点过载;
- 故障自愈:自动剔除故障节点,确保服务不中断;
- 扩展性:通过增加后端节点轻松提升系统容量,无需修改客户端配置。
1.2 Nginx 负载均衡实现(upstream 模块)
Nginx 通过 upstream 模块定义后端服务集群,在 http 段内配置,支持多种调度算法,核心配置逻辑如下:
1.2.1 基础环境准备
假设有 3 台主机,角色与 IP 如下:
|-------------|----------------|-----------------|---------------|
| 主机角色 | IP 地址 | 安装服务 | 作用 |
| Nginx 代理 | 192.168.100.10 | Nginx | 接收客户端请求,分发至后端 |
| 后端节点 1(Rs1) | 192.168.100.20 | Httpd(或 Tomcat) | 提供静态 / 动态服务 |
| 后端节点 2(Rs2) | 192.168.100.30 | Httpd(或 Tomcat) | 提供静态 / 动态服务 |
环境初始化:
-
所有主机关闭防火墙与 SELinux:
# 关闭防火墙 systemctl stop firewalld && systemctl disable firewalld # 临时关闭 SELinux setenforce 0 # 永久关闭 SELinux(需重启) sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
-
后端节点(Rs1、Rs2)部署 Httpd 并创建测试页面:
Rs1 节点
yum -y install httpd
echo "rs1,test" > /var/www/html/index.html
systemctl start httpd && systemctl enable httpd
Rs2 节点(仅页面内容不同)
yum -y install httpd
echo "rs2,test" > /var/www/html/index.html
systemctl start httpd && systemctl enable httpd
1.2.2 负载均衡核心配置(3 种调度算法)
upstream 需定义在 nginx.conf 的 http 段内,通过 proxy_pass 指令将请求转发至集群。
(1)权重轮询(默认,weight)
按后端节点权重分配请求,权重值越大,分配到的请求越多,适用于节点性能差异场景。
http {
# 1. 定义后端集群(名称为 sl,可自定义)
upstream sl {
server 192.168.100.20 weight=2; # 权重 2,接收 2/3 请求
server 192.168.100.30; # 默认权重 1,接收 1/3 请求
}
# 2. 配置虚拟主机,转发请求至集群
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://sl; # 转发请求到 upstream 定义的集群
# 可选:添加代理请求头,传递客户端真实 IP 等信息
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}

(2)IP 哈希(ip_hash)
按客户端 IP 的哈希值分配请求,确保同一客户端始终访问同一后端节点,解决会话(Session)共享问题。
http {
upstream webserver {
ip_hash; # 启用 IP 哈希算法
server 192.168.100.20:80; # 后端节点 1(默认 80 端口可省略)
server 192.168.100.30:80; # 后端节点 2
# 节点故障时标记为 down,避免哈希重算导致会话失效
# server 192.168.100.40:80 down;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://webserver;
proxy_set_header Host $host;
}
}
}
注意:若后端节点故障,需用 down 标记(而非删除配置),否则 IP 哈希会重新计算,导致已建立的会话失效。如:server 192.168.100.10:8080 down

(3)最少连接(least_conn)
优先将请求分配给当前连接数最少的后端节点,适用于请求处理时间差异较大的场景(如动态服务)。
http {
upstream dynamic_server {
least_conn; # 启用最少连接算法
server 192.168.100.20:8080; # 假设后端为 Tomcat(8080 端口)
server 192.168.100.30:8080;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://dynamic_server;
}
}
}
1.2.3 配置验证与效果测试
-
检查 Nginx 配置语法并重载:
nginx -t # 语法检查,输出 "test is successful" 为正常 nginx -s reload # 平滑重载配置
-
客户端测试负载均衡:
多次访问 Nginx 代理 IP,观察返回内容(交替显示 Rs1/Rs2)

1.3 动静分离(结合反向代理)
动静分离指 Nginx 直接处理静态资源(如 HTML、图片、JS),动态请求(如 Java/PHP 接口)转发至后端服务(如 Tomcat、PHP-FPM),减少后端服务压力。
1.3.1 环境准备
新增 1 台 Tomcat 服务器(动态服务节点):
|-----------|----------------|------------|--------------------|
| 主机角色 | IP 地址 | 安装服务 | 作用 |
| Tomcat 节点 | 192.168.100.40 | Tomcat 8.5 | 处理动态请求(如 /test 接口) |
Tomcat 初始化:
# 安装 JDK(Tomcat 依赖)
yum -y install java-1.8.0-openjdk-devel
# 下载并启动 Tomcat
wget https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.93/bin/apache-tomcat-8.5.93.tar.gz
tar -zxvf apache-tomcat-8.5.93.tar.gz -C /usr/local/
ln -s /usr/local/apache-tomcat-8.5.93 /usr/local/tomcat
# 启动 Tomcat(默认 8080 端口)
/usr/local/tomcat/bin/startup.sh
1.3.2 动静分离配置
http {
# 1. 定义静态资源集群(Rs1、Rs2 提供静态文件)
upstream static_cluster {
server 192.168.100.20;
server 192.168.100.30;
}
# 2. 定义动态服务集群(Tomcat 节点)
upstream dynamic_cluster {
server 192.168.100.40:8080;
}
server {
listen 80;
server_name localhost;
# 3. 静态资源请求(匹配 .html、.jpg 等后缀)
location ~* \.(html|jpg|png|css|js)$ {
proxy_pass http://static_cluster;
proxy_set_header Host $host;
}
# 4. 动态请求(匹配 /test 路径)
location /test {
proxy_pass http://dynamic_cluster;
proxy_set_header Host $host;
# Tomcat 需配置 Context Path 为 /test(或调整路径匹配)
}
# 5. 默认请求(静态首页)
location / {
proxy_pass http://static_cluster;
index index.html;
}
}
}
关键说明:
- 静态资源通过正则 ~* \.(html|jpg|png|css|js)$ 匹配,不区分大小写;
- 动态请求通过路径 /test 匹配,转发至 Tomcat 处理;
- 静态资源一致性保障:通过 Rsync 定时同步各静态节点文件,或使用 NFS 共享存储。

二、Keepalived 高可用(解决 Nginx 单点故障)
Nginx 作为反向代理时,自身可能成为单点故障点。Keepalived 基于 VRRP(虚拟路由冗余协议) 实现 Nginx 双机热备,通过 VIP(虚拟 IP)对外提供服务,确保 Nginx 服务不中断。
2.1 Keepalived 核心原理
2.1.1 核心概念
- VIP(虚拟 IP) :集群对外统一访问地址(如 192.168.100.150),客户端通过 VIP 访问服务,无需关心后端 Nginx 节点;
- 主节点(MASTER):正常情况下持有 VIP,处理客户端请求;
- 备节点(BACKUP):实时监控主节点心跳,主节点故障时自动抢占 VIP,成为新主节点;
- 心跳检测:主备节点通过 VRRP 协议每秒发送一次心跳报文,备节点超时(默认 3 秒)未收到心跳则判定主节点故障。
2.1.2 高可用架构
假设有 2 台 Nginx 节点(主备),架构如下:
|-------|--------|-----------------|--------------------|-----------------|
| 节点角色 | 主机名 | IP 地址 | 安装服务 | 作用 |
| 主节点 | Master | 192.168.100.10 | Nginx + Keepalived | 持有 VIP,处理请求 |
| 备节点 | Slave | 192.168.100.40 | Nginx + Keepalived | 监控主节点,故障时接管 VIP |
| 虚拟 IP | - | 192.168.100.150 | - | 客户端统一访问地址 |
2.2 Keepalived 部署与配置
2.2.1 环境初始化(主备节点均执行)
-
安装 Keepalived 与 Nginx:
安装 Keepalived
yum -y install keepalived
安装 Nginx(主备节点 Nginx 配置需一致,如负载均衡规则)
yum -y install nginx
主节点创建测试页面
echo "master node" > /usr/share/nginx/html/index.html
备节点创建测试页面
echo "slave node" > /usr/share/nginx/html/index.html
启动 Nginx 并设置开机自启
systemctl start nginx && systemctl enable nginx
2.2.2 主节点(Master)配置
Keepalived 主配置文件路径:/etc/keepalived/keepalived.conf
! Configuration File for keepalived
# 1. 全局配置
global_defs {
router_id cy01 # 路由标识,同集群内唯一(备节点需不同)
}
# 2. 定义 VRRP 实例(核心配置)
vrrp_instance VI_1 {
state MASTER # 节点初始状态:MASTER(主)/BACKUP(备)
interface ens33 # 绑定的网卡接口(通过 ip addr 查看,如 eth0)
virtual_router_id 51 # 虚拟路由 ID,主备节点必须一致(0-255)
priority 100 # 优先级(主节点 > 备节点,如主 100,备 90)
advert_int 1 # 心跳发送间隔(1 秒)
# 3. 认证配置(主备节点必须一致,防止非法节点加入)
authentication {
auth_type PASS # 认证方式:密码认证
auth_pass 1111 # 认证密码(建议 8 位随机数)
}
# 4. 虚拟 IP(VIP)配置(可配置多个)
virtual_ipaddress {
192.168.100.150/24 # VIP 及子网掩码
}
}
# 5. 虚拟服务器配置(关联 VIP 与后端服务,可选)
virtual_server 192.168.100.150 80 {
delay_loop 6 # 健康检查间隔(6 秒)
lb_algo rr # 负载均衡算法(rr:轮询)
lb_kind DR # LVS 模式(DR:直接路由,性能最优)
persistence_timeout 50 # 会话保持时间(50 秒内同一客户端访问同一节点)
protocol TCP # 协议类型(TCP)
# 6. 后端真实服务节点(Nginx 主节点)
real_server 192.168.100.100 80 {
weight 1 # 权重
# TCP 健康检查(检查 80 端口是否存活)
TCP_CHECK {
connect_port 80 # 检查端口
connect_timeout 3 # 连接超时时间(3 秒)
nb_get_retry 3 # 重试次数
delay_before_retry 3 # 重试间隔(3 秒)
}
}
# 7. 后端真实服务节点(Nginx 备节点)
real_server 192.168.100.200 80 {
weight 1
TCP_CHECK {
connect_port 80
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
}
2.2.3 备节点(Slave)配置
备节点配置与主节点基本一致,仅需修改 3 处:
! Configuration File for keepalived
global_defs {
router_id cy02 # 路由标识:与主节点不同(如 cy02)
}
vrrp_instance VI_1 {
state BACKUP # 初始状态:BACKUP(备节点)
interface ens33
virtual_router_id 51 # 与主节点一致
priority 90 # 优先级:低于主节点(如 90)
advert_int 1
authentication {
auth_type PASS
auth_pass 1111 # 与主节点一致
}
virtual_ipaddress {
192.168.100.150/24 # 与主节点一致
}
}
# 虚拟服务器配置与主节点完全一致
virtual_server 192.168.100.150 80 {
delay_loop 6
lb_algo rr
lb_kind DR
persistence_timeout 50
protocol TCP
real_server 192.168.100.100 80 {
weight 1
TCP_CHECK {
connect_port 80
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
real_server 192.168.100.200 80 {
weight 1
TCP_CHECK {
connect_port 80
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
}
2.2.4 启动 Keepalived 并验证
1.
主备节点均执行:启动服务并设置开机自启
systemctl start keepalived
systemctl enable keepalived
2.验证 VIP 持有状态:
-
-
在主节点(Master) 执行 ip addr,可看到 VIP(192.168.100.150)绑定在网卡上:
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:0c:29:xx:xx:xx brd ff:ff:ff:ff:ff:ff
inet 192.168.100.100/24 brd 192.168.100.255 scope global noprefixroute ens160
inet 192.168.100.150/24 scope global secondary ens160 # VIP 绑定
...
-
-
- 在备节点(Slave) 执行 ip addr,默认无 VIP 绑定。
3.客户端访问验证:
客户端访问 VIP(192.168.100.150),应返回主节点 Nginx 页面(master node)。

2.3 Keepalived 进阶:监控 Nginx 服务(自动故障转移)
默认情况下,Keepalived 仅监控自身进程,若 Nginx 故障(Keepalived 仍运行),会导致服务中断。需通过自定义脚本让 Keepalived 监控 Nginx 状态,实现 Nginx 故障时自动切换主备。
2.3.1 编写监控脚本(主备节点均执行)
脚本内容(检测 Nginx 进程,若不存在则停止 Keepalived):
#!/bin/bash
# 检查 Nginx 进程是否存在(排除 grep 自身)
nginx_status=$(ps -ef | grep -v "grep" | grep -c "nginx: master process")
# 若 Nginx 进程数为 0(故障),停止 Keepalived(触发备节点接管)
if [ $nginx_status -eq 0 ]; then
systemctl stop keepalived
fi
2.编写状态通知脚本(notify.sh,可选,用于发送切换邮件):
vim notify.sh
脚本内容(主备切换时启动 / 停止 Nginx,并发送邮件通知):
#!/bin/bash
VIP=$2 # 接收 VIP 参数
# 邮件发送函数(需提前配置系统邮件服务,如 postfix)
send_mail() {
subject="${VIP} Keepalived 状态切换通知"
content="$(date +'%F %T'): $(hostname) 节点状态切换为 $1"
echo "$content" | mail -s "$subject" admin@example.com # 替换为实际邮箱
}
# 根据状态参数执行操作
case "$1" in
master) # 切换为主节点:启动 Nginx + 发送邮件
nginx_status=$(ps -ef | grep -v "grep" | grep -c "nginx")
if [ $nginx_status -eq 0 ]; then
systemctl start nginx
fi
send_mail "master"
;;
backup) # 切换为备节点:停止 Nginx + 发送邮件(避免端口冲突)
nginx_status=$(ps -ef | grep -v "grep" | grep -c "nginx")
if [ $nginx_status -gt 0 ]; then
systemctl stop nginx
fi
send_mail "backup"
;;
*)
echo "Usage: $0 master|backup VIP"
;;
esac
3.赋予脚本执行权限:
chmod +x /scripts/check.sh /scripts/notify.sh
2.3.2 配置 Keepalived 调用脚本(主节点关键修改)
修改主节点 keepalived.conf,添加脚本调用逻辑:
! Configuration File for keepalived
global_defs {
router_id cy01
}
# 1. 定义脚本:调用 Nginx 检查脚本,每 10 秒执行一次,故障时降低优先级 20
vrrp_script nginx_check {
script "/scripts/check.sh" # 脚本路径
interval 10 # 执行间隔(10 秒)
weight -20 # 脚本返回失败时,节点优先级降低 20
}
vrrp_instance VI_1 {
state MASTER
interface ens160
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.100.150/24
}
# 2. 关联脚本:让 VRRP 实例监控 Nginx 状态
track_script {
nginx_check
}
# 3. 状态切换时调用通知脚本(主备切换触发)
notify_master "/scripts/notify.sh master 192.168.100.150"
notify_backup "/scripts/notify.sh backup 192.168.100.150"
}
# 虚拟服务器配置不变(同 2.2.2 节)
virtual_server 192.168.100.150 80 {
...
}
备节点配置:仅需添加 notify 脚本调用(无需 vrrp_script,因备节点无需主动检查 Nginx):
vrrp_instance VI_1 {
state BACKUP
interface ens160
virtual_router_id 51
priority 90
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.100.150/24
}
# 备节点仅需状态通知
notify_master "/scripts/notify.sh master 192.168.100.150"
notify_backup "/scripts/notify.sh backup 192.168.100.150"
}
2.3.3 模拟故障测试
-
主节点 Nginx 故障:
在主节点停止 Nginx
systemctl stop nginx
-
- 等待 10 秒(脚本执行间隔),Keepalived 检测到 Nginx 故障,自动停止自身进程;
-
- 在备节点执行 ip addr,可看到 VIP(192.168.100.150)已绑定;
-
- 客户端访问 VIP,返回备节点页面(Slave Node: 192.168.100.200),切换成功。

-
主节点恢复:
在主节点重启 Nginx 和 Keepalived
systemctl start nginx
systemctl start keepalived
-
- 主节点优先级(100)高于备节点(90),会重新抢占 VIP;
-
- 备节点检测到主节点心跳,自动释放 VIP,恢复为备角色。
2.4 Keepalived 脑裂现象(核心风险与解决方案)
2.4.1 什么是脑裂?
脑裂(Split Brain)是 Keepalived 高可用集群的典型故障,指主备节点因心跳通信中断,各自误认为对方故障,同时抢占 VIP 并对外提供服务,导致 "双主" 状态,引发 IP 冲突、数据不一致等问题。
2.4.2 脑裂的触发原因
核心原因是主备节点心跳中断,具体场景如下:
|------|------------------------------------------------------------------------------------------------|
| 中断类型 | 具体场景 |
| 网络故障 | 1. 主备节点间的心跳网卡(如 ens160)故障、网线松动;2. 交换机 / 路由器故障导致网络分区;3. 防火墙规则误阻断 VRRP 协议(默认使用 224.0.0.18 多播地址)。 |
| 资源耗尽 | 主节点 CPU / 内存 / 磁盘 I/O 耗尽,导致 Keepalived 进程无法发送心跳; |
| 进程异常 | Keepalived 进程崩溃或被误杀,无法生成心跳报文。 |
2.4.3 脑裂的危害
- VIP 冲突:主备节点同时持有 VIP,客户端访问时出现 "时而通、时而不通",服务可用性下降;
- 数据不一致:若后端挂载共享存储(如 NFS),双主节点同时读写会导致数据覆盖或损坏;
- 资源竞争:双主节点抢占服务端口(如 80),可能导致进程崩溃或资源泄漏。
2.4.4 脑裂的预防与解决方案
核心思路是增强心跳可靠性 和引入第三方仲裁,避免单一节点自判故障。
方案 1:双心跳通道(避免单一网络故障)
为主备节点配置两条独立的心跳通道(如双网卡、双交换机),即使一条通道中断,另一条仍可传递心跳:
# 主节点 keepalived.conf 新增第二个 VRRP 实例(第二条心跳通道)
vrrp_instance VI_2 {
state MASTER
interface ens192 # 第二条心跳网卡(与 VI_1 不同)
virtual_router_id 52 # 虚拟路由 ID 与 VI_1 不同(避免冲突)
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 2222 # 独立认证密码
}
# 第二条通道无需额外 VIP,仅用于心跳检测
virtual_ipaddress {
192.168.101.150/24 # 独立的心跳网段 IP
}
}
# 备节点对应配置 vrrp_instance VI_2,state 设为 BACKUP,priority 设为 90
方案 2:第三方仲裁(Quorum 机制)
引入独立的仲裁节点(如监控服务器、共享存储),主备节点需同时满足 "心跳正常" 和 "仲裁通过",才会抢占 VIP,避免自判失误。
示例:基于共享存储的仲裁
-
部署一台共享存储服务器(如 192.168.100.250),提供 NFS 共享目录 /mnt/quorum;
-
主备节点挂载共享目录:mount -t nfs 192.168.100.250:/mnt/quorum /mnt/quorum;
-
修改 Nginx 检查脚本(check.sh),添加仲裁逻辑:
#!/bin/bash
nginx_status=$(ps -ef | grep -v "grep" | grep -c "nginx")
检查共享存储是否可写(仲裁节点存活)
quorum_status=(echo "test" > /mnt/quorum/(hostname)_alive.txt 2>&1; echo $?)
仅当 Nginx 正常 AND 仲裁通过,才维持 Keepalived;否则停止
if [ nginx_status -eq 0 ] || [ quorum_status -ne 0 ]; then
systemctl stop keepalived
fi
-
- 若主节点仅心跳中断但仲裁正常,会判定自身网络故障,不抢占 VIP;
-
- 仅当 "心跳中断 + 仲裁失败",才判定对方故障,避免脑裂。
方案 3:配置非抢占模式(降低切换风险)
通过 nopreempt 指令设置主节点为 "非抢占模式",仅当备节点成为主节点后,主节点恢复时不会主动抢占 VIP,需手动干预或通过脚本控制,减少脑裂后的冲突:
vrrp_instance VI_1 {
state MASTER
interface ens160
virtual_router_id 51
priority 100
advert_int 1
nopreempt # 开启非抢占模式
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.100.150/24
}
}
三、整体架构总结
3.1 完整架构链路
客户端 → VIP(192.168.100.150)→ Keepalived(主备切换)→ Nginx(反向代理 / 负载均衡)→ 后端服务:
- 静态资源:Rs1(192.168.100.20)、Rs2(192.168.100.30);
- 动态服务:Tomcat(192.168.100.40:8080)。
3.2 核心功能价值
|----------------|-----------------|-----------------|
| 模块 | 核心作用 | 解决的问题 |
| Nginx 反向代理 | 隐藏后端服务地址,统一入口 | 服务安全性、客户端配置简化 |
| Nginx 负载均衡 | 按规则分发请求,剔除故障节点 | 单点过载、服务可用性提升 |
| 动静分离 | 静态资源本地处理,动态请求转发 | 后端服务压力降低、响应速度提升 |
| Keepalived 高可用 | VIP 漂移、主备切换 | Nginx 单点故障风险 |
| 脑裂防护 | 双心跳、第三方仲裁 | 双主冲突、数据不一致 |
通过上述配置,可构建一套高可用、高并发的 Web 服务架构,满足生产环境中对稳定性和性能的核心需求。