4、连接复用:HTTP Keep-Alive、连接池、多路复用对比
说到连接性能优化,有个问题我一直觉得特别有意思:为什么明明 TCP 三次握手已经够快了,我们还要想尽办法复用连接?
我早年做网关优化时,亲眼见过一个场景——客户端每秒发起上千次请求,每次都是新建 TCP 连接。三次握手 + 慢启动,光握手开销就占了请求总耗时的 40%。那时候我就意识到:减少握手次数,比优化握手本身更划算。
这一章,我们来聊聊三种主流的连接复用手段:HTTP Keep-Alive、连接池、多路复用。它们的目标一致——让 TCP 连接活得更久、干得更多。
核心观点:连接复用的本质,是用「已建立的连接」去换「新建连接的时间」。三次握手的 1.5 RTT 开销,在复用场景下只需要支付一次。
4.1 HTTP Keep-Alive:最朴素的复用
HTTP Keep-Alive 是连接复用的「第一课」。说白了,就是让 TCP 连接在完成一次 HTTP 请求/响应后不关闭,等着下一个请求继续用。
在 HTTP/1.0 时代,每个请求都要新建连接。后来 HTTP/1.1 把 Keep-Alive 变成了默认行为。你想想看,如果一个页面有 80 个资源,短连接需要 80 次三次握手,Keep-Alive 只需要 1 次。这差距,肉眼可见。
我的经验:Keep-Alive 的 timeout 设置很关键。太短(比如 5 秒)起不到复用效果;太长(比如 300 秒)会占用服务器 fd 资源。我一般建议 30-60 秒,配合 max_requests 限制单连接处理请求数,防止内存泄漏。
但 Keep-Alive 有个硬伤:串行。同一个连接上,请求必须排队。前一个请求没响应,后一个就得等着。这就是 HTTP 队头阻塞(HOL blocking)的根源。
4.2 连接池:并发复用
既然单连接串行太慢,那就多搞几条连接。连接池的思路很简单:维护一组已建立的 TCP 连接,需要时从池子里取,用完放回。
我在项目中遇到过这样一个场景:微服务间 RPC 调用,每次调用都新建连接,导致 CPU 在 connect 系统调用上消耗了 15%。后来引入连接池,预创建 10-20 条连接,调用延迟直接降了 40%。
连接池的核心参数有这几个:
| 参数 | 含义 | 建议值 |
|---|---|---|
| 最大连接数 | 池中最多维护的连接数 | 根据并发量估算,一般 10-50 |
| 最小空闲数 | 保持至少多少条空闲连接 | 2-5,避免突发流量时建连 |
| 连接超时 | 空闲连接的最大存活时间 | 30-120 秒 |
| 获取超时 | 从池中获取连接的最大等待时间 | 100-500 ms |
⚠️ 避坑指南:我曾经遇到过一个线上事故——连接池最大连接数设了 200,但服务端最大文件描述符只有 1024。高峰期连接池占满 fd,新连接进不来,旧连接又释放不掉,最终导致服务雪崩。记住:连接池大小要和服务端容量对齐。
连接池的典型实现有:Apache HttpClient 的 PoolingHttpClientConnectionManager、Redis 的 JedisPool、数据库连接池 HikariCP。它们底层做的事情都一样——用空间换时间。
4.3 多路复用:单连接并行
连接池虽然能并发,但每条连接都有自己的开销。有没有办法在一条连接上并行处理多个请求?
有。这就是 HTTP/2 的多路复用。
HTTP/2 引入流(Stream)的概念。一条 TCP 连接上可以同时存在多个流,每个流独立传输请求和响应。数据被拆分成更小的帧(Frame),帧与帧之间交错传输,接收方按流 ID 重组。
我刚开始看 HTTP/2 协议时,觉得这玩意儿太复杂了。但后来在 CDN 项目里实测,多路复用比 HTTP/1.1 的 Keep-Alive 快了将近 3 倍。为什么?因为彻底解决了队头阻塞。
关键区别:HTTP/1.1 的 Keep-Alive 是「串行复用」,连接池是「多连接并发复用」,HTTP/2 多路复用是「单连接并行复用」。三者的复用粒度依次提升。
来看一个简单的对比:
| 特性 | HTTP Keep-Alive | 连接池 | HTTP/2 多路复用 |
|---|---|---|---|
| 连接数量 | 1 条 | 多条(池化管理) | 1 条 |
| 请求方式 | 串行 | 多连接并行 | 单连接并行 |
| 队头阻塞 | 有(HTTP 层) | 无(连接间独立) | 无(流间独立) |
| 资源开销 | 低 | 中(多条连接) | 低(单连接) |
| 适用场景 | 低频请求、小资源 | 高并发、微服务 RPC | 大量小资源、实时通信 |
4.4 如何选择?
没有银弹。三种方案各有各的适用场景。
- HTTP Keep-Alive:适合请求频率不高、资源量不大的场景。比如 API 调用、简单的 Web 页面。实现简单,浏览器默认支持。
- 连接池:适合高并发、长连接的场景。比如数据库访问、Redis 操作、微服务间 gRPC 调用。需要客户端库支持,配置得当能大幅提升吞吐。
- 多路复用:适合大量小资源并行加载的场景。比如现代 Web 页面(几十个 CSS/JS/图片)、实时推送服务。需要 HTTP/2 协议支持,服务端和客户端都要升级。
我的建议:如果你在维护一个老系统,先上 Keep-Alive,零成本改造。如果并发上来了,加连接池。如果追求极致性能,升级到 HTTP/2 多路复用。别一上来就搞最复杂的方案——性能优化的第一原则是:用最小的改动解决最大的问题。
嗯,这一章就到这里。连接复用是 TCP 性能优化的「第一桶金」,搞懂了它,你就已经超越了 80% 的开发者。