从 1500ms 到 400ms:一次跨域请求的性能优化

date
Mar 6, 2025
slug
cors-max-age
status
Published
tags
Website
Devops
Cloudflare
Tech
summary
跨域请求的预检耗时是一个容易被忽视的性能瓶颈,性能优化不仅要盯着后端,更要关注全链路
type
Post
最近,我开发了一个新的服务,作为后端开发,我习惯性地在发布前 Review 前端的 API 调用。然而,这次 Review 却让我发现了一个令人费解的问题:某关键接口请求耗时在浏览器显示为 1.5s,但检查后端日志后发现,实际的请求耗时只有 400ms 左右。这就像外卖小哥说餐已送达,而用户却苦等半小时——中间消失的1.1秒究竟被谁偷吃了?

问题发现:前端的 1.5s 与后端的 400ms

在 Review 前端代码时,我注意到一个关键 API 的请求耗时异常高,达到了 1.5s。然而,后端日志显示,这个请求的处理时间仅为 400ms。这意味着,超过 70% 的时间并不是后端处理消耗的,而是发生在前端与后端之间的某个环节。
我打开了 Chrome DevTools 的 Network 面板,分析这个请求的生命周期,一个神秘的"Queueing"阶段映入眼帘,它竟然吃掉了800ms!这个平时容易被忽视的字段,此刻成为了破案的关键线索。

浏览器请求耗时分析:Queueing 阶段的“罪魁祸首”

浏览器网络请求的完整生命周期包含:
在 Network 面板中,我发现这个请求的耗时分布如下:
显然,Queueing 阶段的耗时占了整个请求时间的一半。那么,Queueing 阶段到底在做什么?
notion image
 

浏览器的 Queueing 阶段:请求排队的幕后真相

浏览器的 Queueing 阶段表示请求在等待被发送到网络的时间。通常,它发生在以下场景:
  1. 同域名TCP连接数超过浏览器限制(Chrome默认6个)
  1. 请求优先级调度(如HTML/CSS优先于图片)
  1. 等待前序请求释放连接资源
  1. 跨域预检:对于跨域请求,浏览器会先发送一个 OPTIONS 预检请求,等待服务器响应后再发送实际请求。
但本案的特殊之处在于:跨域预检请求(Preflight) 让Queueing时间雪上加霜!

问题根源:跨域请求的预检耗时

由于我的前端和后端部署在不同的域名下,浏览器在发送实际请求前,会先发送一个 OPTIONS 预检请求,以确认服务器是否允许跨域请求(CORS预检机制)。这个过程包括:
  1. 浏览器发送OPTIONS预检请求(携带OriginAccess-Control-Request-Method等头)
  1. 服务器返回Access-Control-*系列响应头
  1. 浏览器确认安全后才发送真实请求
在这个过程中,OPTIONS 请求的往返时间(RTT)会显著增加请求的总耗时。在我的案例中,OPTIONS 请求的耗时约为 750ms,导致整个请求的耗时达到了 1.5s。
而问题就出在:每次跨域请求都要重复这个三部曲!更糟的是,如果预检请求遇到Queueing,整个流程耗时直接翻倍。

优化方案:CORS 预检缓存 与 Nginx 代理

方案一:CORS 预检缓存

通过设置 Access-Control-Max-Age 头,浏览器可以缓存 OPTIONS 预检请求的结果,减少重复预检。

后端配置示例:

优点
  • 减少重复的 OPTIONS 请求,简单易实现。
需要注意的是,结果可被缓存的最大秒数,Firefox 上限为 24 小时(86400 秒)。Chromium(76 版本之前)上限为 10 分钟(600 秒)。Chromium(从 76 版本开始)上限为 2 小时(7200 秒)。默认值为 5 秒。

方案二:Nginx 代理

通过 Nginx 代理,将前后端域名统一,也就是域名收敛策略, 将API统一到主域名下,避免跨域问题。

配置示例:

优点
  • 完全避免跨域问题,请求路径更简洁。

结案陈词

这次性能优化让我深刻认识到,跨域请求的预检耗时 是一个容易被忽视的性能瓶颈。通过分析浏览器的 Queueing 阶段,我定位到了问题的根源,并通过CORS 预检缓存 成功解决了这个问题。这个案例告诉我们:性能优化不仅要盯着后端,更要关注全链路
就像赛车调校,引擎再快也怕变速箱拖后腿。下次看到神秘的Queueing时间,不妨先检查下是否在"跨域交学费"!
 
(本文完)希望这篇文章能为你带来一些启发,也欢迎在评论区分享你的性能优化经验!👇
 

© Devpan 2023 - 2025