通过预连接,可以提升用户访问体验,并减少服务器性能消耗。本文来自Akamai 网络性能业务部门架构师Utkarsh Goel,他展示了一系列对比测试。LiveVideoStack对本文进行了摘译。
文 / Utkarsh Goel
译 / 王月美
原文 / https://developer.akamai.com/blog/2018/06/25/experiments-with-browser-preconnects/
为什么有此次实验?
现代Web浏览器采用一套性能优化技术来改善用户的体验。预连接提示就是这些优化中的一种,它允许浏览器发现关键主机名,并主动建立连接,以便在不久的将来提供服务请求。在本文中,将讨论Akamai基础架构收集的几个大型数据集和一些实验室内所做的实验,以及从中观察到的通过预连接提示建立的连接的某些特征。此次工作的主要发现是:
介绍
现代网页利用数十个主机名来下载数百种资源。对于这些资源中的每一个,浏览器都会对其TCP高速缓存执行快速查找,以检查是否已经存在与相关主机名的连接,以及该连接是否可用。如果TCP连接不可用,浏览器将对其DNS缓存执行查找,以检查相关主机名是否存在DNS条目。如果DNS和TCP条目在缓存中均不可用,浏览器将执行DNS查找并建立新的TCP连接,然后在需要的地方进行TLS握手。当DNS条目和连接尚不可用时,页面加载时间可能会增加,尤其是需要加载位于网页关键路径上的资源时。
为了避免这些预备任务在网页的关键路径上发生,许多Web开发人员使用preconnect 提示,让浏览器执行DNS查找,并在提示可用时立即与主机建立TCP / TLS会话。一个好的Web开发实践会在所请求的主页 HTML的HTTP响应头中发送preconnect提示,例如
HTTP/1.1 200 OK Link: <https://www.foundry.systems>; rel=preconnect;
或者嵌入在HTML中的标签中
<link rel="preconnect" href="https://www.foundry.systems" />
以上示例中,只要上述提示可用于支持预连接提示的浏览器,浏览器就会执行DNS查找并建立与www.foundry.systems的连接,即使没有待处理的HTTP请求下,也是如此。
接收preconnect提示并不是网络浏览器预先连接到主机名的唯一原因。例如,Chrome有一个内置的预测机制,可以学习用户导航的网页结构,并在用户导航到页面后立即对各种主机名进行推测性预连接。例如,如果预测器知道用户以前访问过页面https://www.example.com/index.html需要来自img.example.com和css.example.com的资源,则下次用户导航至相同页面时,浏览器甚至可以在发现要在这些连接上下载的资源之前主动建立与img.example.com和css.example.com的连接。本文中,我将讨论通过Web开发人员预连接提示或Web浏览器推测性预连接提示建立连接的某些特性。
未使用的Preconnects(预连接)
在某些情况下,主动建立的连接不会被浏览器用来发送任何HTTP请求。 可能是因为以下四种情况中的任一种:
1. 预测器建议根据用户以前的导航来打开与主机的连接,但网页已更改,而且也不需要主动连接的主机名中的任何资源。
2. HTTP请求被取消,而建立的连接仍未使用。
3. 准备好发送请求并且浏览器开始为其建立连接,但是在连接建立完成之前,与同一主机的其他一些连接变为可用并且请求在该连接上进行传输。
4. 浏览器可能不记得服务器是否支持HTTP/2,因此它以HTTP/1.1方式打开多个并行连接,但在协商HTTP/2后仅使用了其中一个。
未使用的Preconnects(实验 )
鉴于以上针对未使用的预连接的情况,接下来我研究了Chrome(版本64)在闲置一段时间后如何处理此类连接。 出于实验的目的,我设置了三个测试页面,来指示浏览器预先连接到主机并在不同时间间隔后在该主机上加载资源。
#1
在第一个测试页https://dev.utkarshgoel.in/preconnect.html中,我在HTML <head>标签中添加了一个预连接提示,以连接到一个支持HTTP /2的主机www.foundry.systems。请注意,此页面在HTML中没有其他内容。我在加载页面时,在后台运行Wireshark实例显示Chrome为www.foundry.systems建立了TCP和TLS握手。我也在后台chrome://net-internals/#http2进行了捕获。然而,该连接没有在网络内部注册为HTTP/2连接,并且网络内部没有显示在连接上发送的SETTINGS帧。
#2
在第二个测试页https://dev.utkarshgoel.in/preconnect_with_delayed_request.html中,在<head>标签中,我为www.foundry.systems添加了一个preconnect提示以及一个阻止页面上任何其他JS执行5秒钟的外部JS。在HTML的body中,我添加了一个带有空src属性的 img标记。然后HTML有一个内联JS,它将图像的src属性设置为指向www.foundry.systems的图像。此实验的目的是在连接建立好五秒后,从www.foundry.systems加载资源。
在这个实验中,我发现Wireshark捕获与之前的实验类似,Chrome为www.foundry.systems建立了一个TCP和一个TLS会话,但这次net-internals将连接注册为HTTP/2连接。然而,该连接在建立5秒后出现在net-internals,此时正发送SETTINGS帧。
这次与之前的实验表明,只有在连接上发送HTTP请求后,Chrome才会发送HTTP/2 SETTINGS帧(因为这标志着HTTP/2连接的开始)。
#3
在第三个测试页面https://dev.utkarshgoel.in/preconnect_with_delayed_request_12s.html中,我克隆了第二个测试页面,并修改了外部JS,以阻止任何其他JS执行12秒。与第二个实验类似,我发现net-internals的连接在12秒后被注册。然而,在Wireshark捕获图中,我观察到不是一个,而是两个连接正在建立。如下面的屏幕截图所示,两个连接大约间隔12秒:
在为外部JS加载具有不同阻塞值的测试页面后,我发现Chrome丢弃了在建立后的前10秒内未使用连接的任何连接状态。在我的实验中,内联JS在预连接后12秒加载了图像,因此因为超过了10秒的限制,所以Chrome建立了一个新的连接。
因此,一个建议是确保当preconnect 提示显示的目标是消除网页关键路径中的DNS和TCP / TLS握手时,浏览器必须能够在10秒内发现需要该连接的资源。
实验中的另一个观察是,即使当客户端第一次连接到服务器时,服务器发送了TLS会话票据;当客户端第二次连接服务器时,客户端也不会在其clientHello中公布会话票据。要观察此情况,请查看下面屏幕截图中的第二个红色框,突出显示clientHello中公布的会话票证的大小。
#4
上述实验操作促使我设置了第四个实验,在测试页面(https://dev.utkarshgoel.in/preconnect_with_repeated_delayed_requests.html)中,我克隆了第三个测试页面,并在HTML body中添加了外部JS和内联JS 。第二个外部JS的目的是阻止第二个内联JS执行额外的70秒,因为这是我发现Chrome通过net-internals终止先前的HTTP /2连接所花费的时间。总结一下,页面加载的流程如下:
HTML load -> Preconnect -> wait 12 seconds -> Reconnect -> load image -> wait 70+ seconds -> Reconnect -> load image
如上方的屏幕截图所示,运行这样的实验会导致建立三个连接。在Wireshark捕获中,我看到Chrome仅在第三个clientHello(如红色框中所示)中公布了会话票证。这表明只有在上一次在连接上发送HTTP请求时,会话票证才从缓冲区中拉出/传递到上层。
未使用连接产生的通信流量
在了解了Chrome主动打开的连接的上述特征之后,我好奇服务器在接收未用于提供任何HTTP请求的连接请求的频率大小。这是非常重要的,因为如果浏览器打开太多这样的连接,则可能会给服务器带来太多的负担。对于每个TLS会话,无论是否提供请求,服务器都必须执行CPU密集型公钥加密操作。为了找到上述问题的答案,我研究了建立在Akamai分布式基础设施上的用于内容传送的超过170万个TCP连接的统计数据。最后,我发现多达6%的TLS连接从不用于HTTP请求。
使用预连接
虽然在少数情况下主动建立的连接不被使用,但在大多数情况下,这些连接还是用于提供HTTP请求的。但是,由于预连接发生在页面导航的早期,并且浏览器可能需要一段时间才能发现连接上发送的请求,所以我也有兴趣来研究连接建立完成时和第一个HTTP请求到达服务器之间的时间间隔。
这个时间间隔将告诉我们主动建立的连接在建立之后仍然闲置的时间长度。
图1.在不同主机名中观察到的时间间隔的箱形图分布。 为了观察清晰,我只绘制了200个主机名的图。
使用的预连接:实验情况
在此分析中,我使用了500多个Akamai边缘服务器来收集Chrome浏览器通过HTTP/2连接生成的,超过730万个HTTP请求的统计信息。图1中的x轴显示了在200个主机名中观察到的时间间隔分布。y轴以毫秒为单位显示时间间隔。如图所示,在主动建立的连接上,在连接建立后的中值情况下,第一个HTTP请求可能会在四秒内到达服务器。但是,对于大多数主机名,第一个请求在连接建立结束后大约50毫秒内到达。如此大的时间间隔相当于有线宽带网络中的多次往返以及少快速移动网络中更少的往返次数。此外,我发现此行为仅适用于与嵌入在HTML中的子资源关联的主机名。
此外,由于HTTP/2协议不允许服务器在客户端在连接上发出第一个请求之前推送任何内容,因此服务器缺乏对此类时间间隙采取行动来改进性能的能力。从理论上讲,人们可以利用实验性的未绑定服务器推送方案,在连接空闲时来推送关键资源。 但是,如上一节所示,Chrome无法读取这些空闲连接上传入的数据,因此,如果不更改Chrome处理网络套接字的方式,则无法使用该技术。
结论
虽然此项研究有许多次要和主要的发现,但我会强调一些我认为对于web开发人员来说最重要的:
对于与HTML上的子资源相关联的大多数主机名建立的连接,连接在建立完成后仍保持约50毫秒空闲。提交给IETF的实验性未绑定服务器推送方案可能是利用连接保持空闲的时间的一种方式。
Utkarsh Goel是Akamai 网络性能业务部门的架构师,他喜欢通过使用构建技术来改善Web性能的当前状态。他还是Akamai Foundry的成员,并专注于探索新 技术以改善各种形式的互联网性能。
感谢Akamai的Mike Bishop,Moritz Steiner和Stephen Ludin为本文的早期版本提供了一些研究思路和反馈意见。
本文分享自 LiveVideoStack 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!