作为开发者(特别是前端开发者),我们通常在浏览器窗口开始出现内容时,以及我们可以消费内容或与页面交互时,讨论网页性能。例如,以下核心网络关键指标指导我们可以看到、使用和体验的讨论:
- 首次内容绘制(FCP),它测量用户首次导航到页面到页面任何部分的内容被渲染的时间,
- 最大内容绘制(LCP),这是页面加载时间线中的主要内容可能已经加载的点,
- 交互到下一绘制(INP),它测量网页对用户输入的响应速度。
鉴于没有可以消费和/或交互的内容,我们实际上没有体验可以测量,但浏览器接收到网页的第一个字节之前的事件呢?我们可以测量这些事件,并随后优化它们,使我们的网页和应用程序加载得更快吗?
Sentry Trace View中如何可视化预TTFB事件
当查看Sentry中的Trace View时,上述问题浮现在脑海中,其中捕获并标记为browser
跨度的是在浏览器窗口中渲染任何内容之前发生的事件。按时间顺序注册了六个跨度:缓存、DNS、连接、TLS/SSL、请求和响应。所有在响应
之前的事件都在第一字节时间(TTFB)之前,它测量请求网页/资源和第一个响应字节开始到达之间的时间。
您可能想知道这些事件是如何被Sentry捕获的,考虑到在页面加载时间线的这一点上Sentry还没有在浏览器中初始化;我也有同样的疑问!答案在于性能API —— 一组用于测量Web应用程序性能的Web标准 —— 更具体地说是导航计时API,它为浏览器提供了一些非常有用的指标,包括每次事件发生(称为PerformanceEntry
)时的高精度时间戳。
真正酷的是,您还可以直接在浏览器中访问性能API:大多数性能条目都是为任何网页记录的,无需任何设置或额外代码即可检索它们。现在就尝试一下,打开开发工具控制台并输入window.performance
。 您会看到类似这样的东西(我手动分组了相关的时间戳并为更容易解析而排序):
// 从 https://whitep4nth3r.com 捕获于周二 7月30日 @ 13.42
{
"timing": {
"navigationStart": 1722343304923,
"redirectStart": 0,
"redirectEnd": 0,
"fetchStart": 1722343304928,
"domainLookupStart": 1722343304928,
"domainLookupEnd": 1722343304928,
"connectStart": 1722343304928,
"secureConnectionStart": 0,
"connectEnd": 1722343304928
"requestStart": 1722343304988,
"responseStart": 1722343304989,
"unloadEventStart": 0,
"unloadEventEnd": 0,
"responseEnd": 1722343304998,
"domInteractive": 1722343305161,
"domContentLoadedEventStart": 1722343305161,
"domContentLoadedEventEnd": 1722343305161,
"domLoading": 1722343304996,
"domComplete": 1722343305381,
"loadEventStart": 1722343305381,
"loadEventEnd": 1722343305381,
},
}
那么Sentry是如何用这些浏览器跨度填充跟踪的呢?由于性能API从浏览器请求URL的那一刻起就记录了这些指标和时间戳,Sentry JavaScript SDK能够在初始化后访问这些信息,回填发生在网页加载之前的所有事件的完整列表,并将它们作为跨度发送到相关的完整跟踪,以便它们可以在Trace View中可视化。
网页加载前发生了什么?
window.performance
为我们提供了一个窗口(双关语绝对有意),让我们可以看到在浏览器中看到任何网页内容之前发生的许多不同事件。它返回一个Performance
对象,其中包含一个timingproperty
,如上述代码示例所示。虽然这是我们检查浏览器在页面加载时记录的事件的快速方法,而无需编写任何代码,但现在Performance.timing
属性已经弃用,并被PerformanceNavigationTiming API
取代(到目前为止只有一些小变化)。
这张来自导航计时级别2规范的图表显示了从浏览器发出导航请求的那一刻起,到当前文档的加载事件完成时,PerformanceNavigationTiming
事件的记录顺序。并非每次页面加载都会提供所有事件,但顺序与我们使用window.performance
观察到的相匹配。
让我们探索每个相关事件指标,看看幕后发生了什么,以及Sentry如何从特定时间戳计算它来填充Trace View中的浏览器跨度。希望凭借这些新知识,我们将开始理解我们可能如何优化TTFB之前的网络性能。
浏览器缓存
如果资源是通过HTTP GET获取的(例如标准的网页请求),浏览器会首先检查HTTP缓存。fetchStart
返回浏览器开始检查缓存之前的时间。Sentry Trace View中的缓存跨度计算为fetchStart
时间戳和domainLookupStart
时间戳之间的时间。
Trace View中的非零缓存跨度值表示浏览器从浏览器缓存中检索资源所花费的时间。较长的缓存跨度可能指向使用较慢或较旧的浏览器,或者用户不经常(如果有的话)清除他们的浏览器缓存。
浏览器DNS
下一个跨度报告DNS(域名系统)查找时间。当用户请求URL时,会查询DNS以“查找”数据库中的域名并将其转换为IP地址。完成此操作所需的总时间通过从domainLookupEnd
时间戳值中减去domainLookupStart
时间戳值来计算。
浏览器连接
接下来,是时候测量浏览器连接到Web服务器所需的时间了。这被称为“连接协商”,测量为两个事件之间的时间:connectStart
(浏览器开始打开到Web服务器的连接)和connectEnd
(与Web服务器的连接已建立)。
浏览器TLS/SSL
如果浏览器连接的Web服务器使用HTTPS,则会在connectStart
和connectEnd
之间发生secureConnectionStart
事件。secureConnectionStart
标记浏览器和Web服务器交换消息以确认和验证安全加密连接的时间,称为TLS(传输层安全)协商。如果未使用HTTPS或如果连接是持久的,则secureConnectionStart
的值可能为0
。
在Sentry中,连接和TLS事件被报告为单独的跨度。在这张Trace View的图片中,您会注意到连接事件开始,TLS事件紧随其后开始,连接结束事件在TLS协商完成后立即结束。这种事件表示对于能够独立识别Web服务器连接或TLS协商中的任何瓶颈非常有用。
浏览器请求
建立与Web服务器的(安全)连接后,浏览器将正式请求资源,由requestStart
事件标记。
浏览器响应
最后,浏览器将接收内容的第一个字节。在Sentry Trace View中,这里标记了TTFB(第一字节时间)的垂直线。
我们可以使PerformanceNavigationTiming
事件更快吗?
现在我们了解了浏览器接收到网页数据的第一个字节之前发生的事情,让我们更深入地了解我们是否可能加快导航时间线中发生的事件。
你能加快浏览器缓存检索事件吗?
作为一个想要提高自己应用程序性能的开发者,我不确定你能否为你的用户群加快这个事件。然而,你大概可以通过勤于管理你自己的个人浏览器缓存来加快这个事件。像你提交git仓库更改一样经常清除你的浏览器缓存:少量且频繁。
你能加快DNS查找吗?
DNS查找的速度可能会受到许多因素的影响,包括(但不限于):
- DNS提供商的基础设施规模:全球范围内的“存在点”(POP)越少,意味着延迟更长,查找速度更慢,
- POP的位置:如果你的网站访问者远离DNS服务器,DNS查找将需要更长的时间,
- DNS缓存时间:DNS是从缓存中提供的,直到它过期。DNS缓存时间的长度由DNS记录上指定的生存时间(TTL)值决定(它将URL指向IP地址)。TTL越高,浏览器在每次后续请求中需要执行另一次DNS查找的可能性就越小。
最终,加快DNS查找涉及投资于拥有庞大且全球分布的POP网络的DNS提供商。如果你是一家规模化企业的开发人员,这可能已经为你处理好了。此外,对于不经常变化的DNS记录,尽可能高地设置TTL值可能是一个好策略。
在撰写本文时,出于好奇,我检查了我的个人网站的DNS记录,我发现TTL设置为5分钟。这意味着DNS缓存每五分钟过期一次,导致浏览器比需要更频繁地进行新的DNS查找。鉴于我永远不会将我的网站URL指向一个新服务器,我决定将TTL更改为60分钟。
在我个人网站上进行的这次非常有限的5天实验中,自从切换TTL以来,我在Sentry上看到的浏览器DNS查找跨度的非零时间更少了。如果你的网站不是关键任务,也不赚钱,这可能是一个好解决方案,可以帮助加快DNS查找。但请记住,如果你的主要服务器宕机,你想将你的URL指向备份服务器,所有用户看到DNS变更将需要长达60分钟。
话虽如此,根据Sematext的说法,“平均DNS查找时间在20到120毫秒之间。任何在这个范围内的时间都被认为是非常好的。”所以也许这种微优化可能不值得记住你的TTL设置为60分钟,当你需要在主服务器故障期间更改为备份服务器。
使用rel="dns-prefetch"
改善第三方资源的DNS查找
大多数前端网站和应用程序可能至少从第三方加载一个或更多资源,即来自不同域的资源/图像/文件/脚本。对不同域的每个请求也将涉及DNS查找事件。虽然值得注意的是,第三方资源将在TTFB之后被请求,因此在我们这篇文章中关心的PerformanceNavigationTimeline
事件之后,你可以尝试使用属性rel="dns-prefetch"
和关联的href
值在请求资源的<link>
标签上,来加快这些第三方资源的DNS查找。这为浏览器提供了一个提示,用户可能需要从资源的源获取东西,此时浏览器可以尝试通过在资源正式请求之前预先执行DNS解析来改善用户体验。当引入来自Google的第三方字体时,这很有用:
<link rel="dns-prefetch" href="https://fonts.googleapis.com/" />
根据页面加载时并行请求的第三方资源数量,这可能有助于加快responseEnd
事件之后在浏览器中发生的事件,即浏览器开始解析HTML并请求所有第三方资源时(特别是如果它们是阻塞渲染的资源)。
注意:不要在从网站顶级域获取的资源上使用dns-prefetch
(即与你的网站一起托管的资源)。在MDN上阅读更多关于使用dns-prefetch
的信息。
你能加快连接和TLS协商事件吗?
不使用HTTPS?我开玩笑的。关于TLS协商时间的底线是,即使在2010年,谷歌将Gmail切换为所有内容都使用HTTPS之后,TLS被宣布“不再计算密集”。在2013年的出版物高性能浏览器网络中,Ilya Grigorik指出,“……早期的Web通常需要额外的硬件来执行‘SSL卸载。’好消息是,这不再必要,曾经需要专用硬件的事情现在可以直接在CPU上完成。”
Ilya在2013年给出的一条建议是充分利用TLS会话恢复,这是一种机制,用于“恢复或在多个连接之间共享相同的协商密钥数据。”简而言之,这是你的计算机和网站记住彼此的方式,这样它们就不必每次重新连接时都经历检查加密密钥(秘密密码)的漫长过程。这使得浏览速度更快,并且使用更少的计算能力。
除非你直接负责在服务器上实现TLS,否则使TLS协商尽可能快可能是99.999%为你处理好的。然而,就像你可以使用rel="dns-prefetch"
提示浏览器可能需要的资源一样,你可以更进一步,使用rel="preconnect"
在链接外部资源上,这也预先执行部分或全部TLS协商。同样,这将在PerformanceNavigationTiming
事件之后发生,但仍然是一个好情报。
你能加快浏览器请求和响应事件(TTFB)吗?
作为一个开发者,第一字节时间(responseStart
)最终是你在页面导航时间线中最能控制的。注意requestStart
和responseStart
事件之间发生的一切,并在优化这些事件时无情地高效,可以对你的页面速度和最终用户体验产生巨大影响。
在您的网站和应用程序中调查以下三件事:
减少或消除请求瀑布
“请求瀑布”是指在另一个资源请求完成后才开始请求资源(代码、数据、图像、CSS等)的情况。在PerformanceNavigationTimeline
方面,requestStart
事件可能会根据你的网页或应用程序的架构以及浏览器在接收第一字节数据之前发生的同步事件数量,延迟responseStart
事件。我亲身体验了这一点;在我注意到页面加载变得难以忍受地慢之后,我使用Sentry调查了情况,发现每次页面加载都对边缘服务器进行了多次往返。选择完全删除这些即时请求,并将所需数据包含在静态页面构建中,意味着我大幅减少了TTFB约80%。
也许你的应用程序在requestStart
事件时会进行一系列数据库调用。这些查询是否需要按顺序发生,还是可以并行发生?更好的是,你能否用一个查询就从数据库中获取所有需要的数据?如果React是你的东西,看看Lazar关于如何在React中识别获取瀑布的帖子。
更好的是:你真的需要在requestStart
时进行任何即时调用数据库吗?或者你可以效仿我的做法,静态构建你的网页,这样在requestStart
之后所需发生的一切就是从CDN(内容交付网络)快速传递静态页面的HTML?注意:这并不意味着你不能在页面加载后使用客户端JavaScript增强你的网页的交互性并获取新数据。
缓存,缓存,缓存
说到CDN,它使内容可以在物理上更接近访问者的全球边缘服务器上缓存,如果你的网站(或其页面的子集)不是提供个性化和/或动态内容,你应该利用缓存:在请求时存储和交付完整的HTML响应,而不需要在请求时重新生成。作为一个使用现代托管解决方案来交付我的网站,我甚至不需要考虑这个级别的配置的前端开发者,我不会假装是缓存方面的专家。但我将分享来自谷歌文章优化第一字节时间的这一小段信息:
- 对于经常更新内容的网站,即使是短暂的缓存时间也可以为繁忙的网站带来显著的性能提升,因为在那段时间内只有第一个访问者会体验到完整的延迟回到源服务器,而所有其他访问者都可以从边缘服务器重用缓存的资源。
类似于TLS协商,作为2024年的前端开发者,这是我们通常不需要担心的事情;由于我们可以使用的工具,这已经为我们处理好了。说到现代工具,许多前端框架和库现在正在将HTML流传输带给大众。
利用HTML流传输的力量
HTML流传输是指,而不是一次性提供整个HTML文档,服务器会随着时间的推移发送它的片段。浏览器接收这些HTML片段,并可以开始解析它们,甚至渲染它们,这样网页似乎可以更快地加载。而不是在requestStart
和responseStart
事件之间等待接收整个HTML文档,这可能还涉及数据库调用和其他逻辑处理,HTML流传输允许responseStart
事件更早发生,从而减少TTFB。
如果你在React生态系统中工作并想了解更多信息,Lazar在React服务器组件的法医学中深入介绍了HTML流传输。
知识就是力量
所有这些关于浏览器接收到网页数据的第一个字节之前发生的事情的数据是非常有力的。但真正的力量在于将这些数据放在Sentry Trace View的上下文中。能够可视化和跟踪PerformanceNavigationTiming
事件和问题,我们打开了在细粒度级别有效地调试该时间线的慢部分的大门,并在可能的情况下进行所有重要的微优化。
如果你的网页和应用程序已经尽可能快了,希望这篇文章为你提供了一些有用的信息。也许你可以使用你对DNS的新知识在你可能参加的所有酷炫派对上给人们留下深刻印象。
通过这些资源了解更多关于提高你的网站性能的信息: