谈谈你对HTTP1.X的Keep-Alive参数的理解,它和TCP的keepalive一样么?

Posted 大帅的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了谈谈你对HTTP1.X的Keep-Alive参数的理解,它和TCP的keepalive一样么?相关的知识,希望对你有一定的参考价值。


本篇文章大概2500字,阅读时间大约5分钟


本文主要是对文章的一个收尾。总结了HTTP的keep-alive参数的作用,以及在HTTP/2的解决方案。

谈谈你对HTTP1.X的Keep-Alive参数的理解,它和TCP的keepalive一样么?

HTTP属于应用层协议,但是常常听到名词“HTTP Keep-Alive",它指的是对长连接和短连接的选择。如下是HTTP1.X下的报文格式,在HTTP请求的首部行,可以设置该参数,并且在HTTP1.1已经默认开启了,并且可以省略该字段:

谈谈你对HTTP1.X的Keep-Alive参数的理解,它和TCP的keepalive一样么?

长连接:指在一个连接上可以连续发送多个数据包,在连接保持期间,如果没有数据包发送,需要双方发心跳包维持连接状态。

短连接:指通讯双方有数据交互时就建立一个连接,数据发送完成后则断开连接

 

而TCP的keepalive机制是在ESTABLISH状态时,双方如何检测连接的可用性。HTTP的keep-alive说的是如何避免进行重复的TCP三次握手和四次挥手的环节,它的意图在于连接的复用——同一个连接上串行方式传递请求-响应数据,它和TCP的心跳不一样。

 

如果HTTP服务器配置了这个Keep-Alive参数,且客户端发来的请求头里Connection为Keep-Alive,那么该客户端和服务器之间会保持长连接,而且HTTP/1.1默认就是长连接了,不需要请求头带这个属性,甚至说现在大多数应用的HTTP版本都是1.1,或者是HTTP2了,比如gRPC是HTTP2,所以一般用户不需要操心它的配置。相反如果Connection为Close,那么说明是短连接,而短连接在大量请求的背景下会频繁打开和关闭TCP链路,这非常耗费时间,正是基于这个背景,HTTP协议才实现了“长连接”的通信方式,也叫“持久连接”(persistent connections)、“连接保活(keep alive)。其核心思想就是“成本均摊”,既然TCP的连接和关闭非常耗时,那就把这个时间成本由原来的一个“请求-应答”均摊到多个“请求-应答”,整体传输效率也就提高了。由于长连接对性能的改善效果非常显著,所以在HTTP/1.1中的连接都会默认启用长连接。只要向服务器发送了第一次请求,后续的请求都会重复利用第一次打开的TCP连接,在这个连接上收发数据。即不管客户端是否显式要求长连接,如果服务器支持长连接,它总会在响应报文里放一个"Connection:keep-alive字段告诉客户端,自己支持长连接。另外,客户端和服务器都可以在报文里附加通用头部字段"Keep-Alive:timeout=value”,限定长连接的超时。但这个字段的约束力并不强,通信的双方可能并不会遵守,所以不太常见。

 

不论HTTP1.X如何改进,它都规避不了一个缺陷,即它默认是一发一收模型,即请求按照发送的顺序被先进先出的排队处理,HTTP不管你的请求哪个优先级高,它只按照顺序处理,即客户端发请求A,B,服务端响应了A,才能响应B,客户端收了A,才能收B。这样的处理流程本质是同步调用,即发送某个请求的线程必须等待对端的响应正常回来后才能继续执行,否则阻塞。而且此时还会有队头阻塞问题,可能影响服务器性能。即按照顺序发送的请求序列中的某一个请求因为某种原因迟迟收不到响应,导致后面排队的所有请求也一并被阻塞,最终导致客户端迟迟收不到后面的响应报文。通过分析也能知道,队头阻塞与短连接和长连接无关,而是由HTTP请求-应答模型所导致,本质是半双工的通信模型,HTTP服务端不能主动推送数据给客户端。


一句话:一个长链接同一时间只能发送一个HTTP请求,等收到服务器响应以后才能被别的请求复用。在TCP层也有一样的问题,即每个TCP分组都会带着一个唯一序号被发出,而所有分组必须按顺序传到接收端。如果中途有一个分组没能到达接收端,那么后续分组必须保存到接收端的缓冲区,等待丢失的分组重发并到达接收端。这一切都发生在TCP层,应用程序对TCP重发和缓冲区中排队的分组一无所知,必须等待分组全部到达才能访问数据。在此之前,应用程序只能在通过套接字读数据时感觉到延迟交互。这种效应称为TCP的队首阻塞。即队头阻塞在http和tcp层都有,原因不同。http的队头阻塞是因为它的请求-应答模式,当然它运行在tcp上,就有两种队头阻塞。

 

因为HTTP的请求-应答模型不能变,所以队头阻塞问题在HTTP/1.1里无法解决,只能缓解,哪怕使用HTTP的流水线模式也有一样的问题,不过可以结合连接池复用去减缓它的影响。一方面可以结合I/O多路复用大幅度减少线程数,一方面让线程都忙碌起来,而不是阻塞在网络I/O调度上,但这种方式也存在缺陷。如果每个客户端都想自己快,建立很多个连接,用户数X并发数就会是个天文数字。服务器的资源根本扛不住,或者被服务器认为是恶意攻击,反而会造成拒绝服务。所以HTTP协议建议客户端使用并发,但不能滥用并发。RFC2616里明确限制每个客户端最多并发2个连接。不过实践证明这个数字实在是太小了,众多浏览器都无视标准,把这个上限提高到了6~8。后来修订的RFC7230也就“顺水推舟”,取消了这个“2”的限制。但“并发连接”所压榨出的性能也跟不上高速发展的互联网无止境的需求,还有什么别的办法吗?

 

为此又诞生了域名分片(domain sharding)技术,还是用数量来解决质量的思路。即多开几个域名,让这些域名都指向同一台服务器,这样实际长连接的数量就又上去了。


但是以上都是治标不治本,最终极的解决方案是HTTP/2协议,HTTP/2在语法上完全兼容了HTTP1系列,并且将数据传输格式改为了二进制格式,即用户看不懂的二进制序列,优点是大大的方便了计算机的识别和解析,无歧义,速度快,体积小。也就是说HTTP/2的请求响应报文不再是传统的那种有什么请求,响应行,空行等的文本格式了。它将TCP协议的一些特性拿到了应用层,将head+body的消息打乱为一个个的二进制的小帧,化整为零。如此一来,使用一个虚拟的“流”的概念来传输数据,同一个消息往返的帧被标记位一个流的唯一ID,这些ID组装起来就说一HTTP1.X里的请求报文和响应报文,只不过它们的顺序被打乱,结构被打散。实际上这个流不存在,即表现的都是一个个的二进制帧,并没有流这个数据结构,只不过这些帧的ID都一样而已。这样的设计就能让HTTP/2客户端同时的发送多个请求,这些请求的传输形式是帧,它们被分成了多个虚拟的流(本质是多个帧)来传输,接收端只需要解析这些帧的ID(流ID),就能拼接出完整的请求,多个请求和响应之间也就没有了顺序,不需要排队等待,也就消除了队头阻塞问题,也不需要长连接,HTTP流水线等机制了,这也叫所谓的HTTP/2的多路复用技术,即多个往返的帧可以复用一个连接,这和I/O多路复用技术是两码事,I/O多路复用是多路网络连接可以复用一个I/O线程。另外,基于这些特性,还能实现HTTP服务器的主动推送,即HTTP/2服务器能新建虚拟的流,来主动向客户端发消息,因为它消除了传统的请求报文和响应报文的限制。


END


点亮在看,你最好看

~

阅读原文,获得更多精彩内容

以上是关于谈谈你对HTTP1.X的Keep-Alive参数的理解,它和TCP的keepalive一样么?的主要内容,如果未能解决你的问题,请参考以下文章

http1.X与2.0

谈谈Http长连接和Keep-Alive以及Tcp的Keepalive

Vue 怎么缓存当前的组件?缓存后怎么更新?说说你对keep-alive的理解是啥

谈谈你对广播的理解?

谈谈你对组件式GIS认识

什么是http2的多路复用