Nginx反向代理和负载均衡

作者:

在

  • Nginx反向代理配置实例

image-20260826182127273

  • web03服务器,配置一个网站,监听在8080
[root@web03 ~]# cd /etc/nginx/conf.d/
[root@web03 conf.d]# vim web.conf
server {
    listen 8080;
    server_name localhost;

    location / {
        root /code/8080;
        index index.html;
        allow all;
    }
}
[root@web03 conf.d]# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@web03 conf.d]# systemctl restart nginx
[root@web03 ~]# mkdir -p /code/8080
[root@web03 ~]# echo "listening 8080 ..." > /code/8080/index.html
  • proxy代理服务,配置监听80端口,使能够通过代理服务器访问到后端的192.168.226.134的8080端口站点内容
[root@proxy ~]# cd /etc/nginx/conf.d/
[root@proxy conf.d]# vim proxy_web_node1.conf
server {
    listen 80;
    server_name proxy.test.com;

    location / {
        proxy_pass http://192.168.226.134:8080;
    }
}
[root@proxy conf.d]# nginx -t
[root@proxy conf.d]# systemctl restart nginx
  • 存在的问题,通过抓包可以看到客户端是使用域名对网站进行访问的,但是代理却是使用的IP地址加端口号
  • 当访问80端口的时候,没有域名的情况下,默认会去找排在最上面的那个配置文件。
  • 所以我们需要解决这个问题,保留住最开始的请求头部信息。

架构说明

text

客户端服务器 (192.168.226.128)
    ↓ 访问 http://proxy.test.com (域名)
    ↓ hosts: 192.168.226.132 proxy.test.com
代理服务器 (192.168.226.132) 
    ↓ 监听 80端口
    ↓ proxy_pass http://192.168.226.134:8080
    ↓ 转发请求(保留原始头信息)
后端Web服务器 (192.168.226.134)
    ↓ 监听 8080端口
    ↓ 返回 "listening 8080 ..."

实践

  1. 后端web服务器

image-20260826184055480

  1. 中间代理服务器proxy_server

image-20260826201921312

  1. 后端业务处理服务器

image-20260826201959024

1. 客户端服务器(192.168.226.128)

角色:访问发起方 / 用户端

职责:

  • 发起 HTTP 请求,目标域名是 http://proxy.test.com
  • 通过本地 /etc/hosts 文件,将域名 proxy.test.com 解析为代理服务器的 IP 192.168.226.132
  • 不参与任何 Nginx 配置,纯粹是”用户”角色

简单理解:就是”用户”或”浏览器”


2. 代理服务器(192.168.226.132)

角色:反向代理 / 入口网关

职责:

  • 监听 80 端口,接收来自客户端的所有请求
  • 域名匹配:识别 server_name proxy.test.com,处理该域名的请求
  • 转发请求:通过 proxy_pass 将请求转发给后端服务器 http://192.168.226.134:8080
  • 保留原始请求信息:通过 proxy_set_header 将客户端的真实IP、域名等信息传递给后端,让后端知道请求来自谁
  • 负载均衡(可选):如果后端有多个服务器,可以在此做分发

简单理解:就是”前台”或”门卫”,所有请求先到它这里,再由它转交给真正干活的人

关键配置:

nginx

server {
    listen 80;
    server_name proxy.test.com;

    location / {
        proxy_pass http://192.168.226.134:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

3. 后端 Web 服务器(192.168.226.134)

角色:真正的业务处理 / 应用服务器

职责:

  • 监听 8080 端口(避免和代理服务器的 80 端口冲突)
  • 提供业务内容:返回静态页面、处理业务逻辑、读取数据库等
  • 接收代理转发来的请求,处理完成后将结果返回给代理服务器,由代理服务器再返回给客户端
  • 可以配置多个,组成集群,由代理服务器做负载均衡

简单理解:就是”后台员工”或”真正干活的”,负责提供实际内容

关键配置:

nginx

server {
    listen 8080;
    server_name localhost;

    root /code/8080;
    index index.html;
}

架构原理

1. 代理服务器(132)标准配置

这是你作为入口网关的完整配置,每一条都有具体作用:

nginx

server {
    # 1. 监听端口
    listen 80;                     # 监听所有网卡的80端口(不要写127.0.0.1,否则外部访问不了)
    server_name proxy.test.com;    # 匹配客户端请求的域名

    location / {
        # 2. 转发目标(指向后端)
        proxy_pass http://192.168.226.134:8080;

        # 3. 【核心】保留原始域名(解决多站点路由问题)
        proxy_set_header Host $host;

        # 4. 【核心】传递真实客户端IP(解决日志溯源问题)
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # 5. 【核心】启用HTTP/1.1长连接(解决高并发性能问题)
        proxy_http_version 1.1;
        proxy_set_header Connection "";   # 清除默认的"close",启用Keep-Alive

        # 6. 【可选】传递原始协议(如果后端需要区分http/https)
        proxy_set_header X-Forwarded-Proto $scheme;

        # 7. 【可选】超时控制(防止后端响应慢导致连接断开)
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        # 8. 【可选】缓冲控制(平衡性能与实时性)
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
    }
}

2. 后端服务器(134)标准配置

这是真正提供内容的服务器配置:

nginx

server {
    listen 8080;                   # 监听8080端口(与代理的proxy_pass一致)
    server_name localhost;         # 这里实际接收的是代理传来的Host头

    root /code/8080;               # 网站根目录
    index index.html index.htm;    # 默认首页文件

    location / {
        # 允许所有访问(如果内网环境)
        allow all;
    }

    access_log /var/log/nginx/access.log main;
    error_log /var/log/nginx/error.log;
}

3. 客户端(128)配置

客户端只需要做好域名解析即可,无需配置Nginx:

bash

# 编辑 /etc/hosts,添加:
192.168.226.132  proxy.test.com

4. 完整请求流程(配图理解)

text

客户端(128)
    ↓ 访问 http://proxy.test.com
    ↓ /etc/hosts 解析 → 192.168.226.132
    ↓
代理服务器(132)
    ↓ listen 80; server_name proxy.test.com;
    ↓ proxy_pass → http://192.168.226.134:8080
    ↓ 传递:Host, X-Real-IP, X-Forwarded-For
    ↓ 使用:HTTP/1.1 长连接
    ↓
后端服务器(134)
    ↓ listen 8080;
    ↓ 根据 Host: proxy.test.com 匹配站点
    ↓ 记录日志:$http_x_forwarded_for = 真实客户端IP
    ↓
返回 "This is the server3 web" ✅

Nginx 反向代理高并发配置总结

核心配置

nginx

proxy_http_version 1.1;
proxy_set_header Connection "";

为什么要加?

对比项 默认(HTTP/1.0) 加配置后(HTTP/1.1)
连接方式 短连接(一次请求一建一断) 长连接(Keep-Alive,复用连接)
高并发后果 端口快速耗尽,大量 TIME_WAIT,新请求进不来 几十个固定连接反复使用,稳定高效
资源消耗 耗 CPU(频繁握手/挥手) 耗少量内存,节省 CPU 和时间

为什么要配两行?

  • proxy_http_version 1.1; → 告诉 Nginx 用 HTTP/1.1 协议向后端发请求
  • proxy_set_header Connection ""; → 删掉默认的 Connection: close,让连接保持不断开

缺一不可:只写第一行不写第二行,后端收到 Connection: close,长连接不会生效。

什么时候必须加?

  • ✅ 生产环境 / 高并发场景 → 必须加

image-20260827112822610


Nginx负载均衡

Nginx负载均衡基本概述

为什么要使用负载均衡

  • 当我们的Web服务器直接面向用户,往往要承载大量并发请求,单台服务器难以负荷,我使用多台Web服务器组成集群,前端使用Nginx负载均衡,将请求分散的打到我们的后端服务器集群中,实现负载的分发。那么会大大提升系统的吞吐率、请求性能、高容灾

image-20260827141033300

四层和七层负载均衡

一、Web服务 → 为什么选Nginx(七层)?

场景举例

  • 用户访问 http://proxy.test.com
  • 需要根据不同的URL转发到不同的后端(/api → API服务器,/static → 静态资源服务器)
  • 需要保持用户的会话(同一个用户的请求始终落到同一台服务器)
  • 需要记录客户端的真实IP

为什么Nginx能做这些?

Nginx是七层负载均衡,它会完整地接收客户端的HTTP请求,拆开”信封”,读取里面的内容:

text

GET /api/user HTTP/1.1
Host: proxy.test.com        ← Nginx能读到这个域名
Cookie: session_id=abc123   ← Nginx能读到这个Cookie
User-Agent: curl/7.76.1

基于这些信息,Nginx可以做:

功能 怎么实现 为什么四层做不了
域名路由 根据 Host: proxy.test.com 转发到对应的后端 四层看不到域名
URL路径匹配 location /api/ 转发到API服务器,location /static/ 转发到静态服务器 四层看不到URL
会话保持 根据Cookie里的 session_id 把同一用户固定到同一台后端 四层看不到Cookie
Header改写 添加 X-Forwarded-For 传递真实IP 四层无法修改HTTP头
重定向/rewrite 根据业务规则重写URL 四层无法操作应用层数据

选型结论

只要你的业务逻辑和HTTP协议相关(域名、URL、Cookie、Header),就必须用七层负载均衡(Nginx)。


二、数据库/端口转发 → 为什么选LVS(四层)?

场景举例

  • MySQL数据库集群:多个MySQL实例,需要把请求分发给不同的数据库服务器
  • Redis缓存集群:多个Redis节点,需要做读写分离
  • 游戏服务器:基于TCP长连接,转发玩家的游戏数据包
  • 端口映射:把公网的某个端口转发到内网某台机器的指定端口

为什么选LVS?

数据库和游戏服务器使用的是TCP协议,不是HTTP协议,它们的数据格式是自定义的二进制协议,没有HTTP头这种概念。

text

MySQL协议数据包:
[包头][包体]  ← 全是二进制,没有"域名"或"URL"这个字段

所以用Nginx(七层)来处理MySQL流量是无效的,因为Nginx只能解析HTTP协议,看不懂MySQL协议。

LVS(四层) 的做法是:

只改IP和端口,不碰数据包内容。

text

客户端(128) → 访问 192.168.226.10:3306 (VIP)
LVS(四层) → 把目标IP改成 192.168.226.20:3306 (后端MySQL)
数据包内容 → 原封不动,MySQL协议数据完全保留

为什么LVS效率高?

对比项 LVS(四层) Nginx(七层)
拆包 不拆,只看IP+端口 拆,读HTTP头
CPU消耗 极低 较高
延迟 微秒级 毫秒级
吞吐量 非常高 中等

选型结论

只要你的流量不是HTTP协议(如MySQL、Redis、游戏),就用四层负载均衡(LVS/HAProxy的tcp模式)。

三、实际生产架构:四层 + 七层混合使用

真正的大规模系统,通常四层和七层配合使用,各司其职:

text

互联网流量
    ↓
【四层负载均衡 LVS】(扛住千万级并发)
    ↓ 只做IP+端口转发,不拆包,效率极高
    ↓
【七层负载均衡 Nginx 集群】(多台)
    ↓ 拆包,读域名/URL/Cookie
    ↓
【后端应用服务器】(Tomcat、PHP、Node.js)

分工:

  • LVS(四层):站在最前面,负责扛住大流量,把请求分发给后端的多台Nginx。
  • Nginx(七层):负责做智能路由(按域名/URL分发),处理会话保持,传递真实IP。
  • 后端服务器:真正执行业务逻辑。

四、一句话总结

场景 用什么 核心原因
Web服务(HTTP) Nginx(七层) 需要读域名、URL、Cookie来做智能转发
数据库/端口转发(TCP) LVS(四层) 看不懂HTTP协议,只需要快速转包,效率最高
大规模生产 LVS+ Nginx混合 四层扛流量,七层做路由,各取所长

记住:七层能干四层的活(但没必要,效率低),四层干不了七层的活(因为看不懂HTTP)。这就是为什么Web服务必须用七层,而数据库转发用四层就够了。


Nginx upstream虚拟配置语法

Syntax: upstream name { ... }
Default: -
Context: http

#upstream例
upstream backend {
    server backend1.example.com       weight=5;
    server backend2.example.com:8080;
    server unix:/tmp/backend3;
    server backup1.example.com:8080   backup;
}
server {
    location / {
        proxy_pass http://backend;
    }
}

Web01服务器上配置Nginx

[root@web01 ~]# cd /etc/nginx/conf.d/
[root@web01 conf.d]# vim node.conf
server {
    listen 80;
    server_name node.test.com;
    location / {
        root /code/node;
        index index.html;
    }
}
[root@web01 conf.d]# mkdir -p /code/node
[root@web01 conf.d]# echo "web01 ..." > /code/node/index.html
[root@web01 conf.d]# systemctl restart nginx
# 关闭防火墙和SElinux
[root@web02 conf.d]# setenforce 0
[root@web02 conf.d]# systemctl stop firewalld

Web02服务器上配置Nginx

[root@web02 ~]# cd /etc/nginx/conf.d/
[root@web02 conf.d]# vim node.conf
server {
    listen 80;
    server_name node.test.com;
    location / {
        root /code/node;
        index index.html;
    }
}
[root@web02 conf.d]# mkdir -p /code/node
[root@web02 conf.d]# echo "web02 ..." > /code/node/index.html
[root@web02 conf.d]# systemctl restart nginx
# 关闭防火墙和SElinux
[root@web02 conf.d]# setenforce 0
[root@web02 conf.d]# systemctl stop firewalld

配置Nginx负载均衡

[root@lb01 ~]# cd /etc/nginx/conf.d/

[root@lb01 conf.d]# vim /etc/nginx/proxy_params
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_connect_timeout 30;
proxy_send_timeout 60;
proxy_read_timeout 60;

proxy_buffering on;
proxy_buffer_size 32k;
proxy_buffers 4 128k;

[root@lb01 conf.d]# vim node_proxy.conf
upstream node {
    server 192.168.226.20:80;
    server 192.168.226.30:80;
}
server {
    listen 80;
    server_name node.test.com;

    location / {
        proxy_pass http://node;
        include proxy_params;
    }
}

[root@lb01 conf.d]# nginx -t
[root@lb01 conf.d]# systemctl restart nginx

因为 upstream node { ... } 这个块定义了 node 这个名字,所以当你在 proxy_pass http://node; 里写 node 时,Nginx 就知道你要找的是这个 upstream 块里定义的服务器列表。


详细解释:node 是怎么变成后端地址的?

第一步:你定义了 upstream 块

nginx

upstream node {                    # ← 这里定义了一个"上游服务器组",名字叫 node
    server 192.168.226.20:80;      # 后端服务器1
    server 192.168.226.30:80;      # 后端服务器2
}

upstream 是 Nginx 的一个指令,作用是定义一个后端服务器组,并给它起个名字(这里叫 node)。

第二步:proxy_pass 引用这个名字

nginx

location / {
    proxy_pass http://node;        # ← 这里的 node 就是上面定义的 upstream 名字
}

当Nginx 看到 proxy_pass http://node; 时,它会:

  1. 去查找有没有定义过名叫 node 的 upstream 块
  2. 找到了,就按照 upstream node 里定义的服务器列表去转发
  3. 按照负载均衡算法(默认轮询),选中一台后端服务器
  4. 把请求转发给 192.168.226.20:80 或 192.168.226.30:80

类比理解

概念 类比
upstream node { ... } 你手机通讯录里存了一个联系人,名字叫”老王”,里面存了老王的手机号
proxy_pass http://node; 你发微信说”转发给老王”
实际转发到 192.168.226.20:80 微信后台查到”老王”的手机号,把消息发过去了

所以 node 只是一个别名,Nginx会根据这个别名去 upstream 块里找到真正的 IP 地址。


如果你不写 upstream 会怎样?

你也可以直接在 proxy_pass 里写 IP:

nginx

location / {
    proxy_pass http://192.168.226.20:80;   # 不经过 upstream,直接写死 IP
}

这样就不用定义 upstream node 了。但这样写的问题是:

  • 只能转发给一台后端服务器,做不了负载均衡
  • 如果后端服务器IP变了,需要手动改配置

所以生产环境都推荐用 upstream 方式,方便扩展和管理。


总结

node 不是自动生效的,是因为你在 upstream node 里定义了它,Nginx 才知道 node 代表什么。这是一种”先定义、后引用”的配置逻辑。

配置 作用
upstream node { server 20:80; server 30:80; } 定义:把 20 和 30 这两台机器打包,起名叫 node
proxy_pass http://node; 引用:使用上面定义的 node 组进行转发

实践

  1. 代理服务器

image-20260827163334072

Nginx 的 include 指令支持相对路径,相对的是 Nginx 的主配置文件所在目录,也就是 /etc/nginx/。

路径解析规则

写法 Nginx 实际查找路径
include proxy_params; /etc/nginx/proxy_params
include conf.d/node_proxy.conf; /etc/nginx/conf.d/node_proxy.conf
include /etc/nginx/proxy_params; /etc/nginx/proxy_params(绝对路径)

所以你在 /etc/nginx/conf.d/ 目录下写 include proxy_params;,Nginx并不会去 conf.d/ 里找,而是回到 /etc/nginx/ 目录去找这个文件。

image-20260827164253896

  1. 后端web服务器

image-20260827171051461

  1. 客户端,域名轮询访问

image-20260827171923649


Nginx负载均衡调度算法

调度算法 概述
轮询 按时间顺序逐一分配到不同的后端服务器(默认)
weight 加权轮询,weight值越大,分配到的访问几率越高
ip_hash 每个请求按访问IP的hash结果分配,这样来自同一IP的固定访问一个后端服务器
url_hash 按照访问URL的hash结果来分配请求,是每个URL定向到同一个后端服务器
least_conn 最少链接数,那个机器链接数少就分发

1. RR – 轮询(Round-Robin)

默认算法,请求按顺序轮流分发到后端服务器。

nginx

upstream load_pass {
    server 192.168.226.10:80;
    server 192.168.226.20:80;
}

特点:

  • 每个请求按时间顺序逐一分配到不同后端服务器
  • 适合后端服务器性能一致的场景
  • 无需额外配置

2. WR – 权重轮询(Weighted Round-Robin)

通过 weight 参数分配权重,权重越高分配到的请求越多。

nginx

upstream load_pass {
    server 192.168.226.10:80 weight=5;   # 权重5,处理5份请求
    server 192.168.226.20:80;            # 默认权重1,处理1份请求
}

特点:

  • 权重比例 = 5:1,每6次请求中5次去web01,1次去web02
  • 适合服务器性能不同的场景
  • 高性能服务器设置更高的权重

3. ip_hash – IP哈希

根据客户端IP地址进行哈希计算,确保同一客户端IP始终访问同一台后端服务器。

nginx

upstream load_pass {
    ip_hash;
    server 192.168.226.10:80;
    server 192.168.226.20:80;
}

特点:

  • 保持会话(Session)一致性
  • 适合有状态的应用(如购物车、登录状态)
  • 不能与 weight 一起使用
  • 如果所有客户端经过同一个代理访问,ip_hash 看到的是代理IP,会导致所有请求转发到同一台后端服务器

注意事项:

nginx

# ❌ 错误:ip_hash 不能和 weight 一起使用
upstream load_pass {
    ip_hash;
    server 192.168.226.10:80 weight=5;   # 会报错!
    server 192.168.226.20:80;
}

# ✅ 正确:ip_hash 单独使用
upstream load_pass {
    ip_hash;
    server 192.168.226.10:80;
    server 192.168.226.20:80;
}

4. least_conn – 最少连接

将请求分发给当前活动连接数最少的服务器。

nginx

upstream load_pass {
    least_conn;
    server 192.168.226.10:80;
    server 192.168.226.20:80;
}

特点:

  • 适合长连接场景(如WebSocket、数据库连接)
  • 避免请求堆积在某台服务器上

5. fair – 响应时间优先(第三方模块)

根据后端服务器的响应时间来分配请求,响应时间短的优先。

nginx

upstream load_pass {
    fair;
    server 192.168.226.10:80;
    server 192.168.226.20:80;
}

特点:

  • 需要安装第三方模块
  • 动态调整,响应快的服务器处理更多请求

二、服务器状态参数

nginx

upstream load_pass {
    server 192.168.226.10:80 weight=5 max_fails=3 fail_timeout=30s;
    server 192.168.226.20:80 weight=2;
    server 192.168.226.30:80 backup;
    server 192.168.226.40:80 down;
}

1. weight(权重)

nginx

server 192.168.226.10:80 weight=5;

说明:

  • 默认值为1
  • 权重值越高,分配到的请求越多
  • 适合后端服务器硬件配置不同的场景

2. max_fails(最大失败次数)

nginx

server 192.168.226.10:80 max_fails=3;

正确理解:

  • 在 fail_timeout 指定的时间窗口内,如果失败次数达到 max_fails 次,nginx 就认为该服务器不可用
  • 失败包括:连接超时、读取超时、后端返回 5xx 错误等
  • 默认值为 1,即一次失败就标记为不可用

3. fail_timeout(失败超时时间)

nginx

server 192.168.226.10:80 max_fails=3 fail_timeout=30s;

正确理解:

  • fail_timeout 有两个作用:
    1. 失败统计的时间窗口:在 30s 内如果失败3次,标记该服务器为不可用
    2. 标记不可用的持续时间:被标记为不可用后,nginx 会在 30s 后再次尝试连接该服务器

工作流程:

text

时间轴:
0s -------- 10s -------- 20s -------- 30s -------- 40s -------- 50s
|           |            |            |            |            |
请求1失败   请求2失败    请求3失败    标记不可用   尝试恢复    恢复成功
                                    (30s内3次失败) 
                                    (30s后自动尝试)

4. backup(备份服务器)

nginx

server 192.168.226.30:80 backup;

正确理解:

  • 只有当所有主服务器都不可用时,才会将请求转发给 backup 服务器
  • backup 服务器平时不处理请求,只作为备用
  • 用于高可用保障

5. down(下线标记)

nginx

server 192.168.226.40:80 down;

正确理解:

  • 永久标记该服务器为不可用
  • nginx 不会向该服务器转发任何请求
  • 常用于服务器维护或升级时,优雅地移除服务器
  • 需要恢复时,手动注释掉 down 或删除该行

三、参数对照表

参数 作用 默认值 注意事项
weight 权重,值越大分配请求越多 1
max_fails fail_timeout 时间内允许的最大失败次数 1 达到后标记为不可用
fail_timeout ① 失败统计时间窗口 ② 标记不可用的持续时间 10s 两个作用合并在一个参数中
backup 备份服务器,主服务器全挂时启用 无
down 永久标记为不可用 无 用于维护场景
max_conns 最大并发连接数 0(无限制) 需要商业版
resolve 动态解析域名 无 后端使用域名时配置

四、配置场景选择

场景 推荐算法 原因
无状态应用(API、静态资源) RR 轮询 简单、均衡、无需额外配置
服务器性能不同 WR 权重轮询 高性能服务器处理更多请求
需要会话保持(登录状态、购物车) ip_hash 同一用户始终访问同一后端
长连接、WebSocket 场景 least_conn 避免连接集中在某台服务器
响应时间敏感场景 fair 响应快的服务器优先处理

五、常见问题

Q1:ip_hash 计算的是代理服务器的IP吗?

A:默认计算 $remote_addr(客户端真实IP)。但如果客户端经过代理访问,nginx看到的是代理服务器的IP,会导致所有请求都转发到同一台后端服务器。

解决方案:

nginx

# 获取真实客户端IP
set_real_ip_from 代理服务器IP段;
real_ip_header X-Forwarded-For;

upstream load_pass {
    ip_hash;
    server 192.168.226.10:80;
    server 192.168.226.20:80;
}

Q2:max_fails 和 fail_timeout 如何配合工作?

A:在 fail_timeout 时间内,如果失败次数达到 max_fails 次,nginx 标记该服务器为不可用。经过 fail_timeout 时间后,nginx 会再次尝试连接。

text

示例配置:max_fails=3 fail_timeout=30s

1. 30s 内失败3次 → 标记不可用
2. 30s 后自动尝试恢复连接
3. 恢复成功 → 重新加入负载均衡池
4. 恢复失败 → 继续标记为不可用,等待下一个30s

Q3:backup 和 down 的区别?

A:

参数 状态 何时接收请求 使用场景
backup 动态 主服务器全部不可用时 高可用保障
down 永久 永不接收请求 服务器维护

六、完整配置示例

nginx

upstream node {
    # 主服务器:权重5,失败3次标记不可用,30s后重试
    server 192.168.226.10:80 weight=5 max_fails=3 fail_timeout=30s;

    # 主服务器:默认权重1,失败3次标记不可用,60s后重试
    server 192.168.226.20:80 max_fails=3 fail_timeout=60s;

    # 备份服务器:主服务器全挂时启用
    server 192.168.226.30:80 backup;

    # 下线服务器:正在维护中
    server 192.168.226.40:80 down;
}

server {
    listen 80;
    server_name node.test.com;

    location / {
        proxy_pass http://node;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

upstream 配置位置

text

客户端 (192.168.226.40)
    ↓ 访问 http://node.test.com
    ↓
【代理服务器/负载均衡器】 (192.168.226.10)  ← 在这里配置 upstream!
    ↓ /etc/nginx/conf.d/node.conf
    ↓ upstream node { ... }
    ├──→ 后端1 (192.168.226.20:80)  ← 被代理的服务器
    └──→ 后端2 (192.168.226.30:80)  ← 被代理的服务器

Nginx 负载均衡会话保持详解

一、什么是会话保持(Session Stickiness)?

在负载均衡环境下,用户的请求会被分发到不同的后端服务器。

text

用户A → 负载均衡器 → 第1次请求 → web01(登录成功,session保存在web01)
用户A → 负载均衡器 → 第2次请求 → web02(web02没有用户的session,需要重新登录!)❌

会话保持的目的:确保同一个用户的请求始终被转发到同一台后端服务器,或者多台服务器共享session数据。

二、Session 和 Cookie 的区别

1. Cookie(客户端存储)

text

浏览器(客户端)
    ↓ 存储位置:浏览器本地
    ↓ 存储内容:用户标识信息(如 session_id)
    ↓ 特点:
        - 大小限制:一般 4KB 左右
        - 自动发送:每次请求会自动携带
        - 安全性:数据可见,可被篡改(需加密)

Cookie 是钥匙,Session 是保险柜。

2. Session(服务端存储)

text

服务器
    ↓ 存储位置:服务器内存/文件/数据库
    ↓ 存储内容:用户实际数据(用户名、权限、购物车等)
    ↓ 特点:
        - 安全:数据存储在服务端,用户无法篡改
        - 灵活:可以存储任意类型的数据
        - 生命周期:由服务器控制

3. 两者关系

text

用户登录流程:

1. 用户输入账号密码 → 提交到服务器
2. 服务器验证通过 → 创建 Session(存储用户信息)
3. 服务器生成 Session ID → 通过 Cookie 返回给浏览器
4. 浏览器保存 Cookie(包含 Session ID)
5. 后续请求 → 浏览器自动携带 Cookie
6. 服务器根据 Session ID → 找到对应的 Session 数据

text

┌─────────────┐      ┌─────────────┐      ┌─────────────┐
│   浏览器     │      │   负载均衡器 │      │   web01     │
│             │      │             │      │             │
│  Cookie     │ ──→  │  分发请求   │ ──→  │  Session    │
│  session_id │      │             │      │  用户数据    │
│  = abc123   │      │             │      │             │
└─────────────┘      └─────────────┘      └─────────────┘
                                                 ↓
                                            同一个用户
                                            session_id=abc123

image-20260828184320082

image-20260828184712765

实践会话保持

纯逻辑架构图(服务依赖关系)

text

                        【用户请求】
                            |
                            v
                  ┌─────────────────┐
                  │    Nginx        │  ← 静态资源(图片/CSS/JS)直接返回
                  │  (HTTP入口)     │  ← 动态请求转发给 PHP-FPM
                  └────────┬────────┘
                           │ (FastCGI 协议)
                           v
                  ┌─────────────────┐
                  │   PHP-FPM       │  ← 执行网站业务代码
                  │  (应用运行时)   │  ← phpMyAdmin 也运行在这里(管理工具)
                  └────────┬────────┘
                           │
          ┌────────────────┼────────────────┐
          │                │                │
          v                v                v
   ┌───────────┐   ┌───────────┐   ┌───────────┐
   │   Session │   │  业务缓存  │   │  持久化   │
   │   (会话)  │   │  (查询缓存)│   │  数据存储 │
   └─────┬─────┘   └─────┬─────┘   └─────┬─────┘
         │               │               │
         └───────────────┼───────────────┘
                         │
                         v
                  ┌─────────────────┐
                  │     Redis       │  ← 缓存层(临时存储)
                  │  (内存数据库)   │  ← 存储:登录态、验证码、热点数据
                  └─────────────────┘
                         │
                         │(缓存未命中时回源)
                         v
                  ┌─────────────────┐
                  │    MySQL        │  ← 持久化层(永久存储)
                  │  (关系型数据库) │  ← 存储:用户表、订单表、商品表
                  └─────────────────┘

准备工作安装Lnmp架构环境

在LNMP架构中为Nginx服务器(服务器A)安装PHP运行环境和扩展的完整命令

dnf install -y epel-release
dnf groupinstall "Development Tools" -y
dnf install -y wget gcc make libxml2-devel openssl-devel sqlite-devel libcurl-devel libpng-devel libjpeg-devel oniguruma libzip-devel
dnf install -y php php-cli php-fpm php-mysqlnd php-zip php-gd php-mbstring php-curl php-xml php-pear php-bcmath php-intl
dnf install -y php-pecl-redis
php -m | grep redis

架构图

text

┌─────────────────────────────────────────────────────────────────────────┐
│                          【内网环境 192.168.226.0/24】                  │
│                                                                         │
│  ┌─────────────────────┐    ┌──────────────────────────────────────┐   │
│  │   【数据库服务器】    │    │          【Web服务器集群】           │   │
│  │   192.168.226.10    │    │                                      │   │
│  │                     │    │   ┌────────────────────────────┐    │   │
│  │   ┌─────────────┐   │    │   │   Web服务器1              │    │   │
│  │   │  MariaDB    │   │    │   │   192.168.226.20          │    │   │
│  │   │  端口:3306  │◄──┼────┼───│   Nginx + PHP-FPM        │    │   │
│  │   └─────────────┘   │    │   │   phpMyAdmin             │    │   │
│  │                     │    │   └────────────────────────────┘    │   │
│  │   远程连接账号:      │    │                                      │   │
│  │   root@'192.168.    │    │   ┌────────────────────────────┐    │   │
│  │   226.%'            │    │   │   Web服务器2              │    │   │
│  │   密码:123456       │    │   │   192.168.226.30          │    │   │
│  └─────────────────────┘    │   │   Nginx + PHP-FPM        │    │   │
│                              │   │   phpMyAdmin             │    │   │
│                              │   └────────────────────────────┘    │   │
│                              └──────────────────────────────────────┘   │
│                                                                         │
│  数据流向:Web服务器(20/30) ---> 远程连接 ---> 数据库服务器(10):3306   │
│  访问入口:用户 ---> Web服务器(20或30) ---> phpMyAdmin管理数据库        │
└─────────────────────────────────────────────────────────────────────────┘
  • 在10服务器上部署mariadb数据库,提供phpmyadmin访问
# 安装mariadb数据库软件
yum install mariadb-server mariadb -y

# 启动数据库并且设置开机自启动
systemctl start mariadb
systemctl enable mariadb

# 设置mariadb的密码
mysqladmin password '123456'

# 验证数据库是否工作正常
mysql -uroot -p123456 -e "show databases;"
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
+--------------------+

# 创建访问用户
grant all privileges on *.* to root@'192.168.226.%' identified by '123456' with grant option;
flush privileges;
部署LNMP
[root@server2 ~]# yum install nginx -y
[root@server2 ~]# vim /etc/php-fpm.d/www.conf 
listen = 9000
[root@server2 ~]# sed -i '/^user/c user = nginx' /etc/php-fpm.d/www.conf
[root@server2 ~]# sed -i '/^group/c group = nginx' /etc/php-fpm.d/www.conf
[root@server2 ~]# systemctl restart php-fpm.service 
[root@server2 ~]# ss -tnl
[root@server2 ~]# cd /etc/nginx/conf.d/
[root@server2 ~]# vim php.conf 
server {
    listen 80;
    server_name php.test.com;
    root /code/phpmyadmin;

    location / {
        index index.php index.html;
    }

    location ~ .php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}
[root@server2 ~]# nginx -t
[root@server2 ~]# nginx -s reload
[root@server2 ~]# mkdir /code
[root@server2 ~]# cd /code/
[root@server2 ~]# unzip phpMyAdmin-5.1.1-all-languages.zip 
[root@server2 ~]# mv phpMyAdmin-5.1.1-all-languages phpmyadmin
[root@server2 ~]# chown -R nginx.nginx /var/lib/php/
[root@server3 ~]# cd /code/phpmyadmin/
[root@server2 phpmyadmin]# cp config.sample.inc.php config.inc.php
[root@server2 phpmyadmin]# vim config.inc.php
$cfg['Servers'][$i]['host'] = '192.168.226.10';

为什么写 listen = 9000?

因为你的 Nginx 配置里写的是:

nginx

fastcgi_pass 127.0.0.1:9000;

这两边必须一致,否则 PHP-FPM 收不到 Nginx 转发的请求。


直观理解

text

Nginx 把请求发到 → 127.0.0.1:9000
                        ↓
                 PHP-FPM 必须在这个地址等着
                        ↓
                 所以 PHP-FPM 要写 listen = 9000

如果两边不一致:

Nginx 转发到 PHP-FPM 监听 结果
127.0.0.1:9000 listen = 9000 ✅ 能通信
127.0.0.1:9000 listen = /run/php-fpm/www.sock ❌ 找不到,报错

因为 phpMyAdmin 需要一个 config.inc.php 才能连接数据库,而官方只提供了一个模板文件 config.sample.inc.php,你需要复制一份再修改。


详细解释

文件 作用 能不能直接改
config.sample.inc.php 配置模板,官方提供的示例 ❌ 不能直接改,改了下次升级会覆盖
config.inc.php 真正的配置文件,phpMyAdmin 实际读取的 ✅ 复制一份来改

第1步:编辑 PHP-FPM配置文件

bash

[root@server2 ~]# vim /etc/php-fpm.d/www.conf
部分 含义
vim Vi IMproved,Linux 下最常用的文本编辑器
/etc/php-fpm.d/www.conf PHP-FPM 的进程池配置文件路径

这个文件里配置什么?

  • listen:PHP-FPM 监听的地址和端口
  • user/group:PHP-FPM 进程的运行用户和组
  • pm:进程管理方式(dynamic/static)
  • pm.max_children:最大子进程数

为什么叫 www.conf? PHP-FPM支持多个进程池(pool),每个池可以有独立的配置。www.conf 是默认的进程池配置文件。如果你有多个网站,可以复制成 blog.conf、shop.conf 等。


text

        /etc/php-fpm.d/  (配置目录)
              │
    ┌─────────┼─────────┬──────────┐
    ▼         ▼         ▼          ▼
 ┌──────┐ ┌──────┐ ┌──────┐  ┌────────┐
 │www.conf│ │blog.conf│ │shop.conf│ │api.conf│  ← 所有 .conf 文件
 └──────┘ └──────┘ └──────┘ └────────┘
    │         │         │          │
    ▼         ▼         ▼          ▼
  PHP-FPM 自动加载并管理所有这些池

核心机制

PHP-FPM 的加载逻辑

PHP-FPM 主程序启动时,会自动扫描并加载 /etc/php-fpm.d/ 目录下所有以 .conf 结尾的文件。

这个行为由 PHP-FPM 主配置文件决定:

bash

# /etc/php-fpm.conf(主配置文件)
include=/etc/php-fpm.d/*.conf   # ← 这行告诉 PHP-FPM:加载这个目录下所有 .conf

每个 .conf 文件 = 一个独立的进程池

文件 池名称 作用
www.conf [www] 处理网站A的PHP请求
blog.conf [blog] 处理网站B的PHP请求
shop.conf [shop] 处理网站C的PHP请求
api.conf [api] 处理API服务的PHP请求

实际网络流程

当用户请求一个 PHP 文件时:

text

用户访问 http://192.168.226.20/index.php
              │
              ▼
┌──────────────────────────────────────────────────────────────┐
│                     server2 (192.168.226.20)                 │
│                                                             │
│  ① Nginx 收到请求(监听 80 端口)                          │
│     │                                                       │
│  ② 匹配到 location ~ .php$                                │
│     │                                                       │
│  ③ fastcgi_pass 127.0.0.1:9000                            │
│     │                                                       │
│     └─────▶ 数据包走"回环接口(loopback)"                 │
│             不经过网卡,不经过交换机,直接在内存里转发      │
│             目的地:127.0.0.1:9000(本机)                  │
│     │                                                       │
│  ④ PHP-FPM 正在 9000 端口等待,接收到请求                  │
│     │                                                       │
│  ⑤ PHP-FPM 执行 /code/phpmyadmin/index.php                 │
│     │                                                       │
│  ⑥ index.php 连接 192.168.226.10:3306(数据库服务器)       │
│     │                                                       │
│  ⑦ 数据返回 → Nginx → 用户浏览器                          │

text

用户访问 http://php.test.com/index.php
              │
              ▼
    ┌─────────────────────────────────┐
    │         Nginx (本机)            │
    │                                  │
    │  ① 请求的是 .php 文件           │
    │     ↓                            │
    │  ② 匹配 location ~ .php$       │
    │     ↓                            │
    │  ③ fastcgi_pass 127.0.0.1:9000 │
    │     (转发给本机 9000 端口)     │
    └─────────────────────────────────┘
              │
              ▼
    ┌─────────────────────────────────┐
    │       PHP-FPM (本机)            │
    │                                  │
    │  ④ 监听 9000 端口               │
    │     ↓                            │
    │  ⑤ 找到 www.conf 池             │
    │     ↓                            │
    │  ⑥ 执行 index.php               │
    │     ↓                            │
    │  ⑦ 连接 192.168.226.10:3306     │
    │     (数据库服务器)             │
    └─────────────────────────────────┘
              │
              ▼
    ┌─────────────────────────────────┐
    │   数据库服务器 192.168.226.10    │
    │   返回数据给 PHP-FPM            │
    └─────────────────────────────────┘
              │
              ▼
    Nginx → 用户浏览器显示页面

text

用户请求 http://php.test.com/index.php(.php 结尾)
              │
              ▼
┌─────────────────────────────────────────────────────────────┐
│  ① Nginx 收到请求                                          │
│     看到是 .php 结尾 → 匹配 location ~ .php$              │
│                                                             │
│  ② location 里配置了 fastcgi_pass 127.0.0.1:9000         │
│     意思:把这个请求交给本机的 PHP-FPM 管理                 │
│                                                             │
│  ③ 安装 Nginx 的时候,同时也装了 php-fpm                   │
│     (yum install php-fpm)                                 │
└─────────────────────────────────────────────────────────────┘
              │
              ▼
┌─────────────────────────────────────────────────────────────┐
│  ④ PHP-FPM 是一个"进程管理器"                              │
│     ● Master 进程:负责管理下面所有的 Worker 进程          │
│     ● Worker 进程:真正执行 PHP 代码的进程                 │
│                                                             │
│  ⑤ www.conf 定义了:                                       │
│     ● listen = 9000(监听 9000 端口)                     │
│     ● user = nginx(以 nginx 用户身份运行)               │
│     ● 这个进程池就是专门处理 phpMyAdmin 请求的             │
│                                                             │
│  ⑥ Worker 进程执行 /code/phpmyadmin/index.php              │
│     也就是执行进程池里的这个 PHP 文件                       │
└─────────────────────────────────────────────────────────────┘
              │
              ▼
┌─────────────────────────────────────────────────────────────┐
│  ⑦ index.php 里 require 了 config.inc.php                  │
│     config.inc.php 里配置了:                               │
│     $cfg['Servers'][$i]['host'] = '192.168.226.10';        │
│                                                             │
│  ⑧ 这个配置的意思是:                                       │
│     "我要连接远程的数据库服务器(192.168.226.10)"          │
└─────────────────────────────────────────────────────────────┘
              │
              ▼
┌─────────────────────────────────────────────────────────────┐
│  ⑨ PHP 代码执行 SQL 查询(如 SELECT * FROM users)        │
│     通过网络发送到 192.168.226.10:3306                      │
│                                                             │
│  ⑩ MySQL 数据库执行查询,把结果集(数据)返回给 PHP       │
└─────────────────────────────────────────────────────────────┘
              │
              ▼
        PHP 拼成 HTML 页面
              │
              ▼
        Nginx 返回给用户浏览器

image-20260828133601487

image-20260828191510535

接入负载均衡

[root@lb01 ~]# vim /etc/nginx/conf.d/proxy_php.com.conf
upstream php {
        server 192.168.226.20:80;
        server 192.168.226.30:80;
}
server {
        listen 80;
        server_name php.test.com;
        location / {
                proxy_pass http://php;
                include proxy_params;
        }
}

[root@lb01 ~]# nginx -t
[root@lb01 ~]# systemctl restart nginx
  • 常用优化配置
    • Proxy代理网站常用优化配置如下,将配置写入新文件,调用时使用include引用即可
[root@Nginx ~]# vim /etc/nginx/proxy_params
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_connect_timeout 30;
proxy_send_timeout 60;
proxy_read_timeout 60;

proxy_buffering on;
proxy_buffer_size 32k;
proxy_buffers 4 128k;

image-20260828193027052


准备redis内存数据库

[root@server1 ~]# yum -y install redis
[root@server1 ~]# vim /etc/redis/redis.conf 
bind 0.0.0.0
[root@server1 ~]# systemctl enable --now redis
Created symlink /etc/systemd/system/multi-user.target.wants/redis.service → /usr/lib/systemd/system/redis.service.
[root@localhost ~]# ss -tnl
State                    Recv-Q                   Send-Q                                     Local Address:Port                                       Peer Address:Port
LISTEN                   0                        128                                              0.0.0.0:22                                              0.0.0.0:*
LISTEN                   0                        511                                              0.0.0.0:80                                              0.0.0.0:*
LISTEN                   0                        511                                              0.0.0.0:6379                                            0.0.0.0:*
LISTEN                   0                        80                                                     *:3306                                                  *:*
LISTEN                   0                        128                                                 [::]:22                                                 [::]:*
LISTEN                   0                        511                                                 [::]:80                                                 [::]:*

bind 参数详解:

配置 含义 谁能连接
bind 127.0.0.1 只监听本机(默认) 只有本机可以连接
bind 0.0.0.0 监听所有 IP 地址 任何机器都可以连接(需密码保护)
bind 192.168.1.100 只监听指定 IP 只有该 IP 网段的机器可连

为什么要改成 0.0.0.0? 因为你的架构中,Web 服务器(192.168.226.20)需要连接Redis服务器(192.168.226.10),如果不改,Redis只监听本机,其他机器连不上。

安全提醒:bind 0.0.0.0 允许任何人连接,生产环境建议:

  1. 设置密码(requirepass)
  2. 用防火墙限制只允许特定IP访问

[root@server2 ~]# vim /etc/php.ini
[Session]
;session.save_handler = files
session.save_handler = redis
session.save_path = "tcp://192.168.226.10:6379"
;session.save_path = "tcp://192.168.226.10:6379?auth=centos" #如果redis存在密码,则使用该方式
session.auto_start = 1

[root@server2 ~]# vim /etc/php-fpm.d/www.conf
#注释php-fpm.d/www.conf里面的两条内容,否则session内容会一直写入/var/lib/php/session目录中
;php_value[session.save_handler] = files
;php_value[session.save_path]    = /var/lib/php/session
[root@server2 conf.d]# systemctl restart php-fpm.service

1. 核心目标:将Session存储从“文件”切换到“Redis”

默认情况下,PHP的Session数据存储在服务器硬盘的文件中(/var/lib/php/session)。你的配置旨在将所有Session数据迁移到Redis内存数据库中,以提升高并发下的读写速度,并方便多台Web服务器共享Session。


2. 逐步配置详解

第一步:修改 /etc/php.ini(全局生效)

这是PHP的核心配置文件,你在这里做了三处关键修改:

  • session.save_handler = redis 告诉PHP,不再使用默认的files(文件)方式,而是使用redis扩展模块来处理Session。
  • session.save_path = "tcp://192.168.226.10:6379" 指定Redis服务器的IP和端口。tcp://是协议头,告诉PHP使用TCP连接。
  • session.auto_start = 1 这个设置意味着每次请求开始时,PHP会自动调用session_start()。 ⚠️ 注意:启用后,如果你的某些页面不需要Session,也会白白占用Redis连接资源。通常建议在代码中手动调用session_start(),这里设为1是为了简化测试或老旧项目的改造。

第二步:修改 /etc/php-fpm.d/www.conf(覆盖全局)

PHP-FPM(处理动态请求的进程管理器)有自己的配置,可以覆盖php.ini的设置。

  • 你注释掉的两行:

    ini

    ;php_value[session.save_handler] = files
    ;php_value[session.save_path]    = /var/lib/php/session

    这两行的原始作用是强制将Session存回文件。如果不注释,它们会覆盖你在php.ini中设置的redis,导致Session依然写入硬盘。注释掉它们,就等于移除了这个“覆盖指令”,让php.ini中的Redis配置正式生效。


3. 为什么需要两步配置?(关键点)

  • php.ini 是“最高指示”,但PHP-FPM的www.conf拥有更高优先级的局部设置权限。
  • 你的操作逻辑是:在php.ini中设定目标为Redis,然后在www.conf中废除干扰项(Files),确保规则统一。

==总结==:通过在php.ini中开启Redis存储,并屏蔽www.conf中的文件存储覆盖指令,最终迫使所有PHP Session数据落地到指定的Redis数据库中。

image-20260829010649788

image-20260829010919087

image-20260829011745483

image-20260829012414648


Nginx四层负载均衡场景实践

Nginx如何配置四层负载均衡

1、通过访问负载均衡的5555端口,实际是后端的web01的22端口在提供服务;

2、通过访问负载均衡的6666端口,实际是后端的MySQL的3306端口在提供服务。

  • 创建存放四层负载均衡配置文件的目录
[root@lb01 ~]# yum install nginx-mod-stream
[root@lb01 ~]# vim /etc/nginx/nginx.conf
include /etc/nginx/conf.c/*.conf;
#include /etc/nginx/conf.d/*.conf;
[root@lb01 ~]# mkdir /etc/nginx/conf.c
  • 配置四层负载均衡
[root@lb01 ~]# vim /etc/nginx/conf.c/lb_domain.conf
stream {
    upstream lb {
            server 192.168.226.20:80 weight=5 max_fails=3 fail_timeout=30s;
            server 192.168.226.30:80 weight=5 max_fails=3 fail_timeout=30s;
    }

    server {
            listen 80;
            proxy_connect_timeout 3s;
            proxy_timeout 3s;
            proxy_pass lb;
    }
}

二、四层配置代码逐行详解

nginx

stream {                                    # ← 四层负载均衡模块
    upstream 1b {                           # ← 定义后端服务器组,名称叫"1b"
        server 192.168.226.20:80 weight=5 max_fails=3 fail_timeout=30s;
        server 192.168.226.30:80 weight=5 max_fails=3 fail_timeout=30s;
    }

    server {                                # ← 定义代理服务器
        listen 80;                          # ← 监听本机的80端口
        proxy_connect_timeout 3s;           # ← 连接后端超时时间
        proxy_timeout 3s;                   # ← 数据传输超时时间
        proxy_pass 1b;                      # ← 转发到 upstream 定义的 "1b" 组
    }
}

访问流程

text

用户浏览器访问 http://192.168.226.10(默认80端口)
                    ↓
        [负载均衡器 lb01] 监听 :80
                    ↓
    四层转发(TCP层,不解析HTTP内容)
                    ↓
    选择后端服务器(根据 weight 权重)
                    ↓
    web01 (192.168.226.20:80)  或  web02 (192.168.226.30:80)
                    ↓
        返回网站首页给用户

image-20260829021756594image-20260829022129481

image-20260829022342610


配置四层负载均衡日志

  • 四层负载均衡开启日志
    • 四层负载均衡是没有access的日志的,因为在nginx.conf的配置中,access的日志格式是配置在HTTP下的,而四层负载均衡配置实在HTTP以外的
    • 如果需要日志则需要配置在stream下面
[root@lb01 ~]# cd /etc/nginx/conf.c/
[root@lb01 conf.c]# vim lb_domain.conf
stream {
    log_format  proxy '$remote_addr $remote_port - [$time_local] $status $protocol '
                  '"$upstream_addr" "$upstream_bytes_sent" "$upstream_connect_time"' ;
    access_log /var/log/nginx/proxy.log proxy;
    upstream lb {
            server 192.168.226.20:80 weight=5 max_fails=3 fail_timeout=30s;
            server 192.168.226.30:80 weight=5 max_fails=3 fail_timeout=30s;
    }

    server {
            listen 80;
            proxy_connect_timeout 3s;
            proxy_timeout 3s;
            proxy_pass lb;
    }
}

[root@lb01 conf.c]# nginx -t
[root@lb01 conf.c]# systemctl restart nginx

image-20260829024319062


Nginx四层负载均衡端口转发

  • 使用Nginx四层负载均衡实现TCP的转发
请求负载均衡 5555    --->     192.168.226.20:22;
请求负载均衡 6666    --->     192.168.226.30:3306;
  • 配置Nginx四层负载均衡实现TCP的转发
[root@lb01 ~]# vim  /etc/nginx/conf.c/lb_domain.conf
stream {
    log_format  proxy '$remote_addr $remote_port - [$time_local] $status $protocol '
                      '"$upstream_addr" "$upstream_bytes_sent" "$upstream_connect_time"' ;
    access_log /var/log/nginx/proxy.log proxy;

#定义转发ssh的22端口
    upstream ssh {
            server 192.168.226.20:22;
    }
#定义转发mysql的3306端口
    upstream mysql {
            server 192.168.226.30:3306;
    }
    server {
            listen 5555;
            proxy_connect_timeout 3s;
            proxy_timeout 300s;
            proxy_pass ssh;
    }

    server {
            listen 6666;
            proxy_connect_timeout 3s;
            proxy_timeout 3s;
            proxy_pass mysql;
    }
}

[root@lb01 ~]# nginx -t
[root@lb01 ~]# setenforce 0
[root@lb01 ~]# systemctl restart nginx

image-20260829122429159

image-20260829122756434


# 安装mariadb数据库软件
yum install mariadb-server mariadb -y

# 启动数据库并且设置开机自启动
systemctl start mariadb
systemctl enable mariadb

# 设置mariadb的密码
mysqladmin password '123456'

# 验证数据库是否工作正常
mysql -uroot -p123456 -e "show databases;"

# 创建访问用户
grant all privileges on *.* to root@'192.168.226.%' identified by '123456' with grant option;
flush privileges;

# 测试访问
[root@server4 ~]# mysql -uroot -p123456 -h 192.168.226.10 -P 6666
MariaDB [(none)]> 
[root@server4 ~]# ssh root@192.168.226.10 -p 5555
root@192.168.226.10 password:

image-20260829124832276


Keepalived高可用

一般是指2台机器启动着完全相同的业务系统,当有一台机器down机了,另外一台服务器就能快速的接管,对于访问的用户是无感知的。

硬件通常使用 F5

软件通常使用 keepalived

keepalived软件是基于VRRP协议实现的,VRRP虚拟路由冗余协议,主要用于解决单点故障问题

一、为什么需要高可用?

单点故障问题

text

用户 → [Nginx负载均衡器] → 后端服务器
              ↑
        如果这台机器挂了?
        所有服务全部不可用!

高可用解决方案

text

用户 → [VIP虚拟IP] → 主Nginx (Active)  ← 正常工作
                 ↘  备Nginx (Standby)   ← 随时待命

主Nginx挂了 → VIP自动漂移到备Nginx → 服务不中断!

二、硬件负载均衡 vs 软件负载均衡

类型 代表产品 优点 缺点
硬件 F5、A10、Radware 性能极高、稳定、功能丰富 价格昂贵(几十万起)
软件 Keepalived、HAProxy、Nginx 免费、灵活、易扩展 性能受限于服务器

三、VRRP 协议详解(核心!)

什么是 VRRP?

VRRP = Virtual Router Redundancy Protocol(虚拟路由冗余协议)

简单说:把多台路由器虚拟成一台”虚拟路由器”,通过一个虚拟IP对外提供服务。

四、VRRP 核心概念详解

1. 虚拟路由器

text

┌─────────────────────────────────────┐
│         虚拟路由器 (VRRP组)           │
│  虚拟IP: 192.168.226.100              │
│  虚拟MAC: 00-00-5e-00-01-51          │
└─────────────────────────────────────┘
            ↓        ↓
    ┌───────────┐  ┌───────────┐
    │ Master    │  │ Backup    │
    │ 实际IP:   │  │ 实际IP:   │
    │ .10       │  │ .11       │
    └───────────┘  └───────────┘
  • 虚拟IP(VIP):对外提供服务的浮动IP
  • 虚拟MAC:00-00-5e-00-01-{VRID},VRID 范围 1-255

2. Master(主路由器)

  • 唯一对外提供服务
  • 响应虚拟IP的ARP请求
  • 定期发送 VRRP 通告(心跳包)

3. Backup(备份路由器)

  • 不对外提供服务
  • 监听 Master 的通告
  • Master 挂了 → 接替成为新的 Master

五、选举机制(重点!)

优先级(Priority)

text

Master 选举依据:优先级越高越优先

配置示例:
Master: priority 150
Backup: priority 100

抢占模式 vs 非抢占模式

① 抢占模式(默认)

text

时刻1: Master(150) 运行中 ✅
时刻2: Master 挂了 ❌
时刻3: Backup(100) 接管成为新 Master ✅
时刻4: 原Master恢复(150) → 立即抢回Master位置!(抢占)

特点:优先级高的永远是 Master

② 非抢占模式(nopreempt)

text

时刻1: Master(150) 运行中 ✅
时刻2: Master 挂了 ❌
时刻3: Backup(100) 接管成为新 Master ✅
时刻4: 原Master恢复(150) → 继续当Backup,不抢占!

特点:只要Master不挂,优先级高的也得等着

六、三种状态机

text

                    ┌─────────────┐
                    │ Initialize  │ ← 系统启动/Keepalived启动
                    │  (初始化)    │
                    └──────┬──────┘
                           │
                    ┌──────▼──────┐
                    │  检查优先级  │
                    └──────┬──────┘
                           │
            ┌──────────────┼──────────────┐
            ↓              ↓              ↓
    ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
    │   Master    │ │   Backup    │ │   Fault     │
    │   (主状态)   │ │   (备状态)   │ │   (故障)    │
    └─────────────┘ └─────────────┘ └─────────────┘

==总结==:都是通过优先级值选举出来的


Keepalived高可用安装配置

安装keepalived

[root@lb01 ~]# yum install -y keepalived
[root@lb02 ~]# yum install -y keepalived

配置master

  • 找到配置文件
[root@lb02 ~]# rpm -qc keepalived
/etc/keepalived/keepalived.conf
/etc/sysconfig/keepalived
  • 修改配置
[root@lb01 ~]# vim /etc/keepalived/keepalived.conf
global_defs {                   #全局配置
    router_id lb01              #标识身份->名称
}

vrrp_instance VI_1 {
    state MASTER                #标识角色状态
    interface ens160             #网卡绑定接口
    virtual_router_id 50        #虚拟路由id
    priority 150                #优先级
    advert_int 1                #监测间隔时间
    #use_vmac                  #使用虚拟mac地址,因为路由问题,可能导致原本的IP不可用
    authentication {            #认证
        auth_type PASS          #认证方式
        auth_pass 1111          #认证密码
    }
    virtual_ipaddress {         
        192.168.226.100                #虚拟的VIP地址
    }
}

配置backup

[root@lb02 ~]# vim /etc/keepalived/keepalived.conf
global_defs {
    router_id lb02
}

vrrp_instance VI_1 {
    state BACKUP        
    interface ens160
    virtual_router_id 50
    priority 100
    advert_int 1
    authentication {    
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.226.100
    }
}

image-20260829171313702

image-20260829171406931

1. 需要关闭防火墙/放行协议的原因

Keepalived 的核心是 VRRP 协议,它通过多播(224.0.0.18)或单播发送心跳包来让主备之间互相检测存活状态。

  • 如果防火墙阻挡了 VRRP 报文:
    • MASTER 收不到 BACKUP 的报文 → 不知道对方存在
    • BACKUP 收不到 MASTER 的报文 → 以为 MASTER 挂了,自己也变成 MASTER
    • 结果:两台都认为自己该持有 VIP,出现 “脑裂”,或者 VIP 无法正常漂移
  • 所以要放行:要么彻底关防火墙,要么放行 VRRP协议(协议号 112)。

工作原理:

  1. 启动时,MASTER(.10)优先级更高,抢占并绑定 VIP
  2. BACKUP(.20)检测到 MASTER 存活,主动放弃 VIP,处于待命状态
  3. 只有 MASTER 对外提供服务,BACKUP 只负责监听心跳
  4. 如果 MASTER 宕机,BACKUP 才会接管 VIP,保证服务不中断

所以你看到 .10 有 192.168.226.100,.20 没有 → 这就是高可用的正常状态。

关闭服务器进行抢占

image-20260829171653605

image-20260829171845877

  • 配置非抢占式
    • 两个节点的state都必须配置为BACKUP
    • 两个节点都必须加上配置 nopreempt
    • 其中一个节点的优先级必须要高于另外一个节点的优先级。
    • 两台服务器的角色状态都启用nopreempt后,必须修改角色状态统一为BACKUP,唯一的区分就是优先级。
Master配置
    vrrp_instance VI_1 {
        state BACKUP
        priority 150
        nopreempt
    }

Backup配置
    vrrp_instance VI_1 {
        state BACKUP
        priority 100
        nopreempt
    }

image-20260829172930996

非抢占式的影响:

场景 行为
.10 宕机 → .20 接管 VIP ✅ 正常切换
.10 恢复上线 ❌ 不会抢回 VIP(因为 nopreempt)
VIP 会一直留在 .20 上 直到 .20 也宕机,VIP 才回到 .10

.10 关了,.20 会在 3 秒内自动接管 VIP,服务不受影响。.10 恢复后,因为配置了nopreempt,不会抢回 VIP,VIP 继续留在 .20 上,直到 .20 也出问题。这是非抢占模式的设计行为。

image-20260829173533809


高可用keepalived故障脑裂

由于某些原因,导致两台keepalived高可用服务器在指定时间内,无法检测到对方的心跳,各自取得资源及服务的所有权,而此时的两台高可用服务器又都还活着。

脑裂故障原因

  • 服务器网线松动等网络故障
  • 服务器硬件故障发生损坏现象而崩溃
  • 主备都开启firewalld防火墙

解决脑裂故障方案

  • 如果发生脑裂,则随机kill掉一台即可
  • 在backup上编写检测脚本, 测试如果能ping通master并且backup节点还有vIP的话则认为产生了脑裂
# 检测脑裂脚本
[root@server2 ~]# cat check_split_brain.sh
#!/bin/sh
vip=192.168.226.100
lb01_ip=192.168.226.10

while true;do
    ping -c 2 $lb01_ip &>/dev/null
    if [ $? -eq 0 -a `ip add|grep "$vip"|wc -l` -eq 1 ];then
        echo 'ha is split brain.warning!!!' `date`>> /root/check_split_brain.sh.out

        systemctl stop keepalived
    else
        echo "ha is ok..." >> /root/check_split_brain.sh.out
    fi
    sleep 5
done
[root@server2 ~]# chmod +x check_split_brain.sh 
[root@server2 ~]# /root/check_split_brain.sh &
[root@server2 ~]# tail -f check_split_brain.sh.out

为什么 cat 看到既有 warning 又有 ok?

text

ha is split brain.warning!!! 2026年 08月 29日 星期六 18:05:42 CST
ha is ok...
ha is ok...
ha is ok...
ha is ok...
时间 状态 输出
18:05:42 脑裂时 ha is split brain.warning!!!
18:05:47 修复后(VIP 释放) ha is ok...
18:05:52 继续检查 ha is ok...
… … …

你的理解完全正确:脚本一直在 ping,当条件不满足时就输出 ok。本质变化是 VIP 从 .226.20 上消失了,所以条件从 true 变成了 false。


一图总结

text

循环开始
    ↓
ping MASTER (192.168.226.10)
    ↓
┌─────────────────────────────────┐
│ 能通? AND 自己有 VIP?          │
└─────────────────────────────────┘
    ↓                    ↓
   YES                  NO
    ↓                    ↓
 脑裂!              正常!
    ↓                    ↓
systemctl stop      echo "ha is ok..."
keepalived
(自杀,释放VIP)
    ↓
VIP 消失 → 下次循环条件不成立 → 输出 ok

image-20260829190422125

image-20260829190521633


Keepalived+Nginx实现网站高可用方案

VIP:192.168.226.100
lb01:192.168.226.10:8080
lb02:192.168.226.20:8080
web01:192.168.226.30:80
web02:192.168.226.50:80

首先部署web01和web02的网站(以web01为例)

[root@server3 ~]# yum install nginx -y
[root@server3 ~]# echo "web01 ..." > /usr/share/nginx/html/index.html 
[root@server3 ~]# nginx
# 访问web01、web02测试
[root@server5 ~]# curl 192.168.226.30
web01 ...
[root@server5 ~]# curl 192.168.226.50
web02 ...

image-20260829210003209

部署lb01和lb02(以lb01为例)

[root@server1 ~]# yum install nginx -y
[root@server1 ~]# yum install nginx-mod-stream -y
[root@server1 ~]# vim /etc/nginx/nginx.conf
include /etc/nginx/conf.c/*.conf;
[root@server1 ~]# mkdir  /etc/nginx/conf.c
[root@server1 ~]# vim /etc/nginx/conf.c/lb_domian.conf
stream {
        upstream lb {
                server 192.168.226.30:80 weight=5 max_fails=3 fail_timeout=30s;
                server 192.168.226.50:80 weight=5 max_fails=3 fail_timeout=30s;
        }

        server {
                listen 8080;
                proxy_connect_timeout 3s;
                proxy_timeout 3s;
                proxy_pass lb;
        }

}
[root@server1 ~]# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@server1 ~]# nginx
[root@server1 ~]# ss -tnl
State    Recv-Q   Send-Q     Local Address:Port       Peer Address:Port   Process   
LISTEN   0        511              0.0.0.0:8080            0.0.0.0:*                
LISTEN   0        128              0.0.0.0:22              0.0.0.0:*                
LISTEN   0        511              0.0.0.0:80              0.0.0.0:*                
LISTEN   0        128                 [::]:22                 [::]:*                
LISTEN   0        511                 [::]:80                 [::]:* 

部署成功:轮询算法

image-20260829212355319

部署keepalived

[root@server1 ~]# yum install keepalived -y
[root@server1 ~]# vim /etc/keepalived/keepalived.conf 
global_defs {                   #全局配置
    router_id lb01              #标识身份->名称
}

vrrp_instance VI_1 {
    state MASTER                #标识角色状态
    interface ens160             #网卡绑定接口
    virtual_router_id 50        #虚拟路由id
    priority 150                #优先级
    advert_int 1                #监测间隔时间
    authentication {            #认证
        auth_type PASS          #认证方式
        auth_pass 1111          #认证密码
    }
    virtual_ipaddress {
        192.168.226.100                #虚拟的VIP地址
    }
}

[root@server1 ~]# systemctl start keepalived.service 

[root@server2 ~]# vim /etc/keepalived/keepalived.conf 
global_defs {                   #全局配置
    router_id lb02              #标识身份->名称
}

vrrp_instance VI_1 {
    state BACKUP                #标识角色状态
    interface ens160             #网卡绑定接口
    virtual_router_id 50        #虚拟路由id
    priority 100                #优先级
    advert_int 1                #监测间隔时间
    authentication {            #认证
        auth_type PASS          #认证方式
        auth_pass 1111          #认证密码
    }
    virtual_ipaddress {
        192.168.226.100                #虚拟的VIP地址
    }
}
[root@server2 ~]# systemctl start keepalived.service

访问VIP进行测试

[root@server5 ~]# for i in {1..6};do curl 192.168.226.100:8080;done
web01 ...
web02 ...
web01 ...
web02 ...
web01 ...
web02 ...

image-20260902121609152

当lb01、lb02产生宕机的时候,keepalived应该可以检测到并发生飘移

bash
#!/bin/bash

# 获取当前 Nginx 进程数量(正常是 9,挂了是 0)
nginx_process_number=`ps -C nginx --no-header | wc -l`

# 判断:如果进程数等于 0,说明 Nginx 挂了
if [ $nginx_process_number -eq 0 ]; then
    # 尝试重启 Nginx
    systemctl restart nginx

    # 重启后再次检查进程数
    nginx_process_number=`ps -C nginx --no-header | wc -l`

    # 如果还是 0(重启失败)
    if [ $nginx_process_number -eq 0 ]; then
        exit 1    # 返回 1 → Keepalived 触发 VIP 漂移
    else
        exit 0    # 返回 0 → 正常,不切换
    fi
else
    # Nginx 正常运行(进程数 = 9),不做任何事
    exit 0
fi

这个脚本的作用是:==检测 Nginx是否正常运行,如果挂了就尝试重启,重启失败则通知 Keepalived 进行 VIP 漂移。==

image-20260902132216713

bash
# 1. 查看 Nginx 正常运行时的进程数
[root@localhost ~]# ps -C nginx --no-header | wc -l
9                              # 1个master + 8个worker = 9个进程 ✅

# 2. 停止 Nginx
[root@localhost ~]# nginx -s stop
[root@localhost ~]# ps -C nginx --no-header | wc -l
0                              # Nginx 已停止,进程数为 0 ✅

# 3. 强制杀掉所有 Nginx 进程
[root@localhost ~]# pkill nginx
[root@localhost ~]# ps -C nginx --no-header | wc -l
0                              # 全部被杀掉,进程数为 0 ✅

# 4. 重新启动 Nginx
[root@localhost ~]# nginx
[root@localhost ~]# ps -C nginx --no-header | wc -l
9                              # 恢复正常,9个进程 ✅

# 5. 给脚本添加执行权限
[root@localhost ~]# chmod +x /usr/local/src/check_nginx_pid.sh

在配置文件里配置你的脚本路径

[root@server1 ~]# vim /etc/keepalived/keepalived.conf
global_defs {                   #全局配置
    router_id lb01              #标识身份->名称
}

vrrp_script check_web {
    script "/usr/local/src/check_nginx_pid.sh"       -> # 这里是增加脚本路径
    interval 5
}

vrrp_instance VI_1 {
    state MASTER                #标识角色状态
    interface ens160             #网卡绑定接口
    virtual_router_id 50        #虚拟路由id
    priority 150                #优先级
    advert_int 1                #监测间隔时间
    track_script {                             # ->  这里是添加脚本调用
    check_web
    }
    authentication {            #认证
        auth_type PASS          #认证方式
        auth_pass 1111          #认证密码
    }
    virtual_ipaddress {         
        192.168.226.100                #虚拟的VIP地址
    }
}

1. 健康检查脚本定义

text

vrrp_script check_web {
    script "/usr/local/src/check_nginx_pid.sh"
    interval 5
}
  • script:指定检测 Nginx 是否存活的脚本路径
  • interval 5:每 5 秒执行一次脚本
  • 作用:脚本返回 0 表示 Nginx 正常,非 0 表示故障

2. 脚本调用与认证

text

track_script {
    check_web
}
authentication {
    auth_type PASS
    auth_pass 1111
}
  • track_script:引用上面定义的 check_web 脚本,若脚本检测失败则触发 VIP 漂移
  • authentication:主备节点间通信的认证方式(密码必须一致)

image-20260902132437310

脚本状态对应表

Nginx 状态 进程数 脚本执行 返回值 Keepalived 动作
正常运行 9 走 else 分支 exit 0 不做任何事,保持 MASTER
挂了但重启成功 0 → 9 走 if 分支,重启后恢复 exit 0 不做任何事,保持 MASTER
挂了且重启失败 0 → 0 走 if 分支,重启后还是 0 exit 1 VIP 漂移到备机

image-20260902151200213

image-20260902151304663


Keepalived高可用性与代理的关联

Keepalived与代理(如 Nginx、HAProxy、LVS)通常组合使用,解决互联网服务中的==单点故障==问题。


1. 各自核心职责

组件 核心职责 技术本质
Keepalived 对外保持同一个虚拟IP(VIP),保证入口地址始终可用。 高可用(HA):通过 VRRP 协议检测代理服务是否存活。如果主代理宕机,立即将 VIP 漂移到备用节点。
代理(Nginx/HAProxy/LVS) 接收客户端请求,根据负载均衡算法转发给后端 Real Server。 负载均衡(LB):将流量分发到多台后端服务器,提高吞吐量和容错能力。

2. 组合使用的核心原因

只有Nginx代理而没有 Keepalived:

  • 所有流量经过单台 Nginx。
  • 该Nginx服务器宕机、关机或网络中断时,整个网站无法访问(==单点故障==)。

只有 Keepalived 而没有代理:

  • Keepalived 本身不处理业务请求,仅提供可漂移的 IP。
  • 请求到达 VIP 后,如果没有代理层接收并转发,流量无法进入后端业务系统。

组合后的效果:

Keepalived 为代理层提供高可用保障,确保入口不中断;代理为后端应用提供负载均衡,确保处理能力充足。

3. 工作流程

text

1. 用户访问域名
       |
       ▼
2. DNS 解析指向虚拟 IP(VIP)
       |
       ▼
3. Keepalived Master 节点持有该 VIP
       |     (通过 VRRP 协议周期性检测 Master 存活状态)
       ▼
4. 代理(Nginx)监听 80/443 端口
       |     (配置了 upstream 后端服务器池)
       ▼
5. 代理根据权重/轮询等算法,将请求转发给后端 Web Server

4. 故障转移机制

当 Master 节点发生故障时:

  1. Keepalived 检测:Backup 节点在 3 个心跳周期内(约 3 秒)未收到 Master 的 VRRP 广播包。
  2. VIP 漂移:Backup 节点立即接管 VIP。
  3. 代理接管:新请求通过 VIP 到达 Backup 节点的代理服务。
  4. 业务无感:Backup 节点的代理继续将请求转发给后端服务器池。

对用户而言,网站入口地址不变,服务未中断。

5. 常见组合方式

组合方式 优势 适用场景
LVS + Keepalived 性能极高,四层转发,无数据拷贝,抗DDoS 能力强 大型门户、直播平台、TCP 四层入口
Nginx + Keepalived 配置灵活,支持七层 HTTP 高级特性(正则、缓存)、SSL 卸载 Web 应用、微服务网关、API 管理
HAProxy + Keepalived 支持四层和七层负载,健康检查功能强大 高要求 TCP/HTTP 负载均衡场景

关联总结

Keepalived 保证代理服务的高可用,确保入口 IP不丢失;代理负责请求分发,保证流量能到达后端业务服务器。两者协同工作,共同保障网站入口的 7×24 小时不间断服务。


LVS的NAT模式

当用户访问服务器集群提供的服务时,发往虚拟IP地址的请求数据包(负载均衡器的外部IP地址)到达负载均衡器。 负载均衡器检查数据包的目标地址和端口号。如果根据虚拟服务器规则表匹配虚拟服务器服务,则通过调度算法从集群 中选择 真实服务器,并将连接添加到记录已建立连接的哈希表中。然后,将目标地址和数据包的端口重写为所选服务器 的目标地址和端口,并将数据包转发到服务器。当传入数据包属于此连接并且可以在哈希表中找到所选服务器时,将重写 该数据包并将其转发到所选服务器。当回复数据包返回时,负载均衡器将数据包的源地址和端口重写为虚拟服务的源地址 和端口。连接终止或超时后,连接记录将在哈希表中删除。

LVS的NAT模式特点: RS应该使用私有地址,网关为DIP DIP和RIP属于同一个网段 请求和响应报文都需要经过DS(性能瓶颈) 支持端口映射 RS可以使用任意操作系统

一、整体流程:一个请求的”NAT之旅”

当用户访问服务器集群提供的服务时,发往虚拟IP地址的请求数据包到达负载均衡器。整个过程可以拆解为以下几个关键步骤:

步骤 方向 操作 数据包详情(基于 192.168.226.xxx)
① 客户端 → DS 用户访问 VIP 客户端 192.168.226.128:12345 → VIP 192.168.226.100:80
② DS内部 匹配虚拟服务规则 DS检查规则表,命中 192.168.226.100:80 的服务
③ DS 内部 调度算法选 RS 通过轮询等算法选中 RS1(192.168.226.30:80),并在哈希表中记录该连接
④ DS → RS1 DNAT 转换 目标地址改写为 192.168.226.30:80,转发给RS1
⑤ RS1 内部 处理业务 RS1 处理请求,生成响应数据
⑥ RS1 → DS 响应包先回 DS RS1 的响应包 192.168.226.30:80 → 客户端 192.168.226.128:12345,==因网关指向 DIP==(192.168.226.10),先回到 DS
⑦ DS → 客户端 SNAT 转换 源地址改写为 192.168.226.100:80 → 客户端 192.168.226.128:12345

当后续同一个连接的数据包到达时,DS可以在哈希表中查找到之前记录的 RS,直接将数据包转发给对应的 RS,无需重新调度。连接终止或超时后,连接记录在哈希表中删除。

在这个过程中,DS扮演了一个”网络地址转换器”的角色,对外屏蔽了内部的真实服务器结构。客户端全程只与 VIP 交互,感知不到后端 RS 的存在。

在 192.168.226.x 网段的环境里:

  • DS这台机器本身有一个真实 IP(DIP),比如 192.168.226.10,用于和后端服务器通信。
  • DS上同时还配置了一个 VIP,比如 192.168.226.100,这个 IP 是对外公布的服务地址,客户端只认这个地址。

关键点:客户端访问 VIP → VIP 就在 DS上 → DS 直接收到包 →DS 做 DNAT→ 转发给 RS。

流程图

text

客户端 (192.168.226.128)
    |
    | 访问 http://192.168.226.100:80
    ↓
┌─────────────────────────────────┐
│   DS(负载均衡器)               │
│   网卡上配置了两个地址:          │
│   ├── VIP: 192.168.226.100     │  ← 客户端访问的就是这个
│   └── DIP: 192.168.226.10      │  ← 用来和 RS 通信
│                                 │
│   收到包后,做 DNAT:            │
│   目标地址 192.168.226.100:80   │
│   → 改为 192.168.226.30:80      │
└─────────────────────────────────┘
    |
    | 转发给 RS1
    ↓
┌─────────────────────────────────┐
│   RS1 (192.168.226.30)           │
│   处理请求,响应包通过网关        │            #   RS 的网关 = DS(的 DIP 地址),这是让响应包“回家”做地址转换的
│  (192.168.226.10) 回到 DS      │                唯一通道
└─────────────────────────────────┘

核心配置要点(必须满足的条件)

基于 192.168.226.x 网段,NAT 模式有以下硬性要求:

RS 使用私有地址,且网关指向 DIP

角色 IP地址 网关设置
DS(DIP) 192.168.226.10 —
RS1(RIP) 192.168.226.30 默认网关必须指向 192.168.226.10
RS2(RIP) 192.168.226.40 默认网关必须指向 192.168.226.10
  • 为什么? 请求包经过 DS 后,目标 IP 变成了 RS 的私有 IP(如 192.168.226.30)。RS 处理完请求后,响应包要回给客户端(192.168.226.128)。但 RS 自己不知道客户端在哪,所以它的默认网关必须指向 DS 的 DIP。只有这样,响应包才会先回到DS,由DS 做 SNAT 后发出。
  • 若不满足会怎样? RS 可能会把响应包直接丢给它的默认网关(比如路由器),而这个路由器不认识这个连接,就会丢包,导致访问失败。

DIP 和 RIP 属于同一个网段

在我们的环境里,DIP(192.168.226.10)和所有 RIP(192.168.226.30、192.168.226.40)都在 192.168.226.0/24 网段内。

  • 为什么? DS改写目标地址后,要通过二层交换(MAC 地址) 把包发给 RS。如果 DIP 和 RIP 不在同一网段,DS 就需要通过路由转发,这会增加复杂度和延迟。

支持的额外特性

  • 支持端口映射:例如客户端访问 192.168.226.100:8080,DS 可以将其映射到 RS1 的 192.168.226.30:80,实现内外端口不一致。
  • RS 操作系统无关:RS 可以是 Windows、Linux、Unix 等任何操作系统,只要能正常处理 TCP/IP 协议即可。

LVS的DR模式

一、整体流程:一个请求的 “DR 之旅”

DR 模式的核心思想是:请求走 DS,响应直接回客户端,从而避免 NAT 模式中响应流量也经过 DS 的性能瓶颈。

在 192.168.226.x 网段环境下,一个请求的完整流程如下:

步骤 方向 操作 数据包详情(基于192.168.226.xxx)
① 客户端 → DS 用户访问 VIP 客户端 192.168.226.128:12345 → VIP 192.168.226.100:80
② DS 内部 匹配虚拟服务规则 DS 检查规则表,命中 192.168.226.100:80 的服务
③ DS内部 调度算法选 RS 通过调度算法选中RS1(192.168.226.30),在哈希表中记录连接
④ DS → RS1 修改 MAC 地址 DS 将数据帧的目标 MAC 地址改为 RS1 的 MAC 地址,在 LAN 上重新传输。数据包的 IP 地址不变(目标 iP 仍是 192.168.226.100)
⑤ RS1 内部 处理请求 RS1 的 lo 接口上配置了 VIP(192.168.226.100),所以它能接收并处理目标为 VIP 的请求
⑥ RS1 → 客户端 响应直接返回 RS1将响应包源地址设为 192.168.226.100,直接通过网关(路由器 192.168.226.1)发回给客户端,不经过DS

关键区别:DR 模式中,DS 只修改了数据帧的 MAC 地址(在二层工作),而没有修改 IP 地址和端口(没有DNAT)。RS 收到的请求包目标 IP仍然是 VIP,因为 RS 的 lo 接口上也配置了 VIP,所以它能正常处理这个包。

关键校验:

  • ✅ DS 和所有 RS 在同一个二层网络(LAN)中,通过交换机/HUB 相连。
  • ✅ RS 的默认网关指向路由器 192.168.226.1,绝不能指向 DIP(192.168.226.10)。
  • ✅ RS 的 lo 接口上配置了 VIP(192.168.226.100),并设置了 ARP抑制。

二、DR 模式的核心机制详解

1. 为什么 DR 模式只需要改 MAC 地址?

在 NAT 模式中,DS 做 DNAT把目标 IP从 VIP 改成 RIP,所以响应包必须先回DS做 SNAT。

但在 DR 模式中:

  • DS不改IP地址,只把数据帧的目标 MAC 地址从 DS 的 MAC 改成 RS 的 MAC。
  • RS 收到的包:目标 IP 仍然是 192.168.226.100(VIP),但因为 RS 的 lo 接口上也配置了 192.168.226.100,所以 RS 的 TCP/IP 协议栈认为这个包是发给自己的,正常接收并处理。
  • RS 响应时,源地址直接设为 192.168.226.100,因为客户端访问的就是这个 IP,所以客户端能正确识别响应包。

text

客户端 192.168.226.128
    |
    | 请求:目标 IP = 192.168.226.100
    ↓
┌──────────────────────────────────────────────────┐
│  DS(负载均衡器)                                │
│  ┌──────────────────────────────────────────┐   │
│  │  收到请求包:                            │   │
│  │  源 IP: 192.168.226.128                  │   │
│  │  目标 IP: 192.168.226.100 (VIP)         │   │
│  │  目标 MAC: DS 的 MAC                    │   │
│  └──────────────────────────────────────────┘   │
│  调度算法选中 RS1                               │
│  ↓ 只改 MAC,不改 IP                          │
│  ┌──────────────────────────────────────────┐   │
│  │  转发包:                                │   │
│  │  源 IP: 192.168.226.128 (不变)           │   │
│  │  目标 IP: 192.168.226.100 (VIP, 不变)   │   │
│  │  目标 MAC: 改为 RS1 的 MAC              │   │
│  └──────────────────────────────────────────┘   │
└──────────────────────────────────────────────────┘
    ↓
┌──────────────────────────────────────────────────┐
│  RS1(物理网卡 IP: 192.168.226.30)              │
│  lo 接口别名: 192.168.226.100                   │
│  ┌──────────────────────────────────────────┐   │
│  │  收到包,检查目标 IP: 192.168.226.100   │   │
│  │  → lo 上有这个 IP,收下!               │   │
│  │  处理请求,生成响应                      │   │
│  └──────────────────────────────────────────┘   │
│  ↓ 响应直接发,不经过 DS                        │
│  源 IP: 192.168.226.100 (VIP)                  │
│  目标 IP: 192.168.226.128                       │
└──────────────────────────────────────────────────┘
    ↓
直接到达客户端 192.168.226.128 ✅

==本质==:

有相同的 VIP → 能直接通信 → 绕过 DS”

  1. “有相同的 VIP” → RS 的 lo 接口上配置了和 DS一样的 VIP,所以 RS 认为自己也是这个 IP的“主人”。
  2. “能直接通信” → RS 收到请求后,直接以 VIP 的身份响应客户端,不需要经过 DS转手。
  3. “绕过 DS” → 响应流量完全不经过DS,减轻了 DS的压力,这就是 DR 模式性能高的根本原因

ARP 抑制(最关键的一步!)

为什么需要 ARP抑制?

在 192.168.226.x 网段中,DS 和所有 RS 的 lo 接口都配置了 VIP(192.168.226.100)。如果不做任何限制:

  • 当路由器 192.168.226.1 询问“谁有 192.168.226.100 的 MAC 地址?”时,DS 和所有 RS 都会响应。
  • 路由器可能把 VIP 的 MAC 地址映射到某一台 RS 上,导致部分请求绕过 DS直接发给 RS,破坏了负载均衡。

2、 ARP 抑制的作用(核心:防止“喧宾夺主”)

在LVS-DR 模式中,DS(调度器)和 RS(真实服务器)都配置了同一个 VIP。如果不做处理,当客户端发送ARP请求询问“谁是 VIP?”时,RS 也会响应,导致客户端直接把请求发给 RS,==DS 就失去了负载均衡的作用==。

ARP 抑制的具体作用:

  1. 隐藏 RS 的 VIP:让 RS 在网络上“装作不知道”自己有这个 VIP,不响应外部对 VIP 的 ARP 请求。
  2. 确保流量入口唯一:保证只有 DS 会响应 VIP 的 ARP请求,从而确保所有客户端的请求必须先经过 DS。
  3. 实现原理:通常通过修改 Linux 内核参数(arp_ignore 和 arp_announce)来实现,让 RS 只在特定的 lo 接口上识别 VIP,而不在物理网卡上对外宣告 VIP。

3、LVS-DR 模式核心总结(本质与流程)

DR 模式的本质可以总结为以下三点:

  1. 请求必须经过 DS(入口在 DS)
    • 客户端请求到达DS,DS 根据调度算法选择一台 RS。
    • 关键动作:DS只修改目标 MAC 地址(改为 RS 的 MAC),不修改源IP和目标IP(目标 IP 依然是 VIP)。
    • DS 将数据包转发给选中的 RS。
  2. 响应不经过DS(出口在 RS)
    • RS 收到数据包后,发现目标IP是自己 lo 接口上的 VIP,于是接收并处理请求。
    • 关键动作:RS 处理完毕后,直接以 VIP 为源IP,客户端IP 为目标IP,将响应包直接发送给客户端。
    • 响应流量完全不经过 DS,这就是 DR 模式性能极高的根本原因(DS 只处理入站请求,不处理出站响应,吞吐量极大提升)。
  3. ARP 抑制是前提(保障机制)
    • 正是因为有了ARP 抑制,才保证了“请求先到 DS”这一前提。
    • 如果没有ARP抑制,客户端可能直接找到 RS,DS 就形同虚设。

LVS的TUN模式

在LVS(NAT)模式的集群环境中,由于所有的数据请求及响应的数据包都需要经过LVS调度器转发,如果后端服务器的数量大于10台,则调度器就会成为整个集群环境的瓶颈。我们知道,数据请求包往往远小于响应数据包的大小。因为响应数据包中包含有客户需要的具体数据,所以LVS(TUN)的思路就是将请求与响应数据分离,让调度器仅处理数据请求,而让真实服务器响应数据包直接返回给客户端。IP隧道(IP tunning)是一种数据包封装技术,它可以将原始数据包封装并添加新的包头(内容包括新的源地址及端口、目标地址及端口),从而实现将一个目标为调度器的VIP地址的数据包封装,通过隧道转发给后端的真实服务器(Real Server),通过将客户端发往调度器的原始数据包封装,并在其基础上添加新的数据包头(修改目标地址为调度器选择出来的真实服务器的IP地址及对应端口),LVS(TUN)模式要求真实服务器可以直接与外部网络连接,真实服务器在收到请求数据包后直接给客户端主机响应数据。

一、整体流程:一个请求的 “TUN 之旅”

text

客户端 (CIP)
  |
  | 请求:目标 IP = VIP
  ↓
[ DS (调度器) ]
  |
  | 1. 收到请求包(目标 IP 是 VIP)
  | 2. 调度算法选中 RS1
  | 3. 关键动作:IP 隧道封装
  |    - 在原始数据包外,再加一层新的 IP 包头
  |    - 新目标 IP = RS1 的 RIP (192.168.226.30)
  |    - 新源 IP = DS 的 DIP (192.168.226.100)
  |
  | 封装后的包: [ 新 IP 头 (DIP -> RIP) ] + [ 原始 IP 头 (CIP -> VIP) + 数据 ]
  ↓
[ RS1 (真实服务器) ]
  |
  | 1. 收到封装包,发现新目标 IP 是自己(RIP),于是拆开外层包头
  | 2. 看到内层原始包头,目标 IP 是 VIP,而自己的 lo 接口上配置了 VIP
  | 3. 接收并处理请求,生成响应
  |
  | 4. 响应:直接通过物理网卡发回给客户端
  |    - 源 IP = VIP (192.168.226.100)
  |    - 目标 IP = CIP (192.168.226.128)
  ↓
直接到达客户端 (CIP) ✅

二、 TUN 模式的核心机制详解

1. 为什么 TUN 模式需要 IP隧道封装?

在 DR 模式中,DS 和 RS 必须在同一个物理网段(二层网络),因为 DS 是通过修改 MAC 地址 来转发数据包的。 但在 TUN 模式中,DS 和 RS 可以跨越不同的网段甚至不同的机房(三层网络)。因为无法直接修改 MAC 地址,DS必须使用 IP隧道技术(IPIP):

  • DS端:把原始数据包(目标IP是 VIP)整个作为数据,再在外面套上一个新的 IP 包头(目标IP 是 RS 的 RIP),然后通过路由发出去。
  • RS 端:收到包后,剥掉外层 IP包头,露出里面的原始数据包。发现目标 IP是 VIP,且自己的 lo 接口上有 VIP,于是正常处理。

2. RS 响应时如何直接回给客户端?

与 DR 模式完全一致:

  • RS 处理完请求后,源 IP 设为 VIP(因为客户端请求的就是这个IP),目标IP 设为客户端 IP。
  • RS 通过自己的物理网卡,直接将该响应包发送给客户端(或通过 RS 自己的网关路由出去),完全不经过 DS。

3. 隧道机制的关键要求

  • RS 必须支持IP隧道协议:RS 的操作系统内核必须支持 IPIP 隧道(Linux 默认支持,通过 modprobe ipip 加载模块)。
  • RS 的 lo 接口必须配置 VIP:这是让 RS 认为“这个包是发给我的”的关键,也是实现响应源 IP 为 VIP 的前提。
  • RS 的网关不能指向DS:因为响应包要直接发给客户端,如果网关指向DS,响应包就会先发给DS,违背了“响应不经过DS”的初衷,DS又会成为瓶颈。

三、 TUN 模式的核心配置要点(必须满足的条件)

  1. VIP 配置:DS 的物理网卡上配置 VIP;RS 的 lo 接口上配置 VIP(通常需要做ARP 抑制,虽然 TUN 模式下DS和 RS 可能不在同一网段,ARP 影响较小,但配置 lo 依然是标准做法)。
  2. 隧道配置:DS 和 RS 都需要配置IPIP 隧道(如 ip tunnel add tunl0 mode ipip),并分配隧道 IP。
  3. 路由配置:DS需要将发往 RS 的包通过隧道路由出去;RS 需要将响应包通过物理网卡路由给客户端(不能经过 DS)。
  4. RS 网关:RS 的默认网关绝对不能指向DS的 DIP,必须指向自己所在网段的真实网关(或直接配置去往客户端网段的路由)。

四、 TUN 模式支持的额外特性

  • 跨网段部署:DIP、VIP、RIP 可以是公网地址,且可以分布在不同的物理网段,甚至跨越机房(这是 TUN 最大的优势)。
  • 请求报文经由 Director,响应不经由 Director:与 DR 模式一样,实现了请求与响应的分离,极大提升了集群的吞吐量。
  • 不支持端口映射:与 DR 模式一样,DS 不修改端口,因此无法做端口映射(如把 80 端口映射到 8080)。
  • RS 的 OS 须支持隧道功能:这是 TUN 模式特有的硬性要求。

五、 LVS-TUN 模式核心总结(本质与流程)

  1. 请求必须经过 DS(入口在 DS)
    • 客户端请求到达 DS,DS根据调度算法选择一台 RS。
    • 关键动作:DS不修改原始 IP 包头,而是在原始包外再封装一层新的IP包头(新目标IP 是 RS 的 RIP),通过隧道发给 RS。
  2. 响应不经过DS(出口在 RS)
    • RS 收到封装包,剥离外层包头,发现内层目标IP 是 VIP(自己的 lo 上有),于是处理请求。
    • 关键动作:RS 处理完毕后,直接以 VIP 为源IP,客户端 IP为目标 IP,将响应包直接发送给客户端。
    • 响应流量完全不经过DS,减轻了 DS 的压力。
  3. ARP抑制与隧道机制
    • ARP 抑制在 TUN 模式中依然重要(防止 RS 在物理网络上响应 VIP 的ARP),但 TUN 模式更核心的是 IP隧道封装,它解决了 DR 模式不能跨网段的限制。

一句话终极总结():

“TUN 模式用 IP 隧道封装突破网段限制,DS 只负责加个壳转发请求,RS 拆壳处理后直接回包给客户端;请求走 DS,响应走 RS,性能高且能跨机房。”


LVS-DR与 LVS-TUN 终极对比总结

本质定性: TUN 模式本质上是 DR 模式在跨网段场景下的“升级版”。两者的核心思想完全一致,即“请求经过DS,响应不经过 DS”,从而实现请求与响应的分离,极大提升集群吞吐量。

唯一的核心区别(DS如何转发请求):

  • DR 模式(二层转发):在数据链路层修改目标 MAC 地址(改为 RS 的 MAC),要求 DS 与 RS 必须在同一物理网段(二层互通)。
  • TUN 模式(三层转发):在网络层给原始数据包加一层新的IP 包头(IP 隧道封装,目标 IP为 RS 的 RIP),允许 DS与 RS 在不同网段(三层互通,可跨机房)。

核心原理全一样,请求过DS,响应不过 DS;区别只在怎么转,DR 改 MAC 限同网段,TUN 套 IP 可跨网段。”


LVS的三大类算法

一、 静态算法(Static Scheduling)

核心特点:调度器不关心后端服务器当前有多忙、是否宕机,只根据预设的规则(如轮流、权重、源 IP)进行分配。适合无状态、处理时间短且服务器性能相近的服务。

1. 轮询(Round Robin, RR)

  • 原理:最公平的“排排坐,吃果果”。请求 1 给 A,请求 2 给 B,请求 3 给 C,请求 4 再回到 A。
  • 适用:无状态服务(如静态网页),且所有服务器性能完全一致。
  • 缺点:现实中服务器性能往往不同,性能差的机器容易被压垮。

2. 加权轮询(Weighted Round Robin, WRR)

  • 原理:给性能好的服务器更高的权重。比如 A(权重3),B(权重1),那么分配序列就是 A, A, A, B, A, A, A, B… 按比例分配。
  • 适用:服务器性能差异较大的场景,通过权重来“能者多劳”。

3. 源地址哈希(Source IPHash, SH)

  • 原理:对客户端的源IP 进行哈希计算,同一个IP的请求永远固定分配给同一台服务器。
  • 适用:需要会话保持(Session 保持)的场景。比如用户登录后,如果下次请求被分到另一台机器,Session 就丢了,SH 可以解决这个问题。
  • 缺点:如果某个 IP段(比如一个公司出口)疯狂发请求,会导致那台服务器压力过大(负载倾斜)。

二、 动态算法(Dynamic Scheduling)

核心特点:调度器会实时监控后端服务器的连接数等状态,动态调整分配。适合处理时间差异大、负载变化快的场景。

1. 最少连接(Least Connections, LC)

  • 原理:谁的当前连接数最少,就把新请求给谁。这能动态平衡负载。
  • 适用:请求处理时间差异大的场景(如数据库查询、复杂计算),因为处理慢的机器连接数会积累,新请求就不给它了。
  • 缺点:不看服务器性能。一台性能极强但当前连接数稍多的服务器,可能不如一台性能差但连接数少的服务器受青睐。

2. 加权最少连接(Weighted Least Connections, WLC)

  • 原理:LVS 的默认算法。结合了“权重”和“连接数”。公式:当前连接数 ÷ 权重。计算结果越小,说明该服务器相对越“闲”,优先分配。
  • 例子:A(权重2,连接4,4/2=2),B(权重1,连接1,1/1=1)。B 的值更小,所以新请求给 B。
  • 适用:服务器性能差异大,且请求处理时间不稳定的场景(最通用、最智能的算法之一)。

3. 基于局部性的最少连接(Locality-Based Least Connections, LBLC)

  • 原理:专为缓存集群(如 CDN、Squid)设计。它优先把请求分配给“已经缓存了目标内容”的服务器。如果都没缓存,就按 LC(最少连接)分配。
  • 适用:缓存服务器集群,目的是提高缓存命中率,减少回源。

4. 带复制的基于局部性最少连接(LBLCR)

  • 原理:在 LBLC基础上增加了“复制”功能。如果目标内容没在任何服务器上,LBLC只是随便找台机器处理,而 LBLCR 会把内容复制到负载较轻的机器上,并让后续请求都指向这台机器。
  • 适用:大型缓存集群,进一步提升命中率。

5. 目标地址哈希(DestinationIP Hash, DH)

  • 原理:对请求的目标IP 进行哈希,同一目标 IP 的请求固定给同一台服务器。
  • 适用:正向代理场景(如网关转发到特定后端服务)。

三、 其他算法(特殊场景优化)

这些算法在 WLC 和 SED 的基础上,为了减少延迟或避免排队而设计。

1. 最短预期延迟(Shortest Expected Delay, SED)

  • 原理:基于 (当前连接数 + 1) ÷ 权重 计算。与 WLC 类似,但更激进地倾向于权重高的服务器。
  • 场景:需要优先利用高性能服务器的场景。

2. 永不队列(Never Queue, NQ)

  • 原理:如果存在空闲服务器(连接数为 0),直接分配给它,不排队。如果都忙,再按 SED 算法分配。
  • 场景:对响应延迟要求极高的场景,避免请求在高负载服务器上排队。

核心总结与选择指南(速查表)

算法类型 算法名称 核心逻辑 典型适用场景
静态 RR 轮流分配 无状态、性能一致的集群
静态 WRR 按权重轮流 性能差异大、无状态
静态 SH 源IP哈希 需要 Session 保持
动态 LC 谁连接少给谁 请求处理时间差异大
动态 WLC (连接数/权重) 最小优先 最通用,性能差异大且请求时间不稳
动态 LBLC 优先给有缓存的机器 缓存集群(CDN)
动态 LBLCR 缓存复制 + 优先给有缓存的 大型缓存集群
动态 DH 目标IP 哈希 正向代理、网关转发
其他 SED (连接数+1)/权重 最小优先 优先压榨高性能服务器
其他 NQ 有空闲机器就上,不等 极低延迟要求,避免排队

一句话记忆法:

  • 要会话保持用 SH;
  • 要智能动态用 WLC(默认);
  • 要缓存优化用 LBLC/LBLCR;
  • 要极致低延迟用 NQ。

LVS 的NAT案例

image-20260914111442797

LVS之NAT案例
注意:RS的网关一定要指向DS的仅主机网卡地址
# 环境准备
DIP:192.168.195.10/24
VIP:192.168.226.10/16
RIP1:192.168.195.20   GATEWAY:192.168.195.10     # GATEWAY是RS网关要指向DS的DIP(物理网卡地址)
RIP2:192.168.195.30   GATEWAY:192.168.195.10
# DS部署
1.安装ipvsadm工具
[root@server1 ~]# yum install ipvsadm ‐y
2.打开转发,配置nat
[root@server1 ~]# echo 1 >> /proc/sys/net/ipv4/ip_forward
[root@server1 ~]# iptables ‐t nat ‐F
[root@server1 ~]# iptables ‐t nat -A POSTROUTING -s 192.168.195.0/24 -o ens160 -j MASQUERADE
3.设置wrr策略和添加RS主机
[root@server1 ~]# ipvsadm ‐A ‐t 192.168.226.10:80 ‐s wrr
[root@server1 ~]# ipvsadm ‐a ‐t 192.168.226.10:80 ‐r 192.168.195.11:80 ‐m ‐w 1
[root@server1 ~]# ipvsadm ‐a ‐t 192.168.226.10:80 ‐r 192.168.195.12:80 ‐m ‐w 1
4.查看策略信息
[root@server1 ~]# ipvsadm ‐ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
‐> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.226.10:80 wrr
‐> 192.168.195.11:80 Masq 1 0 0
‐> 192.168.195.12:80 Masq 1 0 0
# RS部署
# RS的网关必须指向DS
[root@server2 ~]# echo "RS2" > /usr/share/nginx/html/index.html
[root@server3 ~]# echo "RS3" > /usr/share/nginx/html/index.html
[root@server1 ~]# curl 192.168.226.10
RS1
[root@server1 ~]# curl 192.168.226.10
RS2
注意:selinux策略和防火墙!

一、 核心角色与网卡规划表

在 LVSNAT 模式中,Director(负载均衡器)需要两块网卡,Real Server(真实服务器)通常只需要一块内网网卡。

设备名称 角色 网卡名称 VMware 网络模式 IP规划 (示例) 说明
Director (LVS) 负载均衡器 ens160 NAT 模式 (VMnet8) VIP: 192.168.226.100 (实际IP: 192.168.226.18) 对外网卡。负责接收客户端请求,并作为源IP 将响应发回给客户端。
Director (LVS) 负载均衡器 ens224 仅主机模式 (VMnet1) DIP: 192.168.195.10 对内网卡。负责将请求转发给后端的 RS,并接收 RS 返回的数据。
Real Server 1 后端真实服务器 ens160 仅主机模式 (VMnet1) RS1 IP: 192.168.195.11 网关: 192.168.195.10 提供实际服务。网关必须指向 Director 的 DIP。
Real Server 2 后端真实服务器 ens160 仅主机模式 (VMnet1) RS2 IP: 192.168.195.12网关: 192.168.195.10 提供实际服务。网关必须指向 Director 的 DIP。
客户端 (Client) 访问请求方 – 与 VIP 连通即可 宿主机或另一台 NAT 模式虚拟机 通过访问 VIP(192.168.226.100)来测试负载均衡。

二、 配置操作三大核心步骤

1. VMware 层面设置

  • 虚拟网络编辑器 (图1):
    • VMnet8 (NAT模式):子网设为 192.168.226.0,开启 DHCP。
    • VMnet1 (仅主机模式):子网设为 192.168.195.0,开启 DHCP。
  • 虚拟机设置 (图2):
    • Director 虚拟机:添加两块网卡。“网络适配器”选 NAT,“网络适配器2”选仅主机。
    • RS 虚拟机:只保留一块网卡,选“仅主机模式”。

2. Linux 系统层面配置(静态 IP)

  • Director 节点:
    • 修改 ens160 配置文件:设置静态IP 192.168.226.18(==实际ip不是DIP==),再添加一个vip:192.168.226.100
    • 修改 ens224 配置文件:设置静态IP 192.168.195.10(不要配默认网关)。
    • 添加 VIP:通过命令 ifconfig ens160:0 192.168.226.100 netmask 255.255.255.0 up 或配置文件添加。
  • RS 节点:
    • 修改网卡配置文件:设置静态IP 192.168.195.11 和 192.168.195.12。
    • 关键: 将它们的 GATEWAY 设置为 192.168.195.10(即 Director 的 DIP)。

3. Director 系统内核配置

  • 开启 IP 转发(NAT 模式必须):

    bash

    echo 1 > /proc/sys/net/ipv4/ip_forward
    # 永久生效需写入 /etc/sysctl.conf

三、 核心逻辑总结(一句话记忆)

  • VIP 走 NAT 模式:用来“对外”接收客户端的请求,并伪装成客户端与 RS 通信的源地址。
  • DIP 走仅主机模式:用来“对内”和 RS 建立专属局域网,确保 Director 能直接找到 RS。
  • RS 的网关必须指向 DIP:这是 NAT 模式回包的关键,确保 RS 处理完的数据能原路返回给 Director,再由 Director 发回给客户端。
  • 别让两张网卡在同一网段:DIP 和 VIP 必须处于不同的网段(195 和 226),否则路由器会“精神分裂”,导致 NAT 转发失败。

1. 为什么一个网卡能有多个IP?

网卡(如 ens160)在物理层面只是一块硬件,但操作系统可以通过软件的方式,在这块硬件上绑定多个逻辑IP。

  • 一个是主 IP(Primary IP):比如 192.168.226.18,这是网卡自己的真实地址。
  • 其余的是辅助IP/ 别名(Secondary IP/ Alias):比如 192.168.226.100,这就是我们给 LVS配置的 VIP。

2. 在您的架构中,ens160是怎么拥有两个IP 的?

在您的 Director(负载均衡器)上,ens160 这块网卡同时绑定了两个 226 网段的 IP:

  • 实际 IP(192.168.226.18):这是 Director 用来和宿主机通信、或者您用来 SSH 登录管理的IP。
  • VIP(192.168.226.100):这是绑定在 ens160 上的虚拟IP。当客户端访问 192.168.226.100 时,数据包会进入 ens160,LVS软件会拦截这个请求并进行转发。

在终端中看起来是这样的(通过 ip a 命令):

text

2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:0c:29:72:0b:3d brd ff:ff:ff:ff:ff:ff
    inet 192.168.226.18/24 brd 192.168.226.255 scope global noprefixroute ens160
       valid_lft forever preferred_lft forever
    inet 192.168.226.100/24 scope global secondary ens160   <--- 这里多了一个 VIP
       valid_lft forever preferred_lft forever

注意看最后一行,多了一个 secondary 标志,这就是同一个网卡上的第二个 IP。

image-20260912003002246

image-20260912120941620


ipvsadm作用

它是 Linux 内核中LVS(负载均衡)功能的“遥控器”和管理工具。

具体来说,它的作用只有以下三点:

  1. 配置虚拟服务(VIP):告诉系统对外提供服务的 IP 和端口是什么(比如 192.168.226.100:80)。
  2. 管理后端服务器(RS):把真实的服务器 IP 绑定到这个 VIP 后面,让它们组成一个服务器集群。
  3. 查看和修改调度规则:决定用什么算法(比如轮询)把客户端的请求分发给后端的真实服务器,同时可以随时查看当前的连接状态或删除规则。

nmtui 的作用

nmtui 是 Linux 系统中用来配置网络的一个“图形化文本界面”工具。

(全称是 NetworkManager Text User Interface,即网络管理器的文本用户界面)

具体来说,它的核心作用有以下三点:

  1. 图形化配置网络:它提供了一个类似 BIOS 那样的蓝底菜单界面。您不需要去死记硬背繁琐的配置文件路径(比如 /etc/sysconfig/network-scripts/ifcfg-ens160),也不需要记复杂的 nmcli 长命令。直接用键盘方向键和回车,就能修改 IP、网关、DNS。
  2. 轻松激活/停用网卡:可以在界面里直接启用或禁用某张网卡,修改完配置后能一键生效,不需要每次敲命令重启整个网络服务。
  3. 管理网络连接:可以添加新的网络连接(比如添加新的 VLAN、绑定网卡等),或者删除不需要的旧连接。

image-20260912131334067

image-20260912132023147

image-20260912132433942

image-20260912141042053

核心总结:为什么 RS 设置好后,MobaXterm 无法直接 SSH?

一句话结论: 因为您把 RS 的网卡配置成了“仅主机模式”,并设置了 195 内网网段 IP。这相当于把 RS 关进了一个没有外网入口的“密室”。而您的 Windows 宿主机(运行 MobaXterm 的电脑)默认处于 226 外网(NAT)网段,两者不在同一个局域网内,网络物理隔离,所以 MobaXterm 的连接请求找不到目标,必然失败。


详细原因拆解:

  1. IP地址不匹配(根本原因):
    • 您的 MobaXterm 所在宿主机:处于 192.168.226.x 网段(NAT模式)。
    • 您的 RS 服务器:处于 192.168.195.11(仅主机模式)。
    • 这就像您在北京打电话,要找的人在上海,但没有长途线路,根本找不到。
  2. VMware仅主机模式的特性:
    • “仅主机模式(VMnet1)”设计的初衷,就是创建一个封闭的虚拟内网,用来模拟真实机房里的“内网隔离区”。默认情况下,它拒绝外部设备直接访问。
  3. 网卡名字无关:
    • 无论这块网卡叫 ens160 还是 ens224,只要它插在“仅主机模式”的虚拟交换机上,并且配了 195 网段的IP,它就注定无法被外网直接访问。名字只是 Linux 分配的代号,决定连接能不能通的是“IP 网段”和“网络模式”。

最终定论:

“RS 配好后 MobaXterm连不上”不是配置错了,而是成功实现了“后端服务器与外网隔离”的实验目标!


第一步:打开转发,配置 NAT(解决“怎么去”和“怎么回”的问题)

1. echo 1 >> /proc/sys/net/ipv4/ip_forward

  • 作用:开启 Linux 系统的路由转发功能。
  • 为什么需要:你的 server1 作为调度器,当外部用户的请求打到它身上时,它不能自己处理,必须把数据包“转发”给后面的 RS 服务器。如果不写这个 1(默认为0),系统内核会直接丢弃这些需要转发的数据包。

2. iptables -t nat -F

  • 作用:清空 nat 表(Network Address Translation)原有的所有规则。
  • 为什么需要:这是为了排除干扰,防止之前配置的旧 NAT 规则影响当前实验,相当于“清理桌面”。

3. iptables -t nat -A POSTROUTING -s 192.168.195.0/24 -o ens160 -j MASQUERADE

  • 作用:配置源地址伪装(SNAT),这是 NAT 模式的核心。
  • 详细解释:
    • 当后端的 RS 服务器(在 192.168.195.0/24 网段)处理完请求,准备把网页数据发回给最终用户时,因为它们处于内网,无法直接连通外网。
    • 这条命令的意思是:凡是来自 192.168.195.0/24 网段的数据包,在从 ens160 这块网卡(通常是外网网卡)发出去的时候,把数据包的“发件人 IP”偷偷改成 server1自己的外网 IP。
    • 这样用户收到数据时,以为是server1 发的,从而实现完整的双向通信。

第二步:设置 wrr 策略和添加 RS 主机(解决“分给谁”的问题)

4. ipvsadm -A -t 192.168.226.10:80 -s wrr

  • 作用:创建一个对外提供服务的“虚拟 IP”(VIP)。
  • 参数拆解:
    • -A:添加一个虚拟服务 (Add Virtual Service)。
    • -t 192.168.226.10:80:指定对外暴露的 IP和端口(这就是用户最终访问的地址)。
    • -s wrr:指定调度算法为 加权轮询 (Weighted Round Robin)。意思是按权重轮流分配,谁的权重高,谁接到的请求就多。

5. ipvsadm -a -t 192.168.226.10:80 -r 192.168.195.11:80 -m -w 1 6. ipvsadm -a -t 192.168.226.10:80 -r 192.168.195.12:80 -m -w 1

  • 作用:把真正干活的服务器(RS)加入到这个虚拟服务里。
  • 参数拆解:
    • -a:添加真实服务器 (Add Real Server)。
    • -r 192.168.195.11:80:指定真实服务器的内网IP和端口。
    • -m:指定使用 NAT 模式 (Masquerading)。
    • -w 1:给这台服务器分配的权重是 1。因为两台都是 1,所以请求会 1:1 平分。

第三步:查看策略信息(验证成果)

7. ipvsadm -ln

  • 作用:以数字格式(-n)列出(-l)当前的 LVS 规则。
  • 为什么需要:让你检查刚才配置的对不对。

输出结果

text

TCP  192.168.226.10:80 wrr
  -> 192.168.195.11:80       Masq    1      0          0
  -> 192.168.195.12:80       Masq    1      0          0
  • Masq 说明是 NAT 模式。
  • 权重都是 1,且状态正常(后面的连接数为 0)。

==清空旧的nat规则防止干扰,开启系统内核的转发功能,配置SNAT源地址伪装实现NAT模式,最后把真实服务器(RS)加入到LVS虚拟服务中==

image-20260914095728720

image-20260914100354997


第一阶段:临时借用 NAT 网络(安装软件)

  1. 关机:先将作为 RS 的虚拟机(server2 或 server3)关机。
    1. 添加网卡:在 VMware 的虚拟机设置里,添加一块新网卡,网络连接选择 “NAT 模式”。此时这台机器有了一块 NAT 网卡和一块仅主机网卡。
  2. 开机联网:启动虚拟机,确保系统能正常获取到 NAT 网卡分配的 IP 并能 ping 通外网(比如 ping www.baidu.com)。
  3. 安装 Nginx:执行安装命令 yum install nginx -y

image-20260914105442469

image-20260914111004409


LVS 的DR案例

image-20260914114647548

LVS之DR案例
# DS部署
[root@server1 ~]# ifconfig ens160:0 192.168.226.100 broadcast 192.168.226.255 netmask 255.255.255.0 up
[root@server1 ~]# route add -host 192.168.226.100 dev ens160:0
[root@server1 ~]# ipvsadm -A -t 192.168.226.100:80 -s wrr
[root@server1 ~]# ipvsadm -a -t 192.168.226.100:80 -r 192.168.226.20:80 -g -w 2
[root@server1 ~]# ipvsadm ‐a ‐t 192.168.226.100:80 ‐r 192.168.226.30:80 ‐g ‐w 1
# RS部署
[root@server2 ~]# cat rs.sh
#!/bin/bash
vip=192.168.226.100
ifconfig lo:0 $vip broadcast 192.168.226.255 netmask 255.255.255.255 up
route add -host $vip lo:0
echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/lo/arp_announce
echo "1" >/proc/sys/net/ipv4/conf/all/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/all/arp_announce
在所有RS上部署!

不要在DS上直接进行测试,可能会因为网络的问题导致丢包

1. 如果绑在物理网卡上(灾难发生)

  • 现象:RS 的物理网卡上有了 VIP,它就会认为“我是这个 VIP”。
  • ARP 响应:当客户端在局域网里广播:“谁是 192.168.226.100?”时,RS 的物理网卡会立刻抢答:“我是!我的 MAC 地址是 xx:xx…”。
  • 结果:客户端直接把请求发给了 RS。DS(调度器)彻底被架空,负载均衡失效。而且DS 也会抢答,导致局域网里有两个“192.168.226.100”,引发 ARP冲突,网络时通时断,彻底瘫痪。

2. 绑在回环接口 lo 上(完美方案)

  • 现象:RS 把 VIP 绑在 lo(内部对讲机)上。
  • 对外(装聋作哑):因为 lo不是物理网卡,它接收不到外部局域网发来的ARP广播。RS 假装不知道外面有人在找 VIP。
  • 对内(负责干活):当DS 把请求转发过来时,数据包里写着的目标 IP 是 VIP。RS 拆开包一看:“目标 IP 是 192.168.226.100,我 lo 接口上正好有这个 IP,这包是给我的!” 于是收下并处理。
  • 配合ARP 抑制:为了双重保险,那四行 echo 命令还强制告诉内核:“如果别人问 VIP 的 MAC 地址,你要假装没听见(arp_ignore);如果要主动发ARP 请求,千万别用 VIP 当源 IP(arp_announce)。”

总结:

  • 物理网卡绑 VIP = 对外广播“我是 VIP”,会引发混乱。
  • lo 绑 VIP = 对外隐身,对内干活,完美承接 DS 转发来的包。

一、 网络拓扑与核心IP

  • DS(调度器):server1,物理 IP 为 192.168.226.10,网卡名为 ens160。
  • RS(真实服务器):server2(IP: .20)和 server3(IP: .30)。
  • VIP(虚拟IP):192.168.226.100。
    • 特点:DS 和所有 RS 身上都拥有这个 VIP,这是 DR 模式的核心。

二、 DS(调度器)部署详解原因

1. ifconfig ens160:0 192.168.226.100 broadcast 192.168.226.255 netmask 255.255.255.0 up

  • 作用:在物理网卡 ens160 上,绑定一个虚拟子接口 ens160:0,并配置 VIP 192.168.226.100。
  • 为什么这么做:这是对外服务的唯一入口。客户端只知道这个 VIP,不知道后面的 RS。配置在子接口上是为了逻辑清晰,方便后续维护。

2. route add -host 192.168.226.100 dev ens160:0

  • 作用:添加一条主机路由,强制去往 VIP 的流量从 ens160:0 接口出去。
  • 为什么这么做:Linux 系统默认的路由策略可能会把包发到别的网卡上。加这条路由是为了精准锁定,确保 DS能正确处理 VIP 的流量。

3. ipvsadm -A -t 192.168.226.100:80 -s wrr

  • 作用:创建一个虚拟服务,监听 VIP:80,使用 加权轮询(wrr) 调度算法。
  • 为什么这么做:建立 LVS 的核心账本,告诉系统以后遇到访问 192.168.226.100:80 的请求,要按照权重轮流分配。

4. ipvsadm -a -t 192.168.226.100:80 -r 192.168.226.20:80 -g -w 2 5. ipvsadm -a -t 192.168.226.100:80 -r 192.168.226.30:80 -g -w 1

  • 作用:把两台 RS 加入集群。
  • 关键参数 -g:这是 DR 模式(Gatewaying) 的标志。它告诉LVS:只修改数据包的 目标 MAC 地址,不要修改 IP,直接把包“扔”给后端真实服务器。
  • 权重 -w:.20 权重是 2,.30 权重是 1。表示每 3 个请求,2 个给 .20,1 个给 .30。

==核心总结==:route add -host $vip lo:0 的本质

1. 本质定义:一个“流量拦截器” 这条路由指令并不是决定数据包最终的物理去向,而是在 Linux 内核的路由表中强行插入了一条规则:凡是目标 IP为 VIP 的数据包,一律不准直接走物理网卡,必须先进入本地回环接口 lo:0。

2. 核心作用:避免物理网络的“IP 冲突” 为什么要多此一举?因为 RS 在回包时,源 IP 必须是 VIP。如果这个包直接从物理网卡发出去,局域网内的交换机和网关会同时看到 LVS 和 RS 都在使用 VIP,引发严重的 ARP 冲突和网络瘫痪。这条命令把流量关进 lo:0(死胡同),完美避开了物理网络的干扰。

3. 底层机制:内核的“二次路由”与“偷梁换柱” 数据包进入 lo:0 后,并没有消失。Linux 内核的Netfilter/路由子系统会拦截这个包,发现它其实是发给外部客户端的。于是内核在内部自动完成“二次路由”:

  • 重新查表,找到通往客户端的真正出口(通常是内网网关)。
  • 把数据包重新定向到物理网卡,最终成功发送给网关,且不给局域网添乱。

4. 最终闭环:LVS-DR 高性能的基石 这条命令与 ifconfig lo:0 $vip 以及 ARP抑制参数配合,让 RS 实现了完美的“隐身收发”:

  • 收得进:靠 VIP 绑在 lo:0 上,接收LVS 转发的请求。
  • 发得出:靠这条主机路由,在内部绕行后从物理网卡直接把响应发给网关。
  • 不冲突:全程不把 VIP 暴露在物理二层网络上,确保了LVS调度器的绝对权威。

一句话概括: route add -host $vip lo:0 就是给 RS 画了一条“内部单行道”,让 VIP 的流量在本地回环里绕个弯,从而安全、高效地完成响应,彻底解放了 LVS 的带宽压力。

2.如果没有 route add -host $vip lo:0 这条命令,回包会从哪走?

是从 RS 自己的物理网卡(比如ens160) 回去。

模拟一下没有这条路由时,RS1(真实服务器)的尴尬处境:

  1. RS1 收到了 LVS 发来的请求,准备把网页发给客户端。
  2. RS1 构造响应包:源IP 是 VIP(192.168.226.100),目标 IP是客户端(192.168.226.1)。
  3. Linux 系统要发包,会去查本机的路由表:“我要去 192.168.226.1,该从哪个网卡出去?”
  4. 因为 RS1 的物理网卡(ens160)正好在 192.168.226.0/24 网段,系统会默认选择从物理网卡 ens160 发出去。
  5. 灾难发生了:这个包一旦从物理网卡发出去,源IP 就是 192.168.226.100(VIP)。
  6. 局域网里的交换机和网关看到这个包,会瞬间懵圈:“VIP 不是绑在LVS 身上的吗?怎么 RS1 也在用这个IP发包?”。这会导致网络里出现 IP 地址冲突 /ARP 混乱,整个 LVS 集群可能会直接瘫痪断网。

# 1. 定义 VIP 变量(方便后续调用)
vip=192.168.226.100

# 2. 在回环接口 lo:0 上绑定 VIP,子网掩码必须是 255.255.255.255
ifconfig lo:0 $vip broadcast 192.168.226.255 netmask 255.255.255.255 up

# 3. 添加一条去往 VIP 的主机路由,确保响应流量走 lo:0 返回
route add -host $vip lo:0

# 4. 配置 ARP 抑制参数(防止 RS 抢 LVS 的 VIP),分两行写入
echo "1" > /proc/sys/net/ipv4/conf/lo/arp_ignore
echo "2" > /proc/sys/net/ipv4/conf/lo/arp_announce
echo "1" > /proc/sys/net/ipv4/conf/all/arp_ignore
echo "2" > /proc/sys/net/ipv4/conf/all/arp_announce

image-20260914181647186

image-20260914182007062


三、 从“外网/内网”角度的最终对比总结

作为客户端,从网络架构看,区别天壤之别:

对比维度 NAT 模式 DR 模式
客户端看到的源IP 永远是 VIP 永远是 VIP
LVS 在回包时的角色 绝对主力(必须经手回包并改源 IP) 隐身人(响应包直接由 RS 发给网关)
网络结构要求 LVS和 RS 可以不在同一网段 LVS和 RS 必须在同一网段(同一个二层网络)
外网到内网的流向 客户端->LVS->RS->LVS->客户端 客户端->LVS->RS->网关->客户端
谁负责流量瓶颈 LVS(压力大,因为要处理所有进出流量) RS 和网关(LVS只处理很小的请求包)
生产环境适用场景 适合并发量不太高的场景,或者内网服务器需要连接外网 互联网大厂主流,适合高并发、大流量的负载均衡

一句话帮您彻底记住:

  • NAT 模式:LVS是全包圆的苛刻包工头,所有活儿(进和出)都要经过它。

Keepalived+LVS+httpd实现网站负载均衡高可用

这套架构的目的是:让用户通过访问一个虚拟的IP(VIP),自动把请求分摊到两台真实服务器上;如果主调度器挂了,备用调度器会自动顶上,保证服务不中断。

# 设备详细:
server1(DS1):192.168.226.10
server2(DS2):192.168.226.20
server3(RS1):192.168.226.30
server4(RS2):192.168.226.40
VIP:192.168.226.100(单主模型)

#在server3和server4执行下面的脚本:
#!/bin/bash
yum install httpd net-tools -y
systemctl stop firewalld
setenforce 0
vip="192.168.226.100"
mask="255.255.255.255"
ifconfig lo:0 $vip broadcast $vip netmask $mask up
route add -host $vip lo:0
echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/lo/arp_announce
echo "1" >/proc/sys/net/ipv4/conf/all/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/all/arp_announce
echo "<h1>This is RS1</h1>" > /var/www/html/index.html # 修改不同的主页以便测试!
systemctl start httpd

# 在server1和server2上配置keepalived(包含LVS的配置)
[root@server2 ~]# vim /etc/keepalived/keepalived.conf   # 最好事先备份一下mv /etc/keepalived/keepalived.conf  /etc/keepalived/keepalived.conf.bak

! Configuration File for keepalived
global_defs {
    router_id server1 # 设置lvs的id,一个网络中应该唯一
}
vrrp_instance VI_1 {
    state MASTER # 指定Keepalived的角色
    interface ens160 # 网卡
    virtual_router_id 10 # 虚拟路由器ID,主备需要一样
    priority 150 # 优先级越大越优,backup路由器需要设置比这小!
    advert_int 1 # 检查间隔1s
    authentication {
    auth_type PASS
    auth_pass 1111
    }
    virtual_ipaddress {
    192.168.226.100 # 定义虚拟IP地址,可以定义多个
    }
}
# 定义虚拟主机,对外服务的IP和port
virtual_server 192.168.226.100 80 {
    delay_loop 6 # 设置健康检查时间,单位是秒
    lb_algo wrr # 负责调度算法
    lb_kind DR # LVS负载均衡机制
    persistence_timeout 0
    protocol TCP
# 指定RS主机IP和port
    real_server 192.168.226.30 80 {
    weight 2
# 定义TCP健康检查
    TCP_CHECK {
    connect_timeout 10
    nb_get_retry 3
    delay_before_retry 3
    connect_port 80
    }
}
real_server 192.168.226.40 80 {
    weight 1
    TCP_CHECK {
    connect_timeout 10
    nb_get_retry 3
    delay_before_retry 3
    connect_port 80
    }
    }
}

第一部分:全局定义 (global_defs)

nginx

global_defs {
    router_id server1 
}
  • 作用:定义这台Keepalived 节点的身份标识。
  • router_id server1:给这台机器起个名字。在同一个局域网的高可用集群中,每台机器的 router_id 必须唯一。
    • 注意:你在 server2 上操作,如果 server2 是备机,改成 router_id server2 以防混淆

第二部分:VRRP 实例配置 (vrrp_instance VI_1)

这是Keepalived 实现故障自动切换(高可用)的核心部分。

nginx

vrrp_instance VI_1 {
    state MASTER 
    interface ens160 
    virtual_router_id 10 
    priority 150 
    advert_int 1 
    authentication { ... }
    virtual_ipaddress { 192.168.226.100 }
}
  • state MASTER:定义这台机器的初始角色是主节点。另一台备机(server2)这里必须写 state BACKUP。
  • interface ens160:指定虚拟IP(VIP)绑定在哪块物理网卡上。我的网卡名是 ens160。
  • virtual_router_id 10:虚拟路由组 ID。主备两台机器这个数字必须完全一致(都是 10),它们才能互相识别为同一组。
  • priority 150:优先级。数字越大,越容易成为主节点(Master)。备机的优先级必须小于 150(比如设成 100)。当 Master 宕机不再发送心跳时,Backup 会根据优先级自动抢占成为新的 Master。
  • advert_int 1:心跳广播间隔,1 秒一次。Master 每隔 1 秒告诉 Backup “我还活着”。
  • authentication:主备通信的密码认证(密码是 1111),主备必须一致。
  • virtual_ipaddress { 192.168.226.100 }:最关键的配置。这就是对外提供服务的虚拟IP(VIP)。正常情况下,这个IP 绑定在 Master 机器上;Master 宕机后,这个IP 会瞬间“漂移”到 Backup 机器上。

第三部分:LVS 虚拟服务器与真实服务器配置 (virtual_server)

这部分是LVS 负载均衡 的核心。它告诉系统如何分发到达 VIP 的流量。

nginx

virtual_server 192.168.226.100 80 {
    delay_loop 6 
    lb_algo wrr 
    lb_kind DR 
    persistence_timeout 0
    protocol TCP
  • virtual_server 192.168.226.100 80:定义了一个虚拟服务,监听 VIP 的 80 端口。
  • delay_loop 6:健康检查间隔,每 6 秒检查一次后端服务器是否存活。
  • lb_algo wrr:加权轮询(Weighted Round Robin)算法。意思是根据服务器的性能分配请求,性能好的(权重高)多分点请求。
  • lb_kind DR:直接路由(Direct Routing)模式。这是 LVS性能最高的模式(请求发给 LVS,LVS 只改 MAC 地址丢给后端服务器,后端服务器直接把结果返回给客户端,不再经过LVS)。
  • persistence_timeout 0:会话保持时间。设为 0 表示不保持(同一个用户的请求可能分发给不同的后端服务器)。如果需要保持登录状态,可以设为 600(10分钟)。
  • protocol TCP:指定服务协议。

真实服务器定义 (real_server)

在 virtual_server 内部,定义了两台后端真实提供 Web 服务的机器:

nginx

    real_server 192.168.226.30 80 {
        weight 2
        TCP_CHECK {
            connect_timeout 10
            nb_get_retry 3
            delay_before_retry 3
            connect_port 80
        }
    }
    real_server 192.168.226.40 80 {
        weight 1
        TCP_CHECK { ... }
    }
  • real_server 192.168.226.30 80:第一台真实服务器的IP 和端口。
  • weight 2:权重为 2。
  • real_server 192.168.226.40 80:第二台真实服务器的 IP和端口。
  • weight 1:权重为 1。
  • 分配比例:因为权重是2:1,所以 LVS会把 2/3 的请求发给 .30,1/3 的请求发给 .40。
  • TCP_CHECK:健康检查机制。LVS会每隔 6 秒(delay_loop)去尝试连接这两台机器的 80 端口。如果连不上(超时 10 秒,重试 3 次),LVS 就会自动把这台挂掉的服务器从列表中剔除,不再往它上面发请求,直到它恢复正常。

核心警告:LVS-DR 模式的“隐藏要求”

配置里用了 lb_kind DR。配置完这个文件,结果发现访问 VIP 根本不通。因为 DR 模式必须在后端真实服务器(192.168.226.30 和 .40)上也做特殊配置:

  1. 抑制 ARP 广播:必须在后端服务器的网卡上关闭 ARP广播(修改内核参数 arp_ignore 和 arp_announce)。否则,客户端寻找 VIP(.100)时,后端服务器会回应,导致客户端直接连到了后端服务器,绕过了调度器(这就失去了负载均衡的意义)。
  2. 绑定 VIP 到本地回环口:后端服务器需要在 lo:0 上绑定 VIP 192.168.226.100,这样它才能接收目标地址是 VIP 的数据包。

总结与避坑指南

  1. 主备配置差异:在 server2 上配置时,必须修改:state BACKUP、priority 100(比 150 小)、router_id server2。其他的保持不变。
  2. 多台机器准备:
    • 调度器(LVS):192.168.226.20 和 192.168.226.10 (主备)
    • 真实服务器(RS):192.168.226.30 和 192.168.226.40(需要安装并启动 Nginx/Apache,且配置好 DR 模式)
    • 虚拟IP(VIP):192.168.226.100
  3. 启动与测试:配置完成后,重启Keepalived 服务(systemctl restart keepalived)。此时在调度器上执行 ip a,你应该能看到 ens160 上多出了一个 192.168.226.100 的IP。然后用浏览器访问 http://192.168.226.100,测试负载均衡和高可用是否生效。

实践操作

📁 .bak 文件作用标准总结

1. 核心定义 .bak 是英文 backup 的缩写,意为“备份”。在 Linux 系统中,它通常作为文件的后缀名(如 keepalived.conf.bak),用来标识这是一个配置文件的副本。

2. 标准操作规范(最佳实践)

  • 改前必做:修改任何系统或服务的关键配置文件(如 nginx.conf、keepalived.conf、/etc/fstab 等)之前,必须先备份。

  • mv file file.bak:原文件被移走,需要手动新建空文件。若忘记新建,服务会直接报错

    1. 日志与 VIP 生效(最关键的部分):
      • 看日志区域,有几行非常关键的提示:
        • Sending gratuitous ARP on ens160 for 192.168.226.100:Keepalived 正在向局域网发送广播(ARP),告诉所有设备:“虚拟IP 192.168.226.100 现在在我这台机器上!” 这样客户端访问 .100 时,流量才会导过来。
        • TCP connection to [192.168.226.40]:tcp:80 success 和 [192.168.226.30]:tcp:80 success:LVS 健康检查成功! 这说明你的后端真实服务器(RS: .30 和 .40)的 80 端口是通的,LVS 已经成功识别到了这两台服务器。
    2. IP 验证:
      • 最后执行 ip a,你可以看到 ens160 网卡上不仅有原本的 IP 192.168.226.10,还多出了 VIP 192.168.226.100。这证明主机已经成功接管了虚拟IP。

    总结:第一张图代表主节点已经完美上线,正在对外提供服务,并且成功接管了虚拟 IP

  • BACKUP节点
  1. 日志解读:
    • 备机的日志通常没有主机的“Sending gratuitous ARP”那么活跃,因为它不需要发广播宣告主权。它此时正在默默接收主机发来的心跳包(VRRP广播)。

总结:第二张图代表备节点已经处于待命状态,配置正确,随时准备在主机故障时接管业务。

image-20260917125315500

image-20260917125650826

image-20260917130328745

image-20260917130529161

image-20260917130647559


HAProxy介绍

HAProxy 是一款广泛使用的高性能开源软件,主要用于提供高可用性、负载均衡以及基于 TCP 和 HTTP 的应用代理。

解决lua环境

HAProxy 支持基于lua实现功能扩展,lua是一种小巧的脚本语言,于1993年由巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组开发,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

Lua 官网:www.lua.org

  • Lua 应用场景

    • 游戏开发
    • 独立应用脚本
    • Web 应用脚本
    • 扩展和数据库插件,如MySQL Proxy
    • 安全系统,如入侵检测系统
  • 安装基础命令及编译依赖环境

[root@localhost ~]# yum -y install gcc readline-devel
[root@localhost ~]# wget http://www.lua.org/ftp/lua-5.3.5.tar.gz
[root@localhost ~]# tar xvf  lua-5.3.5.tar.gz -C /usr/local/src
[root@localhost ~]# cd /usr/local/src/lua-5.3.5
[root@localhost lua-5.3.5]# make linux test

编译安装HAProxy

  • HAProxy2.0以上版本编译参数
[root@localhost ~]# yum -y install gcc openssl-devel pcre-devel systemd-devel
[root@localhost ~]# wget http://www.haproxy.org/download/2.1/src/haproxy-2.1.3.tar.gz
[root@localhost ~]# tar xvf haproxy-2.1.3.tar.gz  -C /usr/local/src
[root@localhost ~]# cd /usr/local/src/haproxy-2.1.3/
[root@localhost haproxy-2.1.3]# cat README
  • 参考INSTALL文件进行编译安装
[root@localhost haproxy-2.1.3]# make -j 2 ARCH=x86_64 TARGET=linux-glibc  
USE_PCRE=1 
USE_OPENSSL=1 
USE_ZLIB=1  
USE_SYSTEMD=1  
USE_LUA=1 
LUA_INC=/usr/local/src/lua-5.3.5/src/  
LUA_LIB=/usr/local/src/lua-5.3.5/src/
[root@localhost haproxy-2.1.3]# make install PREFIX=/apps/haproxy
[root@localhost haproxy-2.1.3]# ln -s /apps/haproxy/sbin/haproxy /usr/sbin/
  • 查看生成的文件
[root@localhost ~]# tree -C /apps/haproxy/
/apps/haproxy/
├── doc
│   └── haproxy
│       ├── 51Degrees-device-detection.txt
│       ├── architecture.txt
│       ├── close-options.txt
│       ├── configuration.txt
│       ├── cookie-options.txt
│       ├── DeviceAtlas-device-detection.txt
│       ├── intro.txt
│       ├── linux-syn-cookies.txt
│       ├── lua.txt
│       ├── management.txt
│       ├── netscaler-client-ip-insertion-protocol.txt
│       ├── network-namespaces.txt
│       ├── peers.txt
│       ├── peers-v2.0.txt
│       ├── proxy-protocol.txt
│       ├── regression-testing.txt
│       ├── seamless_reload.txt
│       ├── SOCKS4.protocol.txt
│       ├── SPOE.txt
│       └── WURFL-device-detection.txt
├── sbin
│   └── haproxy
└── share
    └── man
        └── man1
            └── haproxy.1

6 directories, 22 files

验证HAProxy版本

[root@localhost ~]# which haproxy
/usr/sbin/haproxy
[root@localhost ~]# haproxy -v
HA-Proxy version 2.1.3 2020/02/12 - https://haproxy.org/
Status: stable branch - will stop receiving fixes around Q1 2021.
Known bugs: http://www.haproxy.org/bugs/bugs-2.1.3.html

[root@localhost ~]# haproxy -vv
HA-Proxy version 2.1.3 2020/02/12 - https://haproxy.org/
Status: stable branch - will stop receiving fixes around Q1 2021.
Known bugs: http://www.haproxy.org/bugs/bugs-2.1.3.html
Build options :
  TARGET  = linux-glibc
  CPU     = generic
  CC      = gcc
  CFLAGS  = -m64 -march=x86-64 -O2 -g -fno-strict-aliasing -Wdeclaration-after-statement -fwrapv -Wno-unused-label -Wno-sign-compare -Wno-unused-parameter -Wno-old-style-declaration -Wno-ignored-qualifiers -Wno-clobbered -Wno-missing-field-initializers -Wtype-limits
  OPTIONS = USE_PCRE=1 USE_OPENSSL=1 USE_LUA=1 USE_ZLIB=1 USE_SYSTEMD=1

Feature list : +EPOLL -KQUEUE -MY_EPOLL -MY_SPLICE +NETFILTER +PCRE -PCRE_JIT -PCRE2 -PCRE2_JIT +POLL -PRIVATE_CACHE +THREAD -PTHREAD_PSHARED -REGPARM -STATIC_PCRE -STATIC_PCRE2 +TPROXY +LINUX_TPROXY +LINUX_SPLICE +LIBCRYPT +CRYPT_H -VSYSCALL +GETADDRINFO +OPENSSL +LUA +FUTEX +ACCEPT4 -MY_ACCEPT4 +ZLIB -SLZ +CPU_AFFINITY +TFO +NS +DL +RT -DEVICEATLAS -51DEGREES -WURFL +SYSTEMD -OBSOLETE_LINKER +PRCTL +THREAD_DUMP -EVPORTS

Default settings :
  bufsize = 16384, maxrewrite = 1024, maxpollevents = 200

Built with multi-threading support (MAX_THREADS=64, default=4).
Built with OpenSSL version : OpenSSL 1.0.2k-fips  26 Jan 2017
Running on OpenSSL version : OpenSSL 1.0.2k-fips  26 Jan 2017
OpenSSL library supports TLS extensions : yes
OpenSSL library supports SNI : yes
OpenSSL library supports : SSLv3 TLSv1.0 TLSv1.1 TLSv1.2
Built with Lua version : Lua 5.3.5
Built with network namespace support.
Built with transparent proxy support using: IP_TRANSPARENT IPV6_TRANSPARENT IP_FREEBIND
Built with PCRE version : 8.32 2012-11-30
Running on PCRE version : 8.32 2012-11-30
PCRE library supports JIT : no (USE_PCRE_JIT not set)
Encrypted password support via crypt(3): yes
Built with zlib version : 1.2.7
Running on zlib version : 1.2.7
Compression algorithms supported : identity("identity"), deflate("deflate"), raw-deflate("deflate"), gzip("gzip")

Available polling systems :
      epoll : pref=300,  test result OK
       poll : pref=200,  test result OK
     select : pref=150,  test result OK
Total: 3 (3 usable), will use epoll.

Available multiplexer protocols :
(protocols marked as <default> cannot be specified using 'proto' keyword)
              h2 : mode=HTTP       side=FE|BE     mux=H2
            fcgi : mode=HTTP       side=BE        mux=FCGI
       <default> : mode=HTTP       side=FE|BE     mux=H1
       <default> : mode=TCP        side=FE|BE     mux=PASS

Available services : none

Available filters :
        [SPOE] spoe
        [CACHE] cache
        [FCGI] fcgi-app
        [TRACE] trace
        [COMP] compression

HAProxy启动脚本

[root@localhost ~]# vim /usr/lib/systemd/system/haproxy.service
[Unit]
Description=HAProxy Load Balancer
After=syslog.target network.target

[Service]
ExecStartPre=/usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg  -c -q
ExecStart=/usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /var/lib/haproxy/haproxy.pid
ExecReload=/bin/kill -USR2 $MAINPID

[Install]
WantedBy=multi-user.target
  • 默认缺少配置文件,无法启动
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl start haproxy
Job for haproxy.service failed because the control process exited with error code. See "systemctl status haproxy.service" and "journalctl -xe" for details.


[root@localhost ~]# tail /var/log/messages
Jul 13 10:39:33 localhost systemd: Starting HAProxy Load Balancer...
Jul 13 10:39:33 localhost haproxy: [ALERT] 193/103933 (8156) : Cannot open configuration file/directory /etc/haproxy/haproxy.cfg : No such file or directory
Jul 13 10:39:33 localhost systemd: haproxy.service: control process exited, code=exited status=1
Jul 13 10:39:33 localhost systemd: Failed to start HAProxy Load Balancer.
Jul 13 10:39:33 localhost systemd: Unit haproxy.service entered failed state.
Jul 13 10:39:33 localhost systemd: haproxy.service failed.

配置文件

  • 查看配置文件范例
[root@localhost ~]# tree -C /usr/local/src/haproxy-2.1.3/examples/
/usr/local/src/haproxy-2.1.3/examples/
├── acl-content-sw.cfg
├── content-sw-sample.cfg
├── errorfiles
│   ├── 400.http
│   ├── 403.http
│   ├── 408.http
│   ├── 500.http
│   ├── 502.http
│   ├── 503.http
│   ├── 504.http
│   └── README
├── haproxy.init
├── option-http_proxy.cfg
├── socks4.cfg
├── transparent_proxy.cfg
└── wurfl-example.cfg

1 directory, 15 files
  • 创建自定义的配置文件
[root@localhost ~]# mkdir  /etc/haproxy
[root@localhost ~]# vim /etc/haproxy/haproxy.cfg
global
    maxconn 100000
    chroot /apps/haproxy
    stats socket /var/lib/haproxy/haproxy.sock mode 600 level admin
    #uid 99
    #gid 99
    user  haproxy
    group haproxy
    daemon
    #nbproc 4
    #cpu-map 1 0
    #cpu-map 2 1
    #cpu-map 3 2
    #cpu-map 4 3
    pidfile /var/lib/haproxy/haproxy.pid
    log 127.0.0.1 local2 info

defaults
    option http-keep-alive
    option  forwardfor
    maxconn 100000
    mode http
    timeout connect 300000ms
    timeout client  300000ms
    timeout server  300000ms

listen stats
    mode http
    bind 0.0.0.0:9999
    stats enable
    log global
    stats uri     /haproxy-status
    stats auth    admin:123456

listen  web_port
    bind 192.168.226.10:80
    mode http
    log global
    server web1  127.0.0.1:8080  check inter 3000 fall 2 rise 5

启动haproxy

[root@localhost ~]# mkdir  /var/lib/haproxy
[root@localhost ~]# useradd -r -s /sbin/nologin -d /var/lib/haproxy haproxy
[root@localhost ~]# systemctl  enable --now haproxy

编译安装HAProxy

[root@server1 haproxy-2.1.3]# make -j 2 ARCH=x86_64 TARGET=linux-glibc  USE_PCRE=1  USE_SYSTEMD=1  USE_LUA=1 LUA_INC=/usr/local/src/lua-5.3.5/src/  LUA_LIB=/usr/local/src/lua-5.3.5/src/

HAProxy中的bind用法
1. 监听所有 IP 的指定端口
frontend http_front
    bind *:80         # 监听所有 IPv4 地址的 80 端口
    bind :::80        # 监听所有 IPv6 地址的 80 端口(双栈支持)
    bind *:443 ssl    # 监听 443 端口并启用 SSL(需配合证书配置)
2. 监听特定 IP 地址
frontend web_front
    bind 192.168.1.100:80   # 仅通过 192.168.1.100 接收 80 端口请求
    bind 203.0.113.5:443    # 仅通过公网 IP 203.0.113.5 接收 HTTPS 请求
3. 监听端口范围
    frontend app_front
    bind *:8000-8010    # 监听 8000 到 8010 之间的所有端口
4. 配置 SSL/TLS 加密(HTTPS)
frontend https_front
    bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
    # 说明:
    # - ssl:启用 SSL 加密
    # - crt:指定 PEM 格式的证书文件(包含证书和私钥,需合并为一个文件)
5. 限制协议类型
frontend tcp_front
    bind *:8080 proto tcp  # 仅接收 TCP 协议(默认)
    mode tcp               # 配合 mode 指令使用

frontend udp_front
    bind *:53 proto udp    # 接收 UDP 协议(如 DNS 服务)
    mode udp
6. 配置监听队列与连接限制
frontend high_traffic_front
    bind *:80 backlog 10000 maxconn 5000
    # 监听队列大小 10000,最大并发连接 5000
7. 绑定到特定网络接口
frontend lan_front
    bind eth0:80   # 仅通过局域网网卡 eth0 接收 80 端口请求
8. 结合 listen 块使用
listen web_service
    bind *:80
    mode http
    server web1 192.168.1.101:80 check
    server web2 192.168.1.102:80 check


HAProxy的统计报告页面(Stats Page)

image-20260922103508734

简单来说,它的作用和意义如下:

  • 是什么:它是 HAProxy 自带的一个内置可视化网页,用来展示当前负载均衡器的实时运行数据和健康状态。
  • 干啥的:用来监控和排错。通过它,你可以直观地看到有哪些前端(Frontend)在接收流量、后端(Backend)有哪些服务器、这些服务器当前是正常的(绿色 UP)还是故障的(红色 DOWN)、连接数有多少、流量有多大等。

HAProxy的配置文件内容

文件路径为 /etc/haproxy/haproxy.cfg

HAProxy是一款广泛使用的高性能开源软件,主要用于提供高可用性、负载均衡以及基于 TCP 和 HTTP 的应用代理。

1. global 段(全局配置)

这一部分主要设置 HAProxy 进程级别的参数,影响整个服务的运行。

  • maxconn 100000: 设定每个 HAProxy 进程可接受的最大并发连接数为 100,000。
  • chroot /apps/haproxy: 将 HAProxy 的工作目录切换到 /apps/haproxy。这是一种安全措施(沙盒机制),即使 HAProxy 被攻破,攻击者也只能访问该目录下的文件。
  • stats socket /var/lib/haproxy/haproxy.sock mode 600 level admin: 定义一个 Unix Socket 用于管理。权限设为 600(仅所有者读写),权限级别为 admin(允许执行所有管理命令)。
  • #uid 99 和 #gid 99:这两行被注释掉了(前面有 #)。原本用于指定运行 HAProxy 的用户 ID 和组 ID。
  • user haproxy 和 group haproxy: 指定 HAProxy 进程运行的用户和组为 haproxy。出于安全考虑,不应该使用 root 用户运行。
  • daemon: 让 HAProxy 以守护进程(后台)模式运行。
  • #nbproc 4 及随后的 #cpu-map ...: 这些都被注释掉了。原本用于设置启动 4 个 HAProxy 进程,并将它们绑定到不同的 CPU 核心上(多核优化)。
  • pidfile /var/lib/haproxy/haproxy.pid: 指定存放 HAProxy 进程 ID (PID) 的文件路径。
  • log 127.0.0.1 local2 info: 配置日志输出。将日志发送到本地回环地址(127.0.0.1)的 local2 设施(Facility),日志级别为 info。

2. defaults 段(默认配置)

这一部分为后续的 listen、frontend 和 backend 段提供默认参数。

  • option http-keep-alive: 开启 HTTP 长连接(Keep-Alive),允许在同一个 TCP 连接上发送多个 HTTP 请求,减少延迟。
  • option forwardfor: 如果后端服务器需要记录客户端的真实 IP 地址,HAProxy 会在 HTTP 请求头中插入 X-Forwarded-For 字段。
  • maxconn 100000: 设定每个前端(frontend)或监听器(listen)的最大并发连接数。
  • mode http: 设置默认的工作模式为 HTTP(七层代理)。其他模式还有 tcp(四层代理)。
  • timeout connect 300000ms: 连接后端服务器的超时时间设为 300,000 毫秒(即 5 分钟)。
  • timeout client 300000ms: 客户端连接的空闲超时时间设为 300,000 毫秒。
  • timeout server 300000ms: 后端服务器连接的空闲超时时间设为 300,000 毫秒。

3. listen stats 段(统计监控配置)

这一部分定义了一个用于查看 HAProxy 运行状态和统计信息的 Web 页面。

  • mode http: 该监听器工作在 HTTP 模式。
  • bind 0.0.0.0:9999: 监听所有网卡的 9999 端口。
  • stats enable: 启用统计报告功能。
  • log global: 使用全局配置中定义的日志设置。
  • stats uri /haproxy-status: 设置访问统计页面的 URL 路径为 /haproxy-status(例如:http://<IP>:9999/haproxy-status)。
  • stats auth admin:123456: 设置访问统计页面的认证信息,用户名为 admin,密码为 123456。

4. listen web_port 段(业务负载均衡配置)

这一部分定义了一个具体的负载均衡监听器,用于转发 Web 流量。

  • bind 192.168.226.10:80: 监听本机的 192.168.226.10 地址的 80 端口(HTTP 标准端口)。
  • mode http: 工作在 HTTP 模式。
  • log global: 使用全局日志设置。
  • server web1 127.0.0.1:8080 check inter 3000 fall 2 rise 5: 定义后端服务器。
    • 名称:web1
    • 地址:127.0.0.1:8080(本地回环地址的 8080 端口)。
    • check: 开启健康检查。
    • inter 3000: 每 3000 毫秒(3秒)检查一次。
    • fall 2: 如果连续 2 次检查失败,则认为该服务器不可用(下线)。
    • rise 5: 如果连续 5 次检查成功,则认为该服务器恢复正常(上线)。

总结

这个配置文件的逻辑是:

  1. 全局:设置基本的安全、日志和性能参数。
  2. 默认:设定通用的 HTTP 超时和代理选项。
  3. Stats:在 9999 端口提供一个带密码保护的监控页面。
    1. 业务:在 192.168.226.10 的 80 端口对外提供服务,将流量转发给本地 8080 端口上的一个 Web 服务(web1),并带有健康检查机制。

日志配置

HAProxy配置

# 在global配置项定义
log 127.0.0.1  local{1-7} info #基于syslog记录日志到指定设备,级别有(err、warning、info、debug)

listen  web_port
  bind 127.0.0.1:80
  mode http
  log global                                #开启当前web_port的日志功能,默认不记录日志
  server web1  127.0.0.1:8080  check inter 3000 fall 2 rise 5

# systemctl  restart haproxy

Rsyslog配置

[root@server1 ~]# vim /etc/rsyslog.conf
$ModLoad imudp
$UDPServerRun 514

$ModLoad imtcp
$InputTCPServerRun 514
..........
local3.*    /var/log/haproxy.log

[root@server1 ~]# systemctl restart rsyslog.service
[root@server1 ~]# vim /etc/haproxy/haproxy.cfg
global
    log 127.0.0.1 local3 info

listen web_port_81
    log global
[root@server1 ~]# systemctl restart haproxy.service
[root@server1 ~]# tail -f /var/log/haproxy.log

将 HAProxy 的日志通过 Rsyslog 记录到本地文件中。简单来说:HAProxy配置中的 local3 是“发送方”的标签,而Rsyslog 中的两行是“接收方”的规则。 它们必须配合使用才能让日志正常落盘。这台服务器同时通过 UDP 和TCP 协议在 514 端口接收外部发来的日志

1. HAProxy 配置文件中的 log 127.0.0.1 local3 info

在 global 段中,你修改了日志配置:

haproxy

log 127.0.0.1 local3 info
  • 127.0.0.1: HAProxy本身不直接将日志写入硬盘文件(为了提高性能和避免 I/O 阻塞),而是将日志发送给系统的日志服务(Rsyslog)。这里指定发送到本机的Rsyslog服务。
  • local3: 这是 Syslog 的设施(Facility)。Linux 系统将日志来源分类为不同的“设施”,如 auth(认证)、cron(定时任务)、mail(邮件)等。local0 到 local7 是预留给用户自定义程序使用的。这里 HAProxy 选择了 local3 作为自己的专属标签。
  • info: 这是日志级别(Level)。表示记录 info 级别及更高级别(如 warning, error 等)的日志。
  • 意义:告诉HAProxy:“把你产生的日志,打上 local3 的标签,发送给本地的 Rsyslog 服务

2. Rsyslog 配置文件中的两行

在 /etc/rsyslog.conf 文件的 #### RULES #### 部分下方(大约第 68-69 行),你增加了:

rsyslog

local3.*                                                /var/log/haproxy.log
local3.*                                                ~
  • local3.*: 这里的 * 代表所有级别(包括 info, warning, error 等)。意思是匹配所有带有 local3 标签的日志消息。

  • /var/log/haproxy.log: 动作指令。意思是:“将所有匹配到的日志,写入到 /var/log/haproxy.log 这个文件中”。

  • local3.* ~: 这里的波浪号 ~ 是 Rsyslog 中的一个特殊动作,代表 丢弃(Discard)。

  • 意义:

    • 第一行定义了日志的存储位置。
    • 第二行非常关键:因为 Rsyslog 的规则是从上往下匹配的,如果不加 ~,这些 local3 的日志不仅会写入 /var/log/haproxy.log,还会继续向下匹配,最终被写入系统的 /var/log/messages 中。加上 ~ 就是为了防止日志重复记录,告诉 Rsyslog:“把这些 local3 的日志存到指定文件后,就不要再往下传递处理了。”

3. ModLoad imudp (加载 UDP 模块)

  • ModLoad: 这是 Rsyslog 的指令,意思是“加载模块”(Module Load)。
  • imudp: 代表 Input Module UDP(UDP 输入模块)。
  • 意义:Rsyslog 默认情况下只接收本机系统产生的日志(通过 Unix Socket 或本地机制)。==如果你想让它接收通过网络传过来的日志(比如另一台服务器发来的),你必须加载相应的网络接收模块==。这行代码就是告诉Rsyslog:“准备好接收UDP 协议的网络日志”。

4. UDPServerRun 514 (在 514 端口运行UDP 服务)

  • UDPServerRun: 这是配合 imudp 模块使用的指令,意思是“启动UDP 服务器并监听”。
  • 514: 这是 Syslog 协议的标准端口号。
  • 意义: 告诉 Rsyslog:“在 514 端口上监听,等待其他设备发送 UDP 日志过来”。

因为写的是 127.0.0.1(本机),所以 HAProxy 是通过本地内部机制把日志交给Rsyslog 的,并不需要走网络UDP。

image-20260922121400037

总结

这是一个典型的 “谁产生,谁打标;谁接收,谁分流” 的流程:

  1. HAProxy产生日志,并贴上 local3 的标签发送给 Rsyslog。
  2. Rsyslog 接收日志,看到标签是 local3。
  3. 根据 Rsyslog的规则,Rsyslog 把带有 local3 的日志提取出来,写入到 /var/log/haproxy.log 文件中,并阻止它们混入系统的常规日志里。

image-20260922141839181

image-20260922142542334

因为 ModLoad 和 UDPServerRun 是两个完全不同的指令,它们之间必须用换行(或者至少是空格)分隔开,否则系统会把它们当成一个整体,导致识别失败。

image-20260922143115710


frontend+backend配置实例

image-20260922144001472

haproxy配置文件

[root@haproxy ~]# vim /etc/haproxy/haproxy.cfg
global
    maxconn 100000
    chroot /apps/haproxy
    stats socket /var/lib/haproxy/haproxy.sock mode 600 level admin
    #uid 99
    #gid 99
    user  haproxy
    group haproxy
    daemon
    #nbproc 4
    #cpu-map 1 0
    #cpu-map 2 1
    #cpu-map 3 2
    #cpu-map 4 3
    pidfile /var/lib/haproxy/haproxy.pid
    log 127.0.0.1 local3 info

defaults
    option http-keep-alive
    option  forwardfor
    maxconn 100000
    mode http
    timeout connect 300000ms
    timeout client  300000ms
    timeout server  300000ms

listen stats
    mode http
    bind 0.0.0.0:9999
    stats enable
    log global
    stats uri     /haproxy-status
    stats auth    admin:123456

frontend test-http
    bind *:80
    mode tcp
    use_backend test-http-nodes

backend test-http-nodes
    mode tcp
    default-server inter 1000 weight 6
    server web1 192.168.226.20:80 check inter 3000 fall 3 rise 5 weight 2 addr 192.168.226.20 port 3306
    server web2 192.168.226.30:80 check inter 3000 fall 3 rise 5

[root@haproxy ~]# systemctl restart haproxy

image-20260922154701166

一、 Frontend(前端模块):负责“接客”

代码位置: 第 41 – 44 行

haproxy

frontend test-http
    bind *:80
    mode tcp
    use_backend test-http-nodes

前端模块的作用是定义 HAProxy 对外的入口,即监听哪个端口、接收什么流量,并把流量转发给谁。

  • frontend test-http:
    • 定义了一个名为 test-http 的前端。名字是自定义的,方便管理。
  • bind *:80:
    • 让 HAProxy 监听本机所有网卡(*)的 80 端口。任何发往这台机器 80 端口的请求都会被这个前端捕获。
  • mode tcp:
    • 重点注意:这里设置的是 TCP 模式(四层代理)。
    • 这意味着 HAProxy 只是单纯地转发 TCP 数据包,不会去解析 HTTP 协议的内容(比如它看不懂 HTTP 头部,不知道 URL 是什么,也看不到 Cookie)。它只认 IP 和端口。
  • use_backend test-http-nodes:
    • 这是核心动作指令:告诉前端,“所有发到我这里的流量,请全部转交给名为 test-http-nodes 的后端去处理。”
    • (注:因为这里是全局转发,没有写复杂的 ACL规则。如果是 HTTP 模式,这里通常配合 acl 规则实现根据域名或路径分发。)

二、 Backend(后端模块):负责“干活”

代码位置: 第 47 – 52 行

haproxy

backend test-http-nodes
    mode tcp
    default-server inter 1000 weight 6
    server web1 192.168.226.20:80 check inter 3000 fall 3 rise 5 weight 2 addr 192.168.226.20 port 3306
    server web2 192.168.226.30:80 check inter 3000 fall 3 rise 5

后端模块的作用是定义真实的后端服务器集群,以及如何对它们进行负载均衡和健康检查。

  • backend test-http-nodes:

    • 定义了一个名为 test-http-nodes 的后端。这个名字必须和前面前端里 use_backend 指定的名字完全一致,这样才能对接上。
  • mode tcp:

    • 同样设置为 TCP 模式,必须和前端的模式保持一致(或兼容)。
  • default-server inter 1000 weight 6:

    • 这是给下面所有 server 设置的默认参数。
    • 意思是:如果在下面的 server 行里没有单独写健康检查间隔(inter),默认就是 1000 毫秒检查一次;如果没有单独写权重(weight),默认权重是 6。
  • server web1 ...(第一台后端服务器):

    • web1: 服务器名称。
    • 192.168.226.20:80: 声明服务器地址是 192.168.226.20,端口是 80。这是 HAProxy 尝试连接的初始目标。
    • check: 开启对该服务器的健康检查。
    • inter 3000: 覆盖了默认值。实际检查间隔是 3000 毫秒(3秒)。
    • fall 3: 连续失败 3 次,判定该服务器宕机(剔除)。
    • rise 5: 连续成功 5 次,判定该服务器恢复(重新加入集群)。
    • weight 2: 覆盖了默认值。该服务器的权重是 2(数值越小,分配到的流量比例越小)
    • addr 192.168.226.20 port 3306: 这是这行代码最大的疑点/坑点!
    • 在 HAProxy 中,如果在 server 名字后面写了 IP 和端口,再使用 addr 和 port 参数,HAProxy 会以后面的 addr 和 port 为准!
    • 也就是说,HAProxy 虽然看到你写了 :80,但最终它会去连接 192.168.226.20 的 3306 端口(通常是MySQL数据库端口),而不是 80 端口。
  • server web2 ...(第二台后端服务器):

    • 地址是 192.168.226.30:80。
    • 健康检查参数和 web1 一样(除了权重未指定,会继承默认的 weight 6)。

整体工作流:

  1. 客户端访问 192.168.226.10:80。
  2. 被 frontend test-http 捕获。
  3. 前端把请求转给 backend test-http-nodes。
  4. 后端中有两台服务器 web1 和 web2。HAProxy会根据权重和健康状态,在四层(TCP)将请求分发给这两台服务器。

image-20260922185955609

核心结论:*:80 的绑定逻辑

可以用一句话精准概括:

当 HAProxy 配置了 bind *:80 时,只要目标 IP 是这台 HAProxy 服务器自身拥有的(配置在网卡上的),无论通过哪个 IP 访问其 80 端口,HAProxy 都会接管并处理该请求。

详细拆解说明:

1. 什么是“HAProxy 上有的 IP”? 就是指在这台 HAProxy 机器的操作系统里(通过 ip addr 或 ifconfig 命令能看到的)实际配置的 IP 地址。通常包括:

  • 对外业务 IP:例如 192.168.226.10(外部浏览器和客户端用它来访问)。
  • 本机回环 IP:例如 127.0.0.1(在本机内部执行 curl localhost 时使用)。
  • 如果机器上还有其他虚拟网卡(如 Docker 网桥 IP 172.17.0.1),也包含在内。

2. * 号的作用范围: * 是通配符,代表“这台机器上的所有本地 IP”。HAProxy 会在所有这些 IP 的 80 端口上竖起耳朵监听。

3. 界限非常明确(仅限于此): 如果外部访问的 IP不是这台 HAProxy机器上配置的IP(比如您去访问 192.168.226.99:80,而该 IP 并不在HAProxy 的网卡上),这个网络请求根本不会到达HAProxy机器,自然也就无法被 *:80 接管,会直接超时或报错。

4. 端口限定(只限 80): *:80 仅仅接管 80 端口,绝对不会暴露机器上的其他端口(如 SSH 的 22,数据库的 3306)。

一句话实战总结:

在您的实验环境中,写成 bind *:80,就相当于开启了“全频道接收”模式。无论是别人从局域网通过 192.168.226.10:80 找过来,还是您自己在机器里通过 127.0.0.1:80 找过来,HAProxy 都会一视同仁

image-20260922192350668

image-20260922192407153

image-20260922193550894


listen替代frontend+backend

使用listen替换上面的frontend和backend的配置方式,可以简化设置,通常只用于TCP协议的应用
listen  web_port_81
    bind *:81
    mode http
    option  forwardfor
    # 这个选项的作用是启用或禁用将原始客户端的IP地址添加到HTTP请求的X-Forwarded-For头部中。
    server web1   192.168.226.20:80   check inter 3000 fall 3 rise 5
    server web2   192.168.226.30:80   check inter 3000 fall 3 rise 5

[root@haproxy ~]# systemctl restart haproxy

image-20260922200712151

image-20260922201135267

image-20260922201530982

HAProxy调度算法

HAProxy通过固定参数balance指明对后端服务器的调度算法,该参数可以配置在listen或backend选项中。

HAProxy的调度算法分为静态和动态调度算法,但是有些算法可以根据参数在静态和动态算法中相互转换。

官方文档:http://cbonte.github.io/haproxy-dconv/2.1/configuration.html#4-balance

静态算法

静态算法:按照事先定义好的规则轮询公平调度,不关心后端服务器的当前负载、链接数和响应速度等,且无法实时修改权重,只能靠重启HAPr

动态算法

动态算法:基于后端服务器状态进行调度适当调整,优先调度至当前负载较低的服务器,且权重可以在haproxy运行时动态调整无需重启。

==算法很多不列举了,需要自己根据情况选择==

listen  web_host
  bind 192.168.226.10:80
  mode http
  log global
  balance static-rr        #就是在 balance 后面跟上算法名称
  server web1  192.168.226.20:80 weight 1 check inter 3000 fall 2 rise 5
  server web2  192.168.226.30:80 weight 2 check inter 3000 fall 2 rise 5