深蓝黑背景封面:右侧大标题「流量进来,要过几层分发」,副标题「LVS、Nginx、业务网关:四层与七层怎么搭」;左侧原创示意图——一支大流量箭头从顶端落下,依次穿过四层逐渐变窄的漏斗板:LVS 四层转发、Nginx 七层代理、业务网关、应用服务,每层板下分出若干支流;左上标注「知识专栏 · 负载均衡」,右上标注「Day 07 · 11-04」 图 1:本文封面,流量像过漏斗,每层挡掉一类问题。「反向代理有什么优点」是道经典面试题,标准答案是转发、均衡、卸载、缓存那串职责清单。真到搭一套扛量的系统,问题变成另一个样子:LVS、Nginx、业务网关经常同时出现在一张架构图里,它们各管哪一层、为什么不嫌多?本文把这件事讲透。

四层与七层:看得见什么,决定了改得了什么。

四层(L4)在传输层工作,眼里只有 IP 和端口:看到一个五元组,按调度算法选一台后端,转发。七层(L7)在应用层工作,能看懂完整 HTTP 请求,于是能按 URL 路由、按 Header 做灰度、卸载 TLS、缓存静态内容。能力越强代价越贵——七层要终结 TCP 连接、解析协议,单机容量比四层低几个量级。这是理解一切分层组合的基本矛盾:性能与灵活不可兼得,让便宜的层挡在前面。

LVS:四层的极限性能担当。

LVS 工作在内核态,是四层负载均衡的经典实现,它的两种主流模式把「回包走哪」这个关键问题答得完全不同。

NAT 模式最直观:调度器把请求包的目的地址和端口改写成选中的真实服务器,回包经过调度器时再改回虚拟地址。双向都要过调度器,调度器因此是带宽瓶颈点,但真实服务器无需任何特殊配置,内网随便扩。

DR(直接路由)模式是性能解:调度器和所有真实服务器共享同一个虚拟 IP,调度器收到请求后只改数据帧的目的 MAC 地址,在同局域网内把帧直接发给选中的服务器;服务器处理完,回包直接从自己发往客户端,不再经过调度器。调度器只扛入向流量,出向带宽完全卸载——代价是所有节点必须在同一局域网,且要解决 ARP 冲突问题。海量连接入口用 DR,正是这个原则的教科书案例。

Nginx:七层的通用反向代理。

到了七层,Nginx 的官方 upstream 模块文档把能力清单列得很清楚:定义一组后端服务器,按加权轮询、最少连接、IP 哈希等策略分发;支持被动健康检查(max_fails 与 fail_timeout,失败超阈值就临时摘除)、备用服务器(backup)、长连接复用(keepalive 连接池)。它终结客户端连接,再另起一条到后端的连接——「终结再重建」这个动作就是七层一切能力的来源:TLS 在这里卸载、请求在这里被看懂、响应在这里可缓存。

业务网关:最贴近业务的那一层。

网关不做通用负载均衡,它做业务语义:鉴权、参数校验、接口级限流、灰度路由。这些逻辑依赖业务上下文(用户身份、套餐、实验分组),放在通用组件里又贵又别扭,于是独立成一层。

三层的分工,用代码跑一遍。

下面这段模拟让同一个请求依次穿过三层,看每层「看得见什么、改得了什么」,用 Node.js 22.15.0 直接运行(node lb_layers.js):

'use strict';

// 三层分发语义模拟:同一个请求依次穿过 LVS(L4)、Nginx(L7)、业务网关。

const packet = {
  srcIp: '203.0.113.9', srcPort: 51000,
  dstIp: '198.51.100.10', dstPort: 80, // VIP
  dstMac: 'AA:AA:AA:AA:AA:AA',         // LVS 调度器的 MAC
  http: { method: 'GET', path: '/api/v2/predict', header: { Authorization: 'Bearer ***' } },
};

function lvsDR(pkt, realServers) {
  const rs = realServers[0];
  const out = { ...pkt, dstMac: rs.mac }; // DR:只改目的 MAC,回包直达客户端
  return {
    forward: out,
    log: [
      `LVS(DR) 看到:四层首部 dst=${pkt.dstIp}:${pkt.dstPort},看不懂 HTTP 内容`,
      `LVS(DR) 动作:dstMac 改为 ${rs.mac} 后直接在同 LAN 转发,IP 与端口不动`,
      `LVS(DR) 回包:${rs.ip} 直接回给 ${pkt.srcIp},不再经过调度器`,
    ],
  };
}

function lvsNAT(pkt, realServers) {
  const rs = realServers[0]; // NAT:双向都改写,回包必须再过调度器
  return {
    forward: { ...pkt, dstIp: rs.ip, dstPort: rs.port },
    log: [
      `LVS(NAT) 动作:dst 改写为 ${rs.ip}:${rs.port}`,
      `LVS(NAT) 回包:源地址改回 VIP ${pkt.dstIp}:${pkt.dstPort},回包必须再过调度器`,
    ],
  };
}

function nginxL7(conn) {
  const upstream = conn.http.path.startsWith('/api/') ? 'api-cluster' : 'static-cluster'; // 按 URL 选 upstream
  return {
    upstream,
    log: [
      `Nginx 看到:完整 HTTP 请求 ${conn.http.method} ${conn.http.path}`,
      `Nginx 动作:终结客户端连接,另起一条到 ${upstream} 的连接;可卸载 TLS、按 URL 分发`,
    ],
  };
}

function bizGateway(req) {
  const authed = Boolean(req.http.header.Authorization); // 鉴权
  const gray = req.http.path.includes('v2');             // 灰度
  return {
    decision: authed ? (gray ? '灰度 pool-v2' : '稳定 pool-v1') : '拒绝 401',
    log: [
      `网关看到:业务语义——凭证${authed ? '有效' : '缺失'}、版本 ${gray ? 'v2' : 'v1'}`,
      `网关动作:鉴权 -> 灰度路由 -> 接口级限流`,
    ],
  };
}

const rs = [{ ip: '10.0.0.11', port: 8080, mac: 'BB:BB:BB:BB:BB:BB' }];
console.log('--- L4 层:LVS ---');
console.log(lvsDR(packet, rs).log.join('\n'));
console.log(lvsNAT(packet, rs).log.join('\n'));
console.log('--- L7 层:Nginx ---');
const r7 = nginxL7(packet);
console.log(r7.log.join('\n'));
console.log(`选中 upstream:${r7.upstream}`);
console.log('--- 业务层:网关 ---');
const gw = bizGateway(packet);
console.log(gw.log.join('\n'));
console.log(`路由决策:${gw.decision}`);

实测输出:

--- L4 层:LVS ---
LVS(DR) 看到:四层首部 dst=198.51.100.10:80,看不懂 HTTP 内容
LVS(DR) 动作:dstMac 改为 BB:BB:BB:BB:BB:BB 后直接在同 LAN 转发,IP 与端口不动
LVS(DR) 回包:10.0.0.11 直接回给 203.0.113.9,不再经过调度器
LVS(NAT) 动作:dst 改写为 10.0.0.11:8080
LVS(NAT) 回包:源地址改回 VIP 198.51.100.10:80,回包必须再过调度器
--- L7 层:Nginx ---
Nginx 看到:完整 HTTP 请求 GET /api/v2/predict
Nginx 动作:终结客户端连接,另起一条到 api-cluster 的连接;可卸载 TLS、按 URL 分发
选中 upstream:api-cluster
--- 业务层:网关 ---
网关看到:业务语义——凭证有效、版本 v2
网关动作:鉴权 -> 灰度路由 -> 接口级限流
路由决策:灰度 pool-v2

一个请求三种视角:LVS 只看到五元组,改个 MAC 就放行;Nginx 看到完整 HTTP,按 /api 前缀选集群;网关看到凭证和版本,才谈得上鉴权与灰度。层次越往下信息越多、单价越贵。

分层组合图:上方一条主干流量线纵贯三层,每层一块面板标明分工与能力边界。LVS 工作在网络层,DR 模式只改 MAC 转发请求、回包直达客户端,单机扛得住海量连接但看不懂 HTTP;Nginx 工作在应用层,终结连接、按 URL 分发、卸载 TLS、缓存静态内容,是通用反向代理;业务网关最贴近业务,做鉴权、参数校验、灰度路由与接口级限流。右侧一栏解释为什么要分层而不是只留一层:性能与灵活不可兼得,越靠下能力越强单价越贵,让便宜的层挡在前面。底部提示:排障时按层切流量,先确定故障在哪一层再深入 图 2:三层各管一段,便宜的层挡在前面。

**这套体系还有两个推论。**一是容量规划按层做:LVS 按连接数和带宽估,Nginx 按新建连接速率与 TLS 算力估,网关按业务逻辑复杂度估,混在一起拍容量一定拍错。二是排障按层切:先区分「连接级失败」还是「请求级失败」,前者往下查四层,后者往上查七层和网关,而不是一上来就抓包。模型网关的鉴权、改写、限流挂在哪一层直接决定行为是否符合预期,看懂分层,给网关写扩展时就知道自己该站在哪。

你维护的流量入口是几层结构?有没有遇到过「该在这一层做的事被硬塞进另一层」的设计?欢迎聊聊。