公众号:小博的前端笔记
HTTP 的 Keep-Alive 机制(也称为持久连接)是优化网络性能的关键技术,尤其在前端性能优化中高频出现。以下是其核心作用及面试回答要点:
一、核心作用
-
复用 TCP 连接
- 传统问题:HTTP/1.0 默认每次请求需新建 TCP 连接,请求完成后立即断开(三次握手 + 四次挥手带来高延迟)。
- 解决方案:Keep-Alive 复用同一 TCP 连接处理多个 HTTP 请求/响应,减少重复建立连接的次数。
-
降低延迟与资源消耗
- 避免频繁握手(减少 RTT 时间)和挥手操作,显著提升页面加载速度。
- 减少服务器/客户端 CPU、内存消耗(避免大量连接的开销)。
-
提升页面性能
- 一个网页通常包含数十个资源(JS/CSS/图片等),复用连接可并行或串行加载资源,加速整体渲染。
二、工作原理
-
客户端请求头:
Connection: keep-alive
-
服务端响应头:
Connection: keep-alive Keep-Alive: timeout=5, max=100
timeout
:空闲连接保持时间(秒)。max
:该连接最多可处理的请求数。
三、面试回答技巧
基础回答(必答核心点):
“Keep-Alive 通过复用同一个 TCP 连接处理多个 HTTP 请求,减少重复的三次握手和四次挥手开销,降低延迟和服务器压力。例如一个页面有 50 个资源,复用连接可将 50 次建连合并为 1 次,大幅提升加载速度。”
进阶亮点(差异化加分):
-
对比 HTTP/1.0 与 HTTP/1.1:
- HTTP/1.1 默认开启 Keep-Alive(无需显式设置
Connection: keep-alive
),而 HTTP/1.0 需手动启用。
- HTTP/1.1 默认开启 Keep-Alive(无需显式设置
-
队头阻塞(HOL)问题:
- 即使开启 Keep-Alive,HTTP/1.1 仍是串行请求响应(一个响应未完成会阻塞后续请求),这是 HTTP/2 多路复用解决的痛点。
-
与 HTTP/2 的关系:
- HTTP/2 的 多路复用(Multiplexing) 在 Keep-Alive 基础上更进一步:单连接并行处理多个请求,彻底解决队头阻塞。
-
何时关闭连接?
- 达到
max
请求数、超过timeout
空闲时间、或主动发送Connection: close
。
- 达到
-
前端优化实践:
- 合理设置服务端
Keep-Alive
参数(如 Nginx 的keepalive_timeout
和keepalive_requests
)。 - 域名分片(Domain Sharding)曾是 HTTP/1.1 的优化手段(绕过浏览器单域名连接数限制),但 HTTP/2 后已不推荐。
- 合理设置服务端
四、提问
”说说 Keep-Alive 的作用?“
“Keep-Alive 解决的是 HTTP 短连接的性能瓶颈。早期 HTTP/1.0 每次请求都需新建 TCP 连接,三次握手和四次挥手的开销很大。 Keep-Alive 通过复用同一个 TCP 连接处理多个请求,减少重复建连开销,降低延迟和服务器压力。 比如一个页面加载 30 个资源,复用连接可将 30 次建连合并为 1 次,加载速度明显提升。 不过要注意,HTTP/1.1 的 Keep-Alive 仍是串行处理请求,队头阻塞问题需 HTTP/2 的多路复用来解决。 实践中,我们会优化服务端参数(如 Nginx 的
keepalive_timeout
),确保连接高效复用。”
五、相关扩展
- 抓包验证:用 Wireshark 或 Chrome DevTools 的 Network 面板观察
Connection ID
,复用连接的请求 ID 相同。 - 性能影响:据 Yahoo 研究,合理使用 Keep-Alive 可减少 50% 以上延迟(尤其高延迟网络)。
- 禁用场景:代理服务器或无状态服务可能关闭 Keep-Alive(通过
Connection: close
)。
经过对浏览器的实际观察产生了以下疑问:
浏览器是如何判断不同的请求使用HTTP Keep-Alive的机制呢,我观察有的静态资源走的同一个,有的静态资源不是同一个,有的服务求请求类型的接口是同一个,有的不是同一个,我的请求都符合同源策略都是一台服务器?
答案如下:
六、Keep-Alive复用机制解析
浏览器判断是否复用 HTTP Keep-Alive 连接是一个综合决策过程,涉及客户端(浏览器)、服务器以及网络环境。即使所有请求都符合同源策略且指向同一台服务器,你观察到有些请求复用连接,有些新建连接,这是完全正常的现象。主要原因在于 HTTP Keep-Alive 的复用机制受到多个关键因素的限制:
以下是浏览器决定是否复用 Keep-Alive 连接的核心机制和影响因素:
-
HTTP 版本与
Connection
头部:- HTTP/1.1 (默认持久连接): 这是现代 Web 的标准。除非服务器或客户端明确发送
Connection: close
头部,否则浏览器默认会尝试复用现有的 TCP 连接发送后续请求。 - HTTP/1.0 (非默认持久连接): 默认情况下连接在请求-响应后关闭。要使能 Keep-Alive,客户端必须在请求中包含
Connection: keep-alive
头部,服务器也必须在响应中包含Connection: keep-alive
头部表示同意。如果服务器不支持或不响应,连接就会关闭。 - HTTP/2 和 HTTP/3: 它们本身就设计为高度复用连接(多路复用),不存在 HTTP/1.1 中“是否复用”的二元问题,复用是必然和高效的。
- HTTP/1.1 (默认持久连接): 这是现代 Web 的标准。除非服务器或客户端明确发送
-
服务器端控制:
-
Keep-Alive
响应头部: 服务器可以在响应中使用Keep-Alive
头部(非标准,但广泛支持)来提供更精细的控制:timeout=
:指定服务器希望保持连接打开的最少空闲时间(秒)。max=
:指定服务器允许在该连接上传输的最大请求数。
-
Connection: close
响应头部: 服务器在任何响应中发送此头部,表示在完成此响应后立即关闭连接。后续请求必须建立新连接。 -
服务器配置: Web 服务器(如 Nginx, Apache)有全局配置参数(例如
keepalive_timeout
,keepalive_requests
)来控制:keepalive_timeout
:空闲连接保持打开的最长时间。超时后服务器会关闭连接。keepalive_requests
:单个连接上允许的最大请求数量。达到此数量后,即使连接未超时,服务器也会在最后一个响应后关闭它。这是导致你看到“有些静态资源复用,有些不复用”的最常见原因之一。例如,如果服务器设置keepalive_requests=100
,那么前 100 个资源(无论静态还是动态)可能复用同一个连接,但第 101 个资源就需要新连接了。
-
服务器主动关闭: 服务器可能因为资源限制、负载均衡策略、配置更改或后端应用逻辑等原因主动关闭空闲或达到限制的连接。
-
-
客户端(浏览器)控制:
-
连接池: 浏览器维护一个到每个源(协议+域名+端口)的连接池。当一个 HTTP 请求需要发出时:
- 浏览器检查池中是否有空闲、健康且协议匹配(HTTP/1.1, HTTP/2)的 TCP 连接。
- 如果有,则优先复用该连接发送请求。
- 如果没有空闲连接,但当前到该源的连接数未达到浏览器的并发连接数限制(通常每个源 6-8 个),则会创建一个新连接。
- 如果连接池已满(达到并发限制)且没有空闲连接,新请求必须排队等待,直到有连接空闲出来。
-
Connection: close
请求头部: 客户端可以在请求中发送此头部,明确告诉服务器在完成此请求的响应后关闭连接。浏览器一般不会主动发送这个,除非遇到特定问题或实现细节。 -
空闲超时: 浏览器自身也可能设置一个空闲超时。如果一条连接在池中空闲时间过长,浏览器可能会主动关闭它以释放资源。
-
页面导航/卸载: 当用户离开页面(导航到新 URL 或关闭标签页/浏览器)时,浏览器通常会关闭该页面的所有连接。
-
-
网络中间件:
- 代理服务器、负载均衡器或防火墙等中间设备也可能有自己的 Keep-Alive 超时设置或连接限制策略,它们可能在不通知客户端或服务器的情况下关闭空闲连接。
-
TLS 会话恢复 (对于 HTTPS):
- 对于 HTTPS 连接,TLS 握手是昂贵的。为了优化,浏览器和服务器会尝试进行 TLS 会话恢复(Session Resumption),这通常依赖于之前的连接状态。虽然这与 HTTP Keep-Alive 复用不完全等同,但它促进了在同一个物理连接或快速建立的新连接上的复用效率。
解释你观察到的现象:
-
“有的静态资源走的同一个”:
- 这些资源请求发生时,浏览器连接池中有可用的空闲连接(之前建立的且未关闭)。
- 这些请求在同一个连接达到服务器的
keepalive_requests
限制之前被发出。 - 连接在服务器的
keepalive_timeout
和浏览器的空闲超时之内没有过期。
-
“有的静态资源不是同一个”:
- 达到连接请求上限 (
keepalive_requests
): 最常见的原因。之前的请求(可能是其他静态资源或接口)已经用完了该连接允许的最大请求数。服务器在响应最后一个允许的请求后关闭了连接。下一个资源请求必须使用新连接。 - 连接空闲超时: 在上一个请求完成和下一个请求发出之间,间隔时间超过了服务器的
keepalive_timeout
或浏览器的空闲超时,连接被关闭。 - 浏览器并发限制: 当页面同时加载大量资源时,浏览器会并行使用多个连接(最多到并发限制数)。资源 A 可能在连接 1 上,资源 B 可能在连接 2 上。虽然都是到同一个源,但它们物理上走了不同的 TCP 连接。
- 服务器主动关闭 (
Connection: close
): 服务器可能在响应某个请求时明确要求关闭连接(原因可能是负载、特定资源逻辑等)。 - 网络中间件关闭。
- 达到连接请求上限 (
-
“有的服务请求类型的接口是同一个”:
- 同静态资源复用原因。API 请求发生时,恰巧有可用的空闲连接,且没有达到连接限制或超时。
-
“有的服务请求类型的接口不是同一个”:
-
服务器主动关闭 (
Connection: close
): 这是接口请求复用失败的一个常见原因。服务器端应用程序逻辑或中间件可能对某些特定的 API 响应强制添加了Connection: close
头部。例如:- 执行了某些需要清理状态的操作。
- 服务器知道后续不会有相关请求(如登出接口)。
- 特定框架或中间件的默认行为。
- 服务器负载较高时主动关闭连接释放资源。
-
达到连接请求上限 (
keepalive_requests
): 该连接已经处理了足够多的请求(包括之前的静态资源或其他 API)。 -
连接空闲超时: 两次 API 调用间隔较长,超过了超时时间。
-
浏览器并发限制/无空闲连接: 在发出该 API 请求时,所有连接都在忙(处理其他请求),浏览器被迫创建新连接。
-
总结:
浏览器判断复用 Keep-Alive 的核心逻辑是:优先尝试复用连接池中到同源的、空闲的、健康的、未达到服务器或客户端限制的现有 TCP 连接。 复用失败(无可用连接、连接被关闭、达到并发上限)则创建新连接。
你观察到的现象差异主要是由以下因素共同作用造成的:
- 服务器的
keepalive_requests
限制: 导致连接在达到一定请求数后被强制关闭。 - 服务器的
keepalive_timeout
/ 浏览器的空闲超时: 导致空闲连接被关闭。 - 服务器显式发送
Connection: close
: 特别是对某些 API 接口,是导致接口复用失败的重要原因。 - 浏览器的并发连接数限制: 导致同时使用多个连接。
- 请求的时序: 请求发出的时间点决定了当时连接池的状态(是否有空闲连接、连接是否刚被关闭)。