笃行
首页
个人 & 心法
互联网/硬件后台
游戏基础架构
UE 引擎
游戏业务
AI / 大模型
数据结构与算法
机器学习数学
通用基础
GitHub
首页
个人 & 心法
互联网/硬件后台
游戏基础架构
UE 引擎
游戏业务
AI / 大模型
数据结构与算法
机器学习数学
通用基础
GitHub
  • 互联网 / 智能硬件后台(3 年)

    • 互联网 / 智能硬件后台
    • php-fpm + Nginx 多进程异步 I/O
    • IoT 私有协议设计与 MQTT
    • 前端工程化与 Web 游戏引擎
    • Elixir 与函数式 / BEAM
    • DNS 攻防与隧道
    • DNS 清洗拦截与 CoreDNS
    • LVS 与 epoll
    • TCP/HTTP 滑动窗口
    • TCP 网络编程

LVS 与 epoll

四层负载 LVS(内核 IPVS)与用户态事件模型 epoll,是高并发接入层的两块基石。本文讲清 LVS 三种转发模式(尤其 DR 为什么最快)、调度算法、Keepalived 高可用,以及 I/O 多路复用三代演进(select → poll → epoll)为什么 epoll 碾压前两代、LT/ET 与惊群的取舍,再进一步到 io_uring 的完成通知模型,最后说明 LVS 与七层 Nginx 如何分层配合。

一句话结论

LVS 四层只改 MAC 廉价均分连接,epoll 注册等待分离只返就绪 fd,二者撑起接入层高并发。

场景问题

一个千万级并发的接入层要同时解决两件事:

  1. 横向扩展:单机扛不住,需要把流量均匀分给一组后端(Real Server),且这一分发本身不能成为瓶颈或单点。
  2. 单机高并发:每台机器要用尽可能少的线程管住尽可能多的连接(C10K → C10M),不能"一连接一线程"。

前者是 LVS(负载均衡)要解的,后者是 epoll(I/O 多路复用)要解的。二者常配合出现:LVS 在四层把连接均分给多台 Nginx/后端,每台后端内部用 epoll 单线程管住海量连接。

打个比方(epoll 为何碾压 select/poll):select/poll 像个没登记本的门房——每次有人问"我的快递到了吗",他都得挨个把整栋楼所有房间的门敲一遍确认(每次遍历全部 fd),住户一多(几万连接)就活活累死,还是 O(n)。epoll 是聪明门房:进楼时先在登记本上把每个人登记一次(epoll_ctl 注册),之后谁的快递到了、谁的门铃响了,门房只把"有响动的那几间"名单直接递给你(epoll_wait 只返就绪 fd)——你按名单去取即可,跟总人数无关,近似 O(1)。类比失效边界:这个"只报有响动的"在 ET(边缘触发)模式下更省,但也更容易踩坑——门房只在"从没货变成有货"那一瞬间按一次铃,你要是没趁这次把快递一口气全取空(没循环读到 EAGAIN),剩下的它可不会再提醒你,数据就这么漏在那儿了。所以 ET 必须配非阻塞 fd + 循环读到 EAGAIN 为止;图省事可以用 LT(水平触发),只要还有货它就一直按铃。

实现方案

LVS 三种转发模式

LVS 工作在内核的 IPVS 模块,只做四层(IP + 端口)转发,不解析应用层。三种模式:

模式数据路径改写性能约束
NAT请求、响应都过 Director改目的/源 IP(DNAT/SNAT)最低(响应也压 Director)RS 网关须指向 Director
DR(直接路由)请求过 Director,响应直连客户端只改目的 MAC最高RS 与 Director 同二层网段,RS 的 lo 绑 VIP
TUN(IP 隧道)请求过 Director(IPIP 封装),响应直连客户端IPIP 封装高RS 需支持隧道,可跨网段

DR 为什么性能最高:

  • Director 只处理入向请求包,且只改二层 MAC 地址(把目的 MAC 改成选中的 RS),不改 IP、不做连接跟踪级重写。
  • 响应包由 RS 直接发回客户端(源 IP 仍是 VIP),完全不经过 Director。而真实流量绝大部分是响应(下行远大于上行),DR 让 Director 只承担了最小的那部分流量,因此吞吐最高。
  • 代价:RS 必须在 lo 上绑 VIP 并抑制 ARP(arp_ignore=1、arp_announce=2),保证只有 Director 应答 VIP 的 ARP。
# Director 上配置 DR 模式(ipvsadm)
ipvsadm -A -t 10.0.0.1:80 -s wlc          # 添加虚拟服务 VIP:80,调度算法 wlc
ipvsadm -a -t 10.0.0.1:80 -r 10.0.0.11 -g -w 3   # -g=DR(gateway), 权重3
ipvsadm -a -t 10.0.0.1:80 -r 10.0.0.12 -g -w 1
ipvsadm -Ln                                # 查看连接与规则

# 每台 Real Server 上(DR 必需)
ip addr add 10.0.0.1/32 dev lo             # lo 绑 VIP
sysctl -w net.ipv4.conf.all.arp_ignore=1   # 不响应非本接口目标的 ARP
sysctl -w net.ipv4.conf.all.arp_announce=2 # 通告时用真实接口地址

调度算法

  • rr(轮询)/ wrr(加权轮询):静态均分,忽略实际负载。
  • lc(最少连接)/ wlc(加权最少连接):按当前活动连接数分,能力强的机器权重高。wlc 是通用默认最优选择。
  • sh(源地址哈希):同源 IP 固定打到同一 RS,用于会话保持。

Keepalived + LVS 高可用(VRRP)

Director 是单点,用 Keepalived 跑 VRRP 做主备:主备通过组播/单播互发心跳竞选,VIP 漂在 MASTER 上;主挂了备接管 VIP(发免费 ARP 更新交换机 MAC 表)。Keepalived 同时对 RS 做健康检查,摘除故障后端。

select → poll → epoll 演进

三代 I/O 多路复用都在解同一个问题:一个线程同时等一批 fd,谁就绪了处理谁。区别在"怎么告诉内核你关心哪些 fd""内核怎么把就绪结果还给你"。

第一代 select(1983,BSD):

fd_set rset;
for (;;) {
    FD_ZERO(&rset);
    for (i = 0; i < n; i++) FD_SET(fds[i], &rset);   // 每次都要重建集合
    int r = select(maxfd + 1, &rset, NULL, NULL, &tv); // 传全量, 内核就地改写 rset
    for (i = 0; i <= maxfd; i++)
        if (FD_ISSET(fds[i], &rset)) handle(fds[i]);   // 返回后还要 O(n) 扫一遍找就绪
}
  • fd 集合是定长 bitmap,FD_SETSIZE 通常 1024 硬上限——想监听第 1025 个 fd 直接踩坏栈内存。
  • 传参是"值-结果"(value-result):内核把入参集合原地改写成就绪集合,所以每次调用都得重新 FD_SET 重建整个集合。
  • 三次 O(n):拷全量集合进内核、内核线性遍历全部 fd、返回后用户再 FD_ISSET 线性扫一遍找出就绪的。
  • 只有读/写/异常三个集合;timeout 在部分实现里也被改写。

第二代 poll(1986,System V):把 bitmap 换成数组,拆开输入输出。

struct pollfd pfds[N];                       // { fd; events(关心的); revents(内核填的) }
for (i = 0; i < N; i++){ pfds[i].fd = fds[i]; pfds[i].events = POLLIN; }
for (;;) {
    int r = poll(pfds, N, -1);               // 传全量数组
    for (i = 0; i < N; i++)
        if (pfds[i].revents & POLLIN) handle(pfds[i].fd);  // 仍要 O(n) 扫 revents
}
  • 去掉 FD_SETSIZE 硬上限(只受 RLIMIT_NOFILE 限制)。
  • events(输入,我关心啥)与 revents(输出,内核填就绪)分离,不用每次重建集合。
  • 但没解决根本问题:每次调用仍把整个 pollfd 数组全量拷进内核 + 内核全量遍历 + 用户全量扫 revents,依旧三处 O(n)。

第三代 epoll(2002,Linux 2.5.44):把"注册"和"等待"彻底分离——这是质变。

维度selectpollepoll
fd 上限FD_SETSIZE≈1024无(受 RLIMIT_NOFILE)无
每次调用拷贝全量 bitmap全量数组不拷(fd 常驻内核红黑树)
就绪查找O(n) 遍历O(n) 遍历O(1) 摘就绪链表
返回内容改写后的全集,需自己扫revents 全集,需自己扫只返就绪 fd
触发模式仅 LT仅 LTLT / ET 可选
数据结构定长 bitmap变长数组红黑树 + 就绪链表
复杂度随连接数线性恶化线性恶化与总数无关

一句话:select/poll 是"每次把整本花名册递进内核,让内核挨个点名";epoll 是"入职登记一次,之后内核只把有事的人名单主动塞给你"。 连接越多,差距越悬殊——10 万连接里通常只有几百个活跃,select/poll 每次却要扫满 10 万。

epoll 事件模型

epoll 是 Linux 的 I/O 多路复用机制,内核维护两个结构:红黑树(存所有被监听的 fd,epoll_ctl 增删改,O(log n))+ 就绪链表(rdllist,就绪 fd 的双向链表,epoll_wait 直接摘取,O(1))。fd 就绪时由内核回调把它挂到就绪链表,无需遍历全部 fd。

int epfd = epoll_create1(0);
struct epoll_event ev, events[MAX];

ev.events  = EPOLLIN | EPOLLET;         // 监听可读 + 边沿触发(ET)
ev.data.fd = listen_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);   // 注册到红黑树

for (;;) {
    int n = epoll_wait(epfd, events, MAX, -1);     // O(1) 取就绪, 只返回就绪 fd
    for (int i = 0; i < n; i++) {
        int fd = events[i].data.fd;
        if (fd == listen_fd) {
            // ET 下必须循环 accept 直到 EAGAIN,否则漏连接
            for (;;) {
                int c = accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK);
                if (c < 0) { if (errno == EAGAIN) break; else break; }
                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = c;
                epoll_ctl(epfd, EPOLL_CTL_ADD, c, &ev);
            }
        } else if (events[i].events & EPOLLIN) {
            // ET 下必须把内核缓冲一次读干净(读到 EAGAIN)
            for (;;) {
                ssize_t r = read(fd, buf, sizeof buf);
                if (r > 0)      handle(buf, r);
                else if (r == 0){ close(fd); break; }        // 对端关闭
                else { if (errno == EAGAIN) break; else { close(fd); break; } }
            }
        }
    }
}

LT(水平触发)vs ET(边沿触发):

  • LT:只要缓冲区还有数据/可写,每次 epoll_wait 都会返回该 fd。编程简单、不易丢事件,但可能重复唤醒。
  • ET:仅在状态从无到有变化时通知一次,必须循环读/写到 EAGAIN 把缓冲处理干净,否则事件丢失。唤醒次数少、效率高,但编程复杂,且 fd 必须为非阻塞。

惊群与 EPOLLEXCLUSIVE:多个进程/线程用各自 epoll 监听同一 listen fd,一个连接到来会唤醒所有等待者,只有一个 accept 成功,其余空转——即惊群。内核提供 EPOLLEXCLUSIVE 标志,让内核每次只唤醒一个等待者,缓解惊群(Nginx 也可用 SO_REUSEPORT 让内核直接把连接哈希分发到各 worker 的独立监听队列,从根上消除惊群)。

epoll 内核细节(面试深挖点)

  • 红黑树里存的是 epitem:每次 epoll_ctl(ADD) 建一个 epitem(key 为 (fd, file 指针)),挂进 struct eventpoll 的红黑树。同一个 fd 重复 ADD 返回 EEXIST。
  • 就绪靠回调,不靠轮询:注册时 epoll 把一个回调 ep_poll_callback 挂进目标 fd 的等待队列。当网卡收包、协议栈把数据放进 socket 缓冲区时,内核唤醒该等待队列,回调就把对应 epitem 链入就绪链表 rdllist。epoll_wait 只是去 rdllist 摘节点——所以是 O(1)、与总 fd 数无关。
  • epoll_wait 空则睡:rdllist 为空时调用者挂到 eventpoll 自己的等待队列上睡眠,被回调唤醒或超时才返回,不空转 CPU。
  • 常见误区:epoll 不用 mmap 共享 fd 表。网上"epoll 靠 mmap 把 fd 映射到用户态省拷贝"是错的——省拷贝来自"注册一次常驻内核",跟 mmap 无关(真正大量用 mmap 共享环形队列的是下面的 io_uring)。
  • EPOLLONESHOT:多线程共用一个 epoll 时,一个 fd 可能被多个线程同时取到导致数据竞争。加 EPOLLONESHOT 让 fd 触发一次后自动从活跃集合禁用,处理完须 epoll_ctl(MOD) 重新武装(re-arm),保证同一时刻只有一个线程处理该 fd。
  • 闭环陷阱:fd close() 会自动从 epoll 移除,但如果该 fd 被 dup/fork 复制过,底层 file 仍在,epoll 可能继续报告它——多进程场景要显式 EPOLL_CTL_DEL。

io_uring:从"就绪通知"到"完成通知"

epoll 再快也只解决了"哪个 fd 可以读写了"(readiness / 反应堆 Reactor 模型),真正的 read/write 系统调用还得你自己做。这留下两个 epoll 治不了的痛点:

  1. 每次 I/O 至少两次系统调用:epoll_wait 拿到就绪,再 read/write。百万 IOPS 下 syscall 的用户/内核态切换本身就成了瓶颈(Meltdown/Spectre 补丁后 syscall 更贵)。
  2. 普通文件无法真异步:磁盘文件在 epoll 里永远"就绪",可真正 read 时若数据不在 page cache 仍会阻塞。epoll 对磁盘 I/O 基本无能为力(这也是为什么早年只能用线程池 + 阻塞读,或残缺的 Linux AIO)。

io_uring(Linux 5.1,2019) 换成完成通知 / 前摄器 Proactor 模型:你告诉内核"帮我把这块数据读进来",内核干完再通知你"读好了"。核心是两个用户态与内核态共享(mmap)的环形队列:

  • SQE(Submission Queue Entry):一条待办 I/O 请求(opcode + fd + buf + offset)。SQ/CQ 是共享内存,提交/收割结果不用拷贝、不用 syscall 就能读写队列。
  • 批量提交:一次 io_uring_enter 可提交几十上百个 SQE,把 N 次 syscall 压成 1 次。
  • SQPOLL 模式:内核起一个轮询线程盯着 SQ,应用只管往 SQ 塞 SQE,稳态下一次 syscall 都不用(真正的零 syscall 快路径)。
  • 啥都能异步:不止 socket,read/write/accept/connect/send/recv/openat/fsync/splice 甚至 statx 都能进 SQ,磁盘 I/O 终于真异步——补上了 epoll + Linux AIO 一直缺的那块。
  • 进阶优化:register_buffers/register_files 预注册缓冲区和 fd,省掉每次的引用计数与地址翻译;IORING_OP_* 链式请求(read 完直接 write)。

epoll vs io_uring 一句话对比:

维度epollio_uring
模型就绪通知 / Reactor完成通知 / Proactor
谁做 I/O应用自己 read/write内核替你做完再通知
每次 I/O syscall≥2(wait + read/write)可批量摊薄,SQPOLL 下趋近 0
磁盘文件异步❌(永远"就绪",read 仍阻塞)✅ 真异步
内核门槛老内核都有5.1+,成熟需 5.6/5.11+
复杂度/风险成熟稳定复杂;曾因 CVE 被部分发行版默认关闭

io_uring 不是"epoll 的替代品"这么简单:它更像把 epoll 的"等就绪 + 自己收发"和磁盘 AIO 统一进一套异步完成框架。高性能存储/代理(如新版 Nginx、数据库、seastar 系)在往它迁移,但绝大多数网络服务 epoll 依旧够用且更简单。

为什么这么做

  • 为什么四层比七层快:LVS/IPVS 只看 IP + 端口做转发决策,不解析 HTTP、不建立到后端的独立连接、不做内容缓冲,在内核里查连接表即可转发;七层网关要完整解析应用层、维护双向连接、可能缓冲请求体,CPU 与内存开销高一个量级。
  • 为什么 epoll 是 O(1) 通知:select/poll 每次调用都要把全量 fd 集合从用户态拷进内核、内核线性遍历所有 fd 找就绪的、再拷回用户态——复杂度 O(n) 且反复拷贝。epoll 把"注册"和"等待"分离:fd 一次性注册进红黑树常驻内核,就绪由回调挂入就绪链表,epoll_wait 只返回就绪的那些,与总连接数无关。
  • 为什么 io_uring 更进一步:epoll 只报"就绪",收发仍要自己发 syscall;io_uring 用共享环形队列 + 完成通知,把提交/收割做成内存操作,批量提交摊薄 syscall,SQPOLL 下稳态零 syscall,且让磁盘文件也能真异步——治的是 epoll 治不了的"syscall 密度"和"文件 I/O 阻塞"。
  • 为什么 DR 让响应绕开 Director:真实业务下行流量远大于上行,只要 Director 不碰响应包,它的负载就只与"新连接建立速率"相关,而非总带宽,扩展性天花板极高。

为什么别的选择不行

  • 为什么不用 select/poll 扛高并发:select 有 FD_SETSIZE(通常 1024)硬上限;poll 无上限但仍 O(n) 全量拷贝+遍历。10 万连接下每次 epoll_wait/poll 都遍历 10 万 fd,绝大多数空闲,CPU 全耗在无效扫描上。epoll 消除了"拷贝全集"和"遍历全集"两个 O(n)。
  • 为什么不用 NAT 模式做大流量:NAT 下响应包也要回 Director 做 SNAT,Director 成为全双工带宽瓶颈;DR 只承担上行请求,吞吐高得多。
  • 为什么不直接用七层网关做第一层:七层单机吞吐有限、成本高,无法承接超大流量入口;用 LVS 四层先把海量连接廉价均分到一排七层网关,才能兼顾规模与能力。
  • 为什么不用一连接一线程:线程栈(默认 MB 级)+ 上下文切换成本使得万级线程就把内存与调度器压垮;epoll 单线程事件循环用一个线程管十万连接,内存与切换开销恒定。
  • 为什么不无脑上 io_uring:接口复杂、SQ/CQ 生命周期与内存序易错,依赖新内核(5.6 前坑多),且历史上多次 CVE 让 Docker/发行版默认禁用它。纯网络服务用 epoll 已经把 syscall 摊得很薄,收益有限;io_uring 的甜点在高 IOPS 磁盘 I/O、混合网络+存储、超高连接建立速率这类 epoll 摸不到的场景。

沉淀结论

  1. LVS 三模式选型:跨网段用 TUN、同网段极致性能用 DR、简单场景用 NAT。DR 快在"只改 MAC + 响应直连客户端"。
  2. 调度默认 wlc;需要会话保持用 sh;纯均分用 rr/wrr。
  3. 高可用靠 Keepalived + VRRP:主备竞选 VIP,故障发免费 ARP 秒级切换,并做 RS 健康检查。
  4. epoll 胜出的两句话:注册与等待分离(红黑树常驻 + 就绪链表 O(1)),只返回就绪 fd(消除 select/poll 的全量拷贝与遍历)。
  5. 三代演进:select(bitmap,1024 上限,全量拷+扫)→ poll(数组去上限,仍全量)→ epoll(注册一次 + 回调就绪 + 只返就绪)。
  6. io_uring 再进一步:从"就绪通知(Reactor)"到"完成通知(Proactor)",共享环形队列 + 批量提交 + SQPOLL 零 syscall + 磁盘真异步;代价是复杂与新内核依赖。
  7. LT vs ET:LT 简单稳妥,ET 高效但必须非阻塞 + 读写到 EAGAIN;惊群用 EPOLLEXCLUSIVE 或 SO_REUSEPORT 解。
  8. 分层配合:LVS(四层,均分连接) → Nginx(七层,内容路由/TLS/缓存) → 应用后端,各取所长。

记忆口诀

LVS 三模式:NAT 都过网关最慢 / DR 只改MAC响应直连最快 / TUN 隧道封装可跨段
三代多路复用:select定长bitmap千上限全量拷 / poll数组去上限仍全量 / epoll注册一次回调就绪只返就绪
epoll 两句话:注册等待分离(红黑树+就绪链表) / 只返就绪fd(消灭全量拷贝遍历)
epoll↔io_uring:epoll报就绪你自己收发(Reactor) / io_uring内核干完再通知(Proactor)共享环+批量+SQPOLL零syscall
LT vs ET:LT非空就通知可分次读简单 / ET从无到有一次必须读到EAGAIN且非阻塞
惊群解法:EPOLLEXCLUSIVE只唤醒一个 / SO_REUSEPORT内核哈希分发到各worker

内容来源

综合整理。主要参考方向:Linux Virtual Server 官方文档(IPVS、NAT/DR/TUN 模式与 ipvsadm)、ipvsadm(8) 手册、Keepalived 官方文档与 VRRP(RFC 3768/5798)、select(2)/poll(2)/epoll(7)/epoll_ctl(2)/epoll_wait(2) man page 与内核 fs/eventpoll.c 实现(红黑树 + rdllist + ep_poll_callback)、C10K/C10M 问题综述、Nginx SO_REUSEPORT/EPOLLEXCLUSIVE 惊群处理文档、io_uring 论文 Efficient IO with io_uring(Jens Axboe)与 io_uring_setup(2)/io_uring_enter(2) man page、liburing。

相关专题:本篇是 epoll/IO 多路复用(LT/ET、惊群)的详解归属;零拷贝(sendfile/splice/mmap)详见 零拷贝,TCP 握手/拥塞/流控详见 TCP 网络。LVS DR 在游戏端到端低延迟链路中的定位见 全栈极限低延迟。

自测:合上资料能说清楚吗?

DR 模式为什么比 NAT 快?它对 Real Server 有什么特殊要求?

参考答案

DR 下 Director 只改目的 MAC转发入向请求,响应包由 RS 直连客户端不经 Director;而下行流量远大于上行,Director 只承担最小的上行部分,吞吐最高。要求:RS 在 lo 绑 VIP 并设 arp_ignore=1、arp_announce=2 抑制 ARP,且与 Director 同二层网段。

epoll 相比 select/poll 快在哪两个关键点?

参考答案

一是注册与等待分离:fd 一次性注册进红黑树常驻内核,就绪时由回调挂入就绪链表,epoll_wait 直接摘取 O(1)。二是只返回就绪 fd:消除了 select/poll 每次调用把全量 fd 拷进内核并线性遍历 O(n) 的两处开销,性能与总连接数无关。

select、poll、epoll 三代分别解决了什么、又留下什么?

参考答案

select:fd_set 定长 bitmap,FD_SETSIZE≈1024 硬上限;值-结果传参每次要重建集合;全量拷进内核 + 全量遍历 + 返回后 FD_ISSET 全量扫,三处 O(n)。poll:改用 pollfd 数组去掉 1024 上限、events/revents 分离免重建,但仍是全量拷 + 全量遍历。epoll:epoll_ctl 注册一次常驻红黑树、就绪由回调挂入就绪链表、epoll_wait 只返就绪 fd,彻底消除随连接数增长的线性开销,并支持 LT/ET。

io_uring 和 epoll 的本质区别是什么?它补了 epoll 的哪些短板?

参考答案

epoll 是就绪通知(Reactor)——只告诉你 fd 可读写,read/write 还得应用自己发 syscall;io_uring 是完成通知(Proactor)——你把 I/O 请求写进共享内存的提交队列 SQ(SQE),内核干完把结果写进完成队列 CQ(CQE)。它补了 epoll 两个短板:① syscall 密度——SQ/CQ 是共享内存、可批量提交,SQPOLL 模式下稳态零 syscall;② 磁盘文件 I/O——文件在 epoll 里永远"就绪"但 read 仍可能阻塞,io_uring 让文件读写也能真异步。代价是接口复杂、依赖新内核(5.6+ 才成熟)、历史上有 CVE 被默认禁用。

LT 和 ET 有什么区别?ET 模式编程要注意什么?

参考答案

LT(水平触发):缓冲区非空/可写就每次 epoll_wait 都通知,可分次读,编程简单。ET(边沿触发):仅状态从无到有变化时通知一次,必须循环读写到 EAGAIN 把缓冲处理干净否则丢事件,且 fd 必须为非阻塞。ET 唤醒次数少、效率高但易出错。

多进程监听同一 listen fd 的惊群问题是什么?有哪两种解法?

参考答案

惊群:一个连接到来唤醒所有等待者,只有一个 accept 成功,其余空转浪费 CPU。解法一 EPOLLEXCLUSIVE:内核每次只唤醒一个等待者。解法二 SO_REUSEPORT:内核把连接哈希分发到各 worker 的独立监听队列,从根上消除惊群且负载更均衡。

为什么用 LVS 四层做第一层、Nginx 七层做第二层,而不直接用七层网关接入?

参考答案

七层网关要完整解析应用层、维护双向连接、缓冲请求体,单机吞吐有限、成本高,无法承接超大流量入口。LVS 四层只看 IP+端口在内核查表转发,能廉价把海量连接均分到一排 Nginx;再由七层做内容路由/TLS/缓存。分层各取所长,兼顾规模与能力。

最近更新: 2026/9/10 11:38
Prev
DNS 清洗拦截与 CoreDNS
Next
TCP/HTTP 滑动窗口