HTTP连接管理学习记录

连接管理目的

HTTP需要进行连接管理,主要是为了维护HTTP连接的性能与表现之间的平衡。HTTP协议经历了几个大版本的升级,协议内容有关连接管理的部分做出了很多的优化改进,这是HTTP协议层面的连接管理;一些优秀的开源网络框架如OkHttp、Volly等等在协议之上又添加了应用层面的连接管理,用来适应复杂多样的业务需求。

通过协议层面的连接管理,我们可以归纳出网络连接管理的核心思想,明确连接管理的目标;通过应用层面的连接管理,我们可以学习到优秀的工程实践,帮助我们落地更高效,更灵活的连接管理方式。

文章接下来的部分,我们会洞察提取每一种连接管理的目的🎯 ,来为设计进一步的连接管理方案提供指导。

HTTP连接管理

HTTP/1.x 连接管理

短连接

这是 HTTP/1.0 的默认模型(如果没有指定 Connection 协议头,或者是值被设置为 close)。而在 HTTP/1.1 中,只有当 Connection 被设置为 close 时才会用到这个模型。

早期HTTP/1.0使用的连接模型。每一个HTTP请求在完成了一次发送Request+收到Response以后,就会断开TCP连接。当下一个连接发起的时候,重新建立TCP连接。

由于建立TCP连接需要三次握手,频繁建立TCP连接在性能方面过于低效,因此短连接是一种在当前非常不建议使用的连接管理模型。

💡 考虑到可能会存在一些非常古老的服务器还在使用HTTP/1.0甚至更早的HTTP协议,因此短连接模型可以在【最大兼容性】情况下开启,而在默认场景下关闭

🎯 短连接是HTTP协议最初的样子,它不存在连接管理,它完全是为了快速占领市场而诞生的

长连接(持久连接)

一个长连接会保持一段时间,重复用于发送一系列请求,节省了新建 TCP 连接握手的时间,还可以利用 TCP 的性能增强能力。这个连接也不会一直保留着:连接在空闲一段时间后会被关闭(服务器可以使用 Keep-Alive 协议头来指定一个最小的连接保持时间)。

HTTP/1.0 里默认并不使用长连接。把 Connection 设置成 close 以外的其他参数都可以让其保持长连接,通常会设置为 retry-after(一段时间后重发请求)。

在 HTTP/1.1 里,默认就是长连接的,不再需要标头(但还是建议加上,以免特殊情况需要回退兼容1.0)。

💡 长连接是当前主流的HTTP协议1.1的默认实现,也是现在使用的最多的连接管理类型。它的管理由服务器端的Keep-Alive字段指定,客户端无法直接参与到长连接的连接管理。但是在服务端资源允许的情况下,客户端可以在应用层实现心跳检测机制来对一些高时效性要求的连接进行保活(WebSocket协议和HTTP/2.0协议有类似实现)

🎯 长连接的目的概况来讲就是:复用;短连接对于性能资源的利用率过低,大量的实例被反复地销毁和创建。这些连接其中有很多是可以重复使用的。因此长连接将短连接中可以重复使用的TCP资源复用,借此达成降低TCP建链带来的性能开销

HTTP流水线

默认情况下,HTTP 请求是按顺序发出的。下一个请求只有在当前请求收到响应过后才会被发出。由于会受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。

流水线是在同一条长连接上发出连续的请求,而不用等待应答返回。这样可以避免连接延迟。理论上讲,性能还会因为两个 HTTP 请求有可能被打包到一个 TCP 消息包中而得到提升。就算 HTTP 请求不断的继续,尺寸会增加,但设置 TCP 的最大分段大小(MSS)选项,仍然足够包含一系列简单的请求。

并不是所有类型的 HTTP 请求都能用到流水线:只有幂等方式,比如 GETHEADPUTDELETE 能够被安全地重试。如果有故障发生时,流水线的内容要能被轻易的重试。

今天,所有遵循 HTTP/1.1 标准的代理和服务器都应该支持流水线,但是实际情况中还是有很多限制:一个很重要的原因是,目前没有现代浏览器默认启用这个特性。

💡 HTTP 流水线在现代浏览器中并不是默认启用的:

  • 代理服务器多数不兼容HTTP流水线,这些会导致 Web 开发人员无法预见网络的表现。
  • 正确的实现流水线是复杂的:传输中的资源大小、多少有效的 RTT 会被用到以及有效带宽都会直接影响到流水线提供的改善。不知道这些的话,重要的消息可能被延迟到不重要的消息后面。因此 HTTP 流水线在大多数情况下带来的改善并不明显。
  • 流水线受制于队头阻塞(HOL)问题。

由于这些原因,流水线已被 HTTP/2 中更好的算法——多路复用(multiplexing)所取代。

🎯 HTTP流水线的目的是通过队列模型解决HTTP请求的等待延迟问题,将串行的请求变为并行来提高效率。但是队列模型无法做到真正意义上的并行,因为HTTP的请求序列化的,服务器的处理还是需要按照顺序进行。这个缺点导致HTTP流水线在实际场合中的应用不高,进而被后续HTTP/2的新特性所替代

并发连接(HTTP/1.x)

为了充分利用网络带宽,提升加载速度,诸如Chromium内核、OkHttp等应用层的程序会在应用层实现HTTP并发,通过多线程,连接池,事件驱动等技术来实现HTTP的并发请求和响应。

当然,HTTP最大并发连接也不是数字越大越好。过多并发同时进行容易导致系统资源被快速耗尽,造成卡顿、发热等问题,影响用户的直接体验;并发较少容易导致网络资源利用不全,加载速度慢,同样也会影响使用体验。

💡 这里的并发连接是应用层的连接,HTTP/1.x的协议本身不支持并发连接,也就是说同一个TCP连接上,后一个HTTP会话要等待前一个HTTP会话结束以后才能开始。而HTTP/2.0在协议层面引入了流这个概念解决了这个问题。

OkHttp中,我们可以通过Dispatcher的方式手动设置允许的最大并发连接数,进而指定合适的策略在不同场景下择优设置并发连接数量

🎯 在HTTP/1.x协议的框架下通过应用层的多线程模型来实现并发连接,本身不能提升HTTP协议的连接表现。其作用更多的在于帮助应用在进行HTTP请求的时候使用更多的网络带宽。但是,这种提升是以牺牲系统资源换来的,过多的线程会导致系统资源加速耗尽,因此浏览器和网络框架会限制此类并发的数量。

域名分片

作为 HTTP/1.x 的连接,请求是序列化的,哪怕本来是无序的,在没有足够庞大可用的带宽时,也无从优化。一个解决方案是,浏览器为每个域名建立多个连接,以实现并发请求。曾经默认的连接数量为 2 到 3 个,现在比较常用的并发连接数已经增加到 6 条。如果尝试大于这个数字,就有触发服务器 DoS 保护的风险。

如果服务器端想要更快速的响应网站或应用程序的应答,它可以迫使客户端建立更多的连接。例如,不要在同一个域名下获取所有资源,假设有个域名是 www.example.com,我们可以把它拆分成好几个域名:www1.example.comwww2.example.comwww3.example.com。所有这些域名都指向同一台服务器,浏览器会同时为每个域名建立 6 条连接(在我们这个例子中,连接数会达到 18 条)。这一技术被称作域名分片。

img

💡 HTTP/1.x的域名分片已经属于过时技术,建议优先升级到 HTTP/2。在 HTTP/2 里,做域名分片就没必要了:HTTP/2 的连接可以很好的处理并发的无优先级的请求。域名分片甚至会影响性能。大多数 HTTP/2 的实现还会使用一种称作连接聚合的技术去尝试合并被分片的域名。

🎯 域名分片的目的是在HTTP/1.x的协议框架下实现HTTP连接的并行处理。通过划分多个不同的域名,将彼此之间没有依赖关系的连接拆分到不同的域名发送请求。这样从整体来看,一系列HTTP请求可以从几个流并行传输。

同时由于大部分浏览器&网络框架限制了一个域名建立的最大HTTP连接数量,因此采用域名分片切换成不同的域名,也是一种“修修补补”的行为。

HTTP/2.0 连接管理

💡 HTTP/2.0引入了很多相比于HTTP/1.x的优化项,但是HTTP/2.0无法向后兼容HTTP/1.x。因此HTTP/2.0需要客户端和服务端配套支持才能正常使用。目前Android客户端常用的OkHttp框架封装了对两个版本的HTTP协议的支持,当发现服务端不支持HTTP/2.0的时候,会降级并使用HTTP/1.1重试请求。

并发连接(HTTP/2.0)

HTTP/2.0引入了的概念,替代了传统的报文。一个帧中可能包含不同会话的不同组成部分,如下图所示:

img

Client向Server发送的是数据流5的DATA部分,Server返回给Client的是数据流1的DATA和数据流3的DATA和Headers部分。当Client收到这些数据后会将他们拆分并组装成完整的报文,这样就实现了在一个连接内并行传输多个数据流的内容。

🎯 HTTP/2.0的并发连接核心之处在于它抛弃了HTTP/1.x的协议框架,设计了全新的协议将HTTP/1.x实现的不完美的异步和并发特性重新实现。

头部压缩

img

在同一个HTTP页面中,许多资源的Header是高度相似的,但是在HTTP2之前都是不会对其进行压缩的,这使得在多次传输中白白浪费了资源来进行重复无谓的操作。

在HTTP/1.x中,头部字段都被表示为字符串,一行一行的首部字段字符串组成首部字段列表。而HTTP/2.0采用了一种头部压缩算法,能够在保证头部内容正常传输的情况下,压缩头部的大小,从而减少带宽浪费,提高HTTP协议的传输效率。

🎯 在协议层面,减少重复信息的传递,降低每一次HTTP请求的带宽消耗。

目的总结🎯

总结上述HTTP协议层面的连接管理的目的,概括出以下几点:

  1. 复用:筛选重复传输的字段、重复创建的实例进行复用,减少资源开销
  2. 异步:采用异步的模型改造系统,提高调度灵活性
  3. 并发:在资源允许的情况下,通过并发的方式充分利用资源