现代化时代中的HTTP持久连接问题

98
根据haproxy的作者所说,他对http有一些了解:

Keep-alive是为了减少服务器CPU使用率而发明的,当时处理器速度慢100倍。但未说的是,持久连接占用很多内存,除了打开它们的客户端外,没有其他人能使用它们。如今,2009年,处理器非常便宜,而内存由于架构或价格的限制仍然有限。如果一个站点需要keep-alive,则存在真正的问题。高负载的站点通常禁用keep-alive以支持最大数量的并发客户端。没有keep-alive的真正缺点是稍微增加了获取对象的延迟。浏览器会在非持久连接网站上将并发连接数翻倍以弥补这一点。

(来自http://haproxy.1wt.eu/)

这是否符合其他人的经验?也就是说,没有keep-alive,结果现在几乎不会被注意到吗? (值得注意的是,对于非常响应的应用程序,例如WebSockets,无论keep-alive状态如何,都会保持“打开”状态)。 对于远离服务器的人或要从同一主机加载许多工件的情况,效果是否更大? (我认为像CSS、图像和JS这样的东西越来越多地来自缓存友好的CDN)。

你有什么想法?

(不确定这是否是serverfault.com的问题,但在有人告诉我将其移动到那里之前,我不会跨帖子发布)。


1
值得注意的是,在haproxy的其他文档中,保持连接(keep-alive)以更有利的术语提到。我很想听听人们的经验,特别是对于大规模托管的情况。 - Michael Neale
“获取一个设计更好的Web/应用程序服务器”? :-) 新er的设计(如Jetty)使用类似续订的连接处理基本上可以减轻内存/线程问题。此外,“几GB”的听起来像是2008/2009年的服务器术语;-) - user166390
3
对我来说听起来像胡说八道。建立新套接字所涉及的额外RTT是一个物理硬性限制,通常足够长,以至于人类可以检测到,并且无法在已知的物理法则内减少。相反,RAM很便宜,价格正在降低,空闲套接字没有理由使用超过几KB的RAM。 - Will Dean
2
但有趣的是,这不仅是理论 - 这是 haproxy 的作者。我听到的所有其他东西都是理论和假设。 - Michael Neale
4个回答

149

嗨,由于我是这篇引用的作者,所以我来回答:-)

在大型网站上有两个主要问题:并发连接和延迟。慢速客户端导致并发连接,需要花费很长时间才能下载内容,并且由于闲置连接状态而导致。这些闲置连接状态是通过重复使用连接以获取多个对象(称为keep-alive)导致的,这进一步增加了延迟。当客户端非常靠近服务器时,它可以密集地使用连接并确保连接几乎从不闲置。但是当序列结束时,没有人关心快速关闭通道,连接会保持打开和未使用很长一段时间。这就是为什么许多人建议使用非常低的keep-alive超时的原因。在像Apache这样的某些服务器上,您可以设置的最低超时时间为1秒,这通常太长以维持高负载:如果您面前有20000个客户端,并且他们平均每秒获取一个对象,则将永久建立这些20000个连接。像Apache这样的通用服务器上的20000个并发连接量巨大,将需要32到64 GB的RAM,具体取决于加载了哪些模块,并且即使添加RAM也可能无法希望更高。实际上,对于20000个客户端,您甚至可能会看到服务器上的40000到60000个并发连接,因为浏览器将尝试设置2到3个连接(如果他们有许多对象要获取)。

如果在每个对象后关闭连接,则并发连接数量将急剧下降。事实上,它将按照平均下载一个对象所需时间与对象间时间之比的因素下降。如果您需要50毫秒来下载一个对象(缩略图、按钮等),并且如上所述平均每秒下载1个对象,则每个客户端只有0.05个连接,即20000个客户端只有1000个并发连接。

现在建立新连接的时间很重要。远程客户端会遇到不愉快的延迟。过去,浏览器在禁用keep-alive时使用大量并发连接。我记得MSIE上有4个连接,而Netscape上有8个连接。这样会将每个对象的平均延迟减少很多。现在,由于keep-alive无处不在,我们不再看到那么高的数字了,因为这样做会进一步增加远程服务器的负载,而浏览器负责保护互联网基础设施。

这意味着在当今的浏览器中,非keep-alive服务越来越难以获得像keep-alive服务一样的响应能力。此外,一些浏览器(例如:Opera)使用启发式算法来尝试使用管线化。管线化是一种有效地使用keep-alive的方式,因为它通过发送多个请求而无需等待响应来几乎消除了延迟。我曾经在一个包含100张小照片的页面上尝试过,第一次访问比没有keep-alive时快两倍左右,但下一次访问则快了大约8倍,因为响应很小,只有延迟才起作用(只有“304”响应)。

我认为,理想情况下,我们应该在浏览器中设置一些可调节参数,使它们在获取对象之间保持连接,并在页面完成后立即关闭。但不幸的是,我们没有看到这一点。

因此,一些需要在前端安装通用服务器(例如Apache)并且必须支持大量客户端的网站通常需要禁用keep-alive。为了强制浏览器增加连接数,他们使用多个域名来实现下载的并行处理。这在使用SSL的站点上尤其有问题,因为连接设置更高,因为需要额外的往返。

现在更常见的是,这些网站倾向于安装轻量级前端,如haproxy或nginx,它们没有问题处理数万到数十万个并发连接,在客户端启用keep-alive,并在Apache端禁用它。在服务器端,建立连接的成本几乎为零,而且在时间上几乎不可感知。这样可以提供最佳效果:由于客户端的keep-alive具有非常低的超时时间,因此低延迟,同时在服务器端保持较少的连接数。每个人都满意 :-)
一些商业产品通过重复使用前置负载平衡器和服务器之间的连接,并将所有客户端连接多路复用到它们上面来进一步改善这种情况。当服务器靠近LB时,收益不会比以前的解决方案高得多,但通常需要适应应用程序,以确保没有用户会话交叉风险,因为多个用户共享一个连接可能会导致此类问题。理论上不应该发生这种情况。现实情况则大相径庭 :-)

1
非常感谢您提供全面详尽的答案!我曾经因页面上关于保持连接的各种评论而感到有些困惑,但现在一切都变得清晰明了了。 - Michael Neale
有趣的是,我观察到Linux上的Chrome会在保持连接的情况下重复使用连接,时间相当长 - 即打开另一个选项卡所需的时间 - 这个选项卡是到不同的主机名,但通过DNS通配符解析到同一台服务器(大规模虚拟主机) - 因此重用了同一个连接!(这让我感到惊讶,而且不是好的那种 - 显然,如果保持活动只是客户端的话,那就没问题)。 - Michael Neale
我听到的只是“除了Apache,使用任何其他东西都没关系”。我推断出的是“禁用mod_php和Passenger,那么连Apache可能也有一线生机”。 - coolaj86
1
@CoolAJ86:重点绝不是抨击Apache,我个人也在使用它。重点是服务器越通用,可扩展性就越少。有些模块需要预分叉模型,那么你就无法扩展到大量连接。但正如所解释的那样,这并不是什么大问题,因为你可以将其与另一个免费组件haproxy结合使用。在这种情况下,为什么会有人替换所有东西呢?最好安装haproxy,而不是通过重新实现应用程序来使用另一个服务器! - Willy Tarreau

24
自这篇文章发布以来(并在 StackOverflow 上发布),我们现在有了越来越受欢迎的服务器,比如 nginx。
例如,nginx 可以在单个进程中保持打开 10,000 个 keep-alive 连接,并且只需要 2.5 MB 的 RAM。事实上,非常容易用很少的 RAM 保持多千个连接,而你会遇到的唯一限制将是其他限制,例如打开文件句柄或 TCP 连接的数量。
Keep-alive 之所以成为问题,不是因为 keep-alive 规范本身有任何问题,而是因为 Apache 的基于进程的扩展模型和 keep-alive 被植入到一个架构不具备承载它的服务器中。
特别令人困扰的是 Apache Prefork + mod_php + keep-alives。这个模型中,每个连接将继续占用 PHP 进程占用的所有 RAM,即使它完全空闲,仅作为 keep-alive 保持打开状态。这是不可扩展的。但服务器不必设计成这样——没有特别的原因要求服务器必须将每个 keep-alive 连接都保持在单独的进程中(特别是当每个此类进程都有一个完整的 PHP 解释器时)。PHP-FPM 和像 nginx 中那样的基于事件的服务器处理模型优雅地解决了这个问题。
更新 2015 年:
SPDY 和 HTTP/2 用更好的东西替换了 HTTP 的 keep-alive 功能:不仅可以保持连接并在其上发出多个请求和响应,还可以将它们进行多路复用,因此响应可以以任何顺序并行发送,而不仅仅是按照请求的顺序。这可以防止慢速响应阻塞更快的响应,并消除浏览器同时打开多个并行连接的诱惑。这些技术进一步凸显了 mod_php 方法的不足之处以及像基于事件的(或至少是多线程的)Web 服务器与像 PHP-FPM 这样的东西分别耦合的好处。

2
我的理解是这与CPU无关,而是与打开到世界另一端的重复套接字的延迟有关。即使您拥有无限带宽,连接延迟也会减慢整个过程。如果您的页面有数十个对象,则会放大此问题。即使是持久连接也具有请求/响应延迟,但当您有2个套接字时,平均而言,一个应该在流式传输数据,而另一个可以阻塞。此外,路由器永远不会假定套接字连接后才允许您写入它。它需要完整的往返握手。再次强调,我不自称专家,但这就是我一直所看到的。真正酷的是完全异步协议(不,不是完全病态的协议)。

是的 - 那将是我的假设。也许这是一个权衡 - 有一个点,延迟(由于距离)意味着这是一个真正的问题。 - Michael Neale
好的,现代排版会让您连接到附近的代理服务器(也许)。但是,您是否应该将问题扩展到代理服务器是否应该使用持久连接? - catchpolenet
@Michael Neale 另外,由于诸如 TCP 慢启动之类的因素,实际的延迟惩罚比您预期的要严重得多。 - MartinodF
也许权衡的是更短的超时时间。如果您有挤压的请求,为什么要关闭套接字并重新开始呢?即使一秒钟也足以允许页面完全持久加载,然后立即关闭套接字。 - catchpolenet

1
非常长的保持连接时间可以在使用“源拉取”CDN(如CloudFront或CloudFlare)时非常有用。事实上,即使您提供完全动态的内容,这可能比没有CDN更快。
如果您有很长的保持连接时间,以至于每个PoP基本上都与您的服务器建立了永久连接,则用户第一次访问您的站点时,他们可以与本地PoP进行快速TCP握手,而不是与您进行缓慢的握手。(光本身通过光纤大约需要100毫秒,并且建立TCP连接需要传递三个数据包。 SSL需要三个往返。)

1
我本来想点个赞的,但是你第二段有一个错误的说法,即光只需要10毫秒就可以传播到地球的一半。真空中10毫秒的光速是3000公里,在光纤中10毫秒的光速不超过2000公里;地球表面上的一半是20000公里。所以如果你的光纤直接从伦敦到悉尼而不是绕道非洲或者经过夏威夷的话,那么时间应该是100毫秒左右。 - user2127434
@pyramids 你说得对,我可能打错了,或者只是犯了个小错误。我会更新的。 - mjs
从澳大利亚墨尔本或悉尼到美国西海岸(洛杉矶)的往返航班至少需要约160ms,即每段约80ms。这相当不错,考虑到它似乎非常接近理论最佳情况。 - thomasrutter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接