当前位置:首页 > 文章列表 > 文章 > 前端 > PerformanceResourceTiming 拆分 DNS 与 TLS 耗时

PerformanceResourceTiming 拆分 DNS 与 TLS 耗时

来源:17golang原创 2026-10-01 19:34:29 0浏览 收藏

PerformanceResourceTiming 可以把资源请求的连接阶段拆成 DNS、TCP 和 TLS 三部分。DNS 使用 domainLookupEnd - domainLookupStart;完整连接使用 connectEnd - connectStart;对 HTTPS 连接,TLS 可按 connectEnd - secureConnectionStart 计算,TLS 之前的连接部分则是 secureConnectionStart - connectStart。

真正难点不在减法,而在 0 值解释:跨域资源没有开放 Resource Timing 时,相关字段会被隐藏为 0;DNS 缓存、HTTP 连接复用和资源缓存也可能让阶段耗时为 0。因此采集代码必须同时输出“数值”和“是否可解释”,不能把所有 0 都当成网络极快。

背景:一个连接耗时数字不够定位问题

假设监控平台只上报 connectEnd - connectStart,某个静态资源的连接阶段突然从几十毫秒升到数百毫秒。这个数字不能直接说明是 DNS 解析变慢、TCP 建连受阻,还是 TLS 握手成本上升。

PerformanceResourceTiming 为资源加载提供高精度时间戳。与本文相关的字段如下。

阶段起点终点计算方式
DNSdomainLookupStartdomainLookupEnd终点 - 起点
完整连接connectStartconnectEnd终点 - 起点
TLSsecureConnectionStartconnectEnd仅安全连接且字段可见时计算
TLS 前连接connectStartsecureConnectionStart仅安全连接且字段可见时计算

有些资料也用 requestStart - secureConnectionStart 表示从 TLS 开始到请求发出的整体区间。本文为了严格拆分连接阶段,使用 connectEnd 作为 TLS 终点;如果业务关心“握手开始到真正发出请求”的等待,则可另外记录前一种区间,但不要把两种口径混在同一报表里。

PerformanceResourceTiming DNS 与 TLS 字段关系说明图

图1:DNS、连接与请求边界中的字段依赖;图中关系用于解释公式,不表示真实运行时间轴。

旧写法的问题:直接相减会制造假结论

function getNetworkCost(entry) {
  // 旧写法直接相减,没有区分不可观测、复用连接和非 HTTPS。
  return {
    dns: entry.domainLookupEnd - entry.domainLookupStart,
    tls: entry.connectEnd - entry.secureConnectionStart,
  };
}

这段代码至少有三个误区。

  1. 跨域字段受限:跨域响应没有 Timing-Allow-Origin 时,domainLookupStart、connectStart、secureConnectionStart、requestStart 等字段默认返回 0。此时相减结果不是有效网络测量。
  2. 非 HTTPS:普通 HTTP 没有 TLS 握手,secureConnectionStart 为 0,直接用 connectEnd - 0 会把相对时间戳误报为超大的 TLS 耗时。
  3. 缓存和连接复用:浏览器可能不需要重新解析 DNS 或建立连接,阶段耗时为 0 并不代表采集失败;仅凭一个条目也不能断言具体命中了哪一层复用。

新规则:数值和状态一起返回

更稳妥的 API 不只返回毫秒数,还标记详细时间是否可见、是否存在新的安全握手,以及 0 值应如何解释。下面的函数保持口径单一,并对浮点误差做非负保护。

function splitConnectionTiming(entry) {
  // requestStart 为 0 时,常见原因是跨域详细时间未通过 TAO 开放。
  const detailedTimingVisible = entry.requestStart > 0;

  if (!detailedTimingVisible) {
    return {
      observable: false,
      reason: "timing-restricted-or-unavailable",
      dnsMs: null,
      tcpBeforeTlsMs: null,
      tlsMs: null,
      connectionMs: null,
    };
  }

  const duration = (end, start) => Math.max(0, end - start);
  const hasTlsHandshake = entry.secureConnectionStart > 0;

  return {
    observable: true,
    reason: hasTlsHandshake ? "new-secure-connection" : "no-new-tls-handshake",
    dnsMs: duration(entry.domainLookupEnd, entry.domainLookupStart),
    connectionMs: duration(entry.connectEnd, entry.connectStart),
    tcpBeforeTlsMs: hasTlsHandshake
      ? duration(entry.secureConnectionStart, entry.connectStart)
      : duration(entry.connectEnd, entry.connectStart),
    tlsMs: hasTlsHandshake
      ? duration(entry.connectEnd, entry.secureConnectionStart)
      : null,
  };
}

tlsMs: null 与 tlsMs: 0 应保持不同语义:null 表示当前条目没有可识别的新 TLS 握手;0 则表示握手字段存在,但在浏览器计时精度下差值为 0。监控后端也应保留这种区分,不要在序列化时把 null 强制转换成 0。

代码对比:用 PerformanceObserver 持续收集

页面初始化较晚时,单次 performance.getEntriesByType("resource") 只能看到调用当时仍在缓冲区中的条目。使用 PerformanceObserver 并设置 buffered: true,可以先接收观察器创建前已经记录的条目,再持续接收新增条目。

function reportResourceTiming(entry) {
  const timing = splitConnectionTiming(entry);

  // 只上报必要维度,避免把完整 URL 查询参数带入监控系统。
  const url = new URL(entry.name);
  const payload = {
    origin: url.origin,
    path: url.pathname,
    initiatorType: entry.initiatorType,
    nextHopProtocol: entry.nextHopProtocol,
    transferSize: entry.transferSize,
    ...timing,
  };

  // 示例只输出结构;生产环境应批量发送并限制采样率。
  console.log(payload);
}

// 长页面可增大缓冲区,默认 resource timing 缓冲条目数量有限。
performance.setResourceTimingBufferSize(1000);

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // observe 的 type 已限定 resource,这里按 Resource Timing 条目处理。
    reportResourceTiming(entry);
  }
});

observer.observe({ type: "resource", buffered: true });

采集 URL 时最好只保留 origin 和 pathname,移除查询参数与片段,避免把用户标识、签名或临时令牌送入性能平台。对于高流量页面,还应限制采样率和单页上报条数。

兼容注意:跨域 TAO 决定能否看到详细字段

同源资源通常可以读取完整 timing。跨域资源若希望被页面读取 DNS、连接、请求开始等详细时间,资源响应必须带 Timing-Allow-Origin。例如,只允许指定站点读取时可以返回:

# 由跨域资源服务器返回,仅允许指定来源读取详细 Resource Timing。
Timing-Allow-Origin: https://app.example.com

这是响应头,不是前端请求头。前端 JavaScript 自己添加同名请求头不会开放数据。若资源来自 CDN,还要确认该响应头在缓存键、边缘节点和最终响应中按预期保留。

Resource Timing 可观测性与零值解释结构图

图2:资源来源、TAO 响应头、缓存与连接复用共同影响最终可解释的 DNS/TLS 指标。

采用建议:先按可观测性分组,再比较耗时

采集到数据后,不要直接把所有资源混在一个平均值里。建议先按以下维度分组。

状态字段表现处理方式
详细时间受限requestStart 等受限字段为 0记为不可观测,不纳入 DNS/TLS 平均值
新 HTTPS 连接secureConnectionStart > 0分别记录 DNS、TLS 前连接与 TLS
没有新 TLS 握手secureConnectionStart = 0,详细字段可见TLS 记为 null,并结合缓存/复用信息解释
DNS 差值为 0起止时间相等可能是缓存或复用,不直接宣称 DNS 为零成本

transferSize === 0 常用于辅助判断资源是否来自本地缓存,nextHopProtocol 可以说明协商到的协议。但单个字段通常不足以还原浏览器内部连接池决策,性能报表应使用“可能复用”“未观察到新握手”等谨慎表述。

常见问题

为什么同一个域名只有第一个资源有 DNS 和 TLS 耗时?

后续资源可能复用了 DNS 结果和已建立连接,因此没有新的查询或握手阶段。这通常是浏览器连接管理的正常现象,应按资源批次或 origin 聚合,而不是要求每个资源都出现非零值。

TLS 应该用 connectEnd 还是 requestStart 作为终点?

若目标是拆分连接阶段,使用 connectEnd - secureConnectionStart 更贴合“安全连接建立完成”的边界;若要衡量从握手开始到请求即将发送的整体区间,可记录 requestStart - secureConnectionStart。两者命名和口径必须分开。

跨域字段全部为 0,前端能绕过吗?

不能靠前端代码绕过。需要跨域资源的响应端正确返回 Timing-Allow-Origin,并确保 CDN 或代理没有移除该头。

为什么观察器没有收到更早的资源?

需要使用 { type: "resource", buffered: true },同时注意 resource timing 缓冲区容量。长生命周期页面可提前创建观察器、增大缓冲区,并及时消费条目。

总结

用 PerformanceResourceTiming 拆分 DNS 与 TLS 的核心,是先定义稳定公式,再定义 0 值语义。DNS 用域名查询起止字段,TLS 用安全连接开始到连接结束字段;跨域受限返回不可观测,未发生新握手返回 null。这样监控平台看到的不是一堆含义混乱的零值,而是可以按来源、协议和连接状态解释的性能数据。

参考资料

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go runtime/trace 观察阻塞区域的阅读方式Go runtime/trace 观察阻塞区域的阅读方式
上一篇
Go runtime/trace 观察阻塞区域的阅读方式
动漫岛网页端需要登录吗?追番记录、APP提醒与账号边界说明
下一篇
动漫岛网页端需要登录吗?追番记录、APP提醒与账号边界说明
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    289次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    342次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    344次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    308次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    130次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码