跳转至

HTTP 连接资源消耗

理解单个 HTTP 连接的真实资源开销,才能在百万并发场景下做出正确的架构决策


核心概念

  1. TCP 连接内存 — 内核为每个连接分配发送/接收缓冲区和控制块,约 3-10 KB
  2. 线程开销 — 传统模型每连接一个线程(~1 MB),协程模型仅需几 KB
  3. TLS 会话 — HTTPS 连接额外消耗 50-100 KB 内存
  4. 文件描述符 — 每个连接占一个 fd,受系统 ulimit 限制
  5. 临时端口 — 客户端端口上限 65535,高并发时需要多端口或多 IP

详解

单个 TCP 连接的内核开销

操作系统为每个 TCP 连接分配的资源:

资源 大约消耗 说明
内核内存 3-10 KB 发送/接收缓冲区 + TCP 控制块
文件描述符 1 个 fd 受 ulimit 限制
CPU — 三次握手 + 协议栈处理
临时端口 1 个 客户端端口,上限 65535

服务器侧的连接模型差异

模型 每连接内存 比喻
线程模型(Java BIO) ~1 MB(线程栈) 每人占一条泳道(1米宽)
协程模型(Go/async) ~4 KB 每人只占一把椅子
异步事件驱动(Nginx) ~2 KB 几乎不占位置

百万并发的真实数字

指标 单连接 × 1,000,000
内核内存 ~10 KB ~10 GB
线程(传统模型) ~1 MB ~1 TB(不可行)
协程(Go/async) ~4 KB ~4 GB(可行)
TLS 内存(HTTPS) ~50 KB ~50 GB
文件描述符 1 100 万(需调 ulimit)

形象比喻

传统线程模型 — 泳池:

每个连接 = 一个人占一条泳道(1 米宽)
100 万人 → 需要 100 万条泳道 → 1000 公里宽的泳池
物理上不可能 🤯

异步/事件驱动模型 — 叫号系统:

人来了不占泳道,坐在池边等叫号
有数据来了才临时下水
每个人只占一把椅子(几 KB)
100 万人 → 100 万把椅子 → 大概几百 MB ✅

高速公路收费站:

模型 比喻 100 万车
线程模型 每辆车配一个专属收费员 需要 100 万个收费员
异步模型 一个收费员 + 叫号系统 一个收费员 + 一块显示屏 + 几 GB 内存

撑住百万并发的关键手段

手段 说明
Epoll / Kqueue 不为每个连接开线程,一个线程监控百万 fd
连接池复用 HTTP/1.1 Keep-Alive、HTTP/2 多路复用,少建新连接
轻量协程 Go goroutine、Rust async、Python asyncio
负载均衡 L4 LB 分散到几十台机器
内核调优 ulimit -n 1048576、调整 TCP 缓冲区大小

常见陷阱

误区:连接池越大越好

池子里每个连接都占内存和 fd。配得合理比配得大更重要。连接池打满时新请求排队,延迟飙升。

别忽视 TLS 开销

HTTPS 场景下 TLS 会话缓存每个连接 50-100 KB,百万并发下可能吃掉 50 GB 内存。考虑在 CDN 边缘终止 TLS。


练习题

Go 的 goroutine 初始栈大小是多少?和 Java 线程相比如何?
答案

Go goroutine 初始栈大小为 2 KB(可动态增长到 1 GB),Java 线程默认栈大小为 1 MB。这意味着在相同内存下,Go 能创建的并发单元数量是 Java 的约 500 倍。

为什么操作系统对客户端临时端口有限制?
答案

客户端端口是 16 位整数,理论上限 65535。每个 TCP 连接需要一个唯一的四元组(源IP:源端口:目标IP:目标端口),如果一个客户端对同一服务器发起大量连接,端口很快耗尽。解决方案包括使用多 IP、连接池复用、HTTP/2 多路复用等。