无论你是做前端、后端还是运维,HTTP都是你不得不面对的网络协议。它是最常用的应用层协议,其优化不仅可以通过降低延迟带来更好的体验,还可以通过降低资源消耗带来更高的并发。但是刚学HTTP的同学很难把HTTP的所有优化点都说完。这可能是因为你没有为各大厂商的面试准备好,或者你可能没有加入一个快速发展的项目。当产品的用户数量不断翻倍时,需求会倒逼你去优化HTTP协议。本文是根据我在2019GOPS全球运维大会上海站演讲的PPT,对文字进行重新提炼后的总结。我希望从四个新的维度涵盖大部分HTTP优化技术。这样,即使你不需要极端的方法来解决当前的性能瓶颈,你也会知道优化的方向在哪里。需求来了,你可以去谷歌搜索资料。第一个维度是在编码效率方面更快地将消息转换为更短的字符流。这是最直接的性能优化点。1.编码效率的优化如果你对HTTP/1.1协议做过抓包分析,你会发现它是以“whitespace-delimited”的方式编码的。用空格和回车编码是因为HTTP在诞生之初就追求可读性,更有利于它的推广。但是目前这种低效的编码方式已经严重影响了性能,所以2009年谷歌推出了基于二进制的SPDY协议,大大提高了编码效率。2015年稍作改进后确定为HTTP/2协议,目前超过50%的网站都在使用。这是编码优化的大方向,包括即将到来的HTTP/3。但是这些新技术如何提高性能呢?那我们就要拆开来看了,我们先从数据压缩说起。抓包看到的是数据,不是信息。数据实际上是信息和冗余数据的总和,而压缩技术就是尽可能去除冗余数据。压缩分为无损压缩和有损压缩。对于图片、音频和视频,我们每天都在和有损压缩打交道。例如,当浏览器只需要缩略图时,无需浪费带宽传输高分辨率图像。但是,高清视频经过有损压缩后,在肉眼无法分辨的情况下,压缩了上千倍。这是因为声音和视频都可以逐步压缩。还记得以前的VCD吗?当光盘被划伤时,整张光盘无法播放,因为当时的视频已经被增量压缩,关键帧太少,所以当关键帧损坏时,后面的所有增量帧都无法播放。让我们再看看无损压缩。你一定用过gzip,它可以让httpbody实现无损压缩。压缩后的信息肉眼看全是乱码,但接收端解压后,可以看到发送端的原文。不过gzip的效率其实并不高。如果你将它与谷歌推出的brotli进行比较,你就会知道它的缺陷:在评估压缩算法时,我们关注两个指标:压缩率和压缩速度。从上图可以看出,无论使用gzip的9个压缩级别中的哪一个,其压缩率都低于brotli(相比gzip,压缩级别也可以配置为10),并且压缩速度也较慢。所以,如果可以的话,您应该尽快更新您的gzip压缩算法。说完body的压缩,我们再来看HTTPheader的压缩。对于HTTP/1.x,标头是性能杀手。尤其是现在cookie泛滥的时代,每个请求都要携带好几个KB的header,对带宽、CPU、内存都是一种浪费!HTTP2采用HPACK技术,编码后头部的大小大大减小,这也是HTTP3的演进方向。HPACK是如何实现头部压缩的?HPACK通过哈夫曼算法、静态表和动态表对这三个表头进行压缩。例如上图中,方法GET存在于静态表中,可以用1个字节表示的整数2表示;user-agentMozilla这一行的header很长,第二次出现时,用2个字节表示,整数62就够了;即使是第一次出现,也可以利用哈夫曼算法对Mozilla超长的浏览器标识符进行压缩,可以获得高达5/8的压缩率。静态表中只存储最常见的header,有的只有name,有的既有name,也有value。静态表的大小有限,目前只有61个元素。增量编码的思想应用到动态表中,即第一次出现时,将其添加到动态表中,第二次出现时,只传输其在动态表中的序号.Huffman编码在winrar等压缩软件中被广泛使用,但是HPACK中的Huffman不同,它使用的是静态huffman编码。也就是统计了互联网上几年的HTTP头,根据每个字符出现的概率,重建哈夫曼树。这样,按照规则,出现次数最多的字符a、c、e或者1、2、3只用5位来表示,很少出现的字符用几十位来表示。说完header,再来看httpbody的编码。这里仅举三个例子:1.只有几十个字节的小图标不需要用独立的HTTP请求传输。根据RFC2397的规定,它们可以直接嵌入到HTML或CSS文件中,浏览器解析它们就会被识别,就像下图中的头像:2.JS源码中可能有很多小文件文件,这些文件中也有很多空行和注释。通过WebPack工具,先在服务器端打包成一个文件,然后去掉多余的字符,编码效果也很好。3、在表单中,可以一次传输多个元素,如复选框、文件等。这减少了HTTP请求的数量。可见http协议从header到body都有很多编码方式,可以使传输的报文更短,这样既节省了带宽,又减少了延迟。编码效率优化后,再来看“通道”。这虽然是通信领域的词汇,但是非常适合总结HTTP的优化点,所以在这里借用一下。2.信道利用率优化信道利用率包括3个优化点,第一个优化点是复用!在高速低电平通道上,可以运行许多低速高电平通道。比如主机上只有一张网卡,但是浏览器、微信、钉钉可以同时收发消息;一个进程可以同时服务数以万计的TCP连接;一个TCP连接可以同时传输多个HTTP2STREAM消息。其次,为了让通道有更高的利用率,必须及时修复错误。因此,TCP的很大一部分工作就是及时发现丢包和乱序数据包,并快速处理。最后,与经济学一样,资源总是稀缺的。在有限的带宽下,如何公平对待不同的连接、用户和对象?比如在下载一个页面的时候,如果CSS和图片以相同的优先级下载,就会出现问题。后面显示图片没关系,但是如果没有获取到CSS,页面就无法显示。另外,在传输报文时,包头中并不携带目标信息,但却是必不可少的。如何减少这些控制信息的比例呢?让我们从多路复用开始。从广义上讲,多线程和协程都是多路复用,但这里我主要指的是http2的stream。因为http协议的设计是客户端先发送请求,服务器可以回复响应。这样发送和接收消息就不会占满带宽。最高效的方式是发送端不断发送请求,接收端??不断发送响应,这对于又长又胖的网络尤其有效:这就是HTTP2流式多路复用连接的方式。我们知道chrome最多可以同时建立6个连接到一个站点,但是使用HTTP2,只需要一个连接就可以高效地传输页面上的数百个对象。我特意让我的个人网站www.taohui.pub同时支持HTTP1和HTTP2。下图从连接的角度展示了HTTP2和HTTP1的区别。熟悉chromeNetwork网络面板的同学一定对waterfall不陌生。它可以帮你分析HTTP请求哪里慢了,是发送请求慢,接收响应慢,还是解析太慢。下图是我的网站从瀑布的角度对比。从这两张图可以看出,HTTP2在各个方面都优于HTTP1。让我们看看网络错误的恢复。在应用层,lingering_time通过延迟关闭连接来防止浏览器因为RST错误而收不到http响应,而timeout则使用定时器及时发现错误并释放资源。在传输层,通过使用timestamp=1,TCP可以更准确地测量定时器的超时时间RTO。当然,timestamp还有一个目的,就是防止序列号在长胖网络中回绕。什么是序列号包装?我们知道每条TCP报文都有一个序号,序号不是指报文的先后顺序,而是已经发送的字节数。由于它是一个32位整数,因此它最多可以处理232或4.2GB的动态消息。如上图所示,当1G-2G的包在网络中飞的时间过长,就会和5G-6G的包重叠,造成错误。网络错误有很多种,比如报文的顺序不能保证。启用tcp_sack可以减少乱序时的重发包数量,减少带宽消耗。使用Chrome浏览器直接下载大文件时,网络不好的情况下,所有错误都要重传,体验很差。用迅雷下载要快很多。这是因为迅雷将大文件拆分成很多小块,可以多线程下载,每个小块失败后,再下载小块,效率很高。这种断点续传和多线程下载技术就是HTTP的Range协议。如果你的服务是一个缓存,你也可以使用Range协议。例如,Nginx的Slice模块就是这样做的。事实上,最复杂的网络错误恢复算法是拥塞控制,它可以提高整体网络性能。有同学会问,TCP没有流量控制,为什么还会出现网络拥塞?这是因为TCP链路中各个路由器的处理能力不匹配。如上图所示,R1的峰值网络为700M/s,R2的峰值网络为600M/s。他们都需要通过R3到达R4。但是R3的最大带宽只有1000M/s!当R1和R2中的TCP全速使用各自的带宽时,R3会造成丢包。拥塞控制是为了解决丢包问题。自1982年TCP诞生以来,一直沿用传统的拥塞控制算法。检测到丢包后刹车减速,效果很差。为什么?可以观察下图,路由器中有一个缓冲队列,当队列为空时,ping延迟最短;当队列快满时,ping延迟很大,但没有丢包;当队列满时,会发生丢包。所以,当队列积压时,并没有发生丢包。此时虽然峰值带宽不会减少,但是网络延迟会增加,应该避免。当队列刚好有积压时,测量驱动的拥塞控制算法开始制动并减速。在如今内存越来越便宜,队列越来越大的时代,新算法尤其有效。当Linux内核更新到4.9版本后,原来的CUBIC拥塞控制算法被谷歌的BBR算法所取代。从下图可以看出,当丢包率达到0.01%时,CUBIC无法使用,BBR也没有问题,直到丢包率达到5%,BBR的带宽急剧下降。再来看资源的均衡分配。为了公平对待连接和用户,服务器会进行限速。例如下图中的LeackyBucket算法,可以平滑流量的突然增加,更公平地分配带宽。另一个例子是HTTP2中的优先级功能。一个页面上有成百上千个对象,这些对象的重要性各不相同,有的相互依赖。例如,某些JS文件将包含jQuery.js。如果一视同仁,即使先下载了前者,也无法使用。HTTP2允许浏览器在下载对象时根据解析规则设置流中每个对象的权重优先级(255最大,0最小)。每个代理和资源服务器将根据优先级分配内存和带宽,以提高网络效率。最后看TCP的包效率,这也会影响上面的HTTP性能。例如,启用Nagle算法后,网络中的小包数量大大减少。考虑到40字节的包头,信息占比更高。Cork算法类似于Nagle算法,但它对小数据包的控制更为积极。Cork和Nagle控制来自发送端的小包,而quickack控制来自接收端的pureack小包的数量,以增加信息的比例。说完相对微渠道,我们再从宏观的角度来看第三个优化点:传输路径的优化。3、传输路径优化传输路径的第一个优化点是缓存。在浏览器、CDN、负载均衡等组件中,缓存无处不在。您可能熟悉缓存的基本用法。这里只讲过期缓存的用法。直接丢弃过期的缓存很浪费,因为“过期”是由客户端的定时器决定的,并不代表资源真的失效了。所以,你可以把它的标识带到源服务器,由服务器判断缓存是否还有效。如果有效,只返回304和一个空体,这样可以节省带宽。对于负载均衡,过期缓存也可以保护源站,限制回源请求。当源服务器挂掉的时候,也可以给用户带来缓存过期的降级服务体验,比返回503要好很多。传输路径的第二个优化点是慢启动。系统自带的TCP协议栈会逐渐提高传输速度,以避免瓶颈路由器丢包。它的启动速度称为初始拥塞窗口。早期的初始拥塞窗口是1MSS(通常是576字节),后来改为3MSS(Linux2.5.32),后来在Google的建议下改为10MSS(Linux3.0)。之所以不断增加启动窗口,是因为随着互联网的发展,网页越来越丰富,越来越大。如果初始窗口太小,下载第一个网页的时间会更长,体验差。当然,修改初始窗口非常简单。下图为Linux下调整窗口的方法。修改启动窗口是一种常用的性能优化方法。例如,CDN厂商更改了启动窗口。下图是2014年和2017年主流CDN厂商的启动窗口大小,可以看出有些窗口在2014年调整过大,2017年又收回了,所以并不是初始窗口越大越好,会增加瓶颈路由器的压力。让我们看看如何在传输路径上从拉模式升级到推模式。例如index.html文件中包含在HTTP/1中,在下载some.css之前必须先下载index.html,也就是两次RTT的时间。但在HTTP/2中,服务器可以通过两个流并行传输index.html和some.css,节省了一半的时间。事实上,当丢包发生时,HTTP2流的并行传输会严重退化,因为TCP的队头阻塞问题还没有解决。上图中的SPDY相当于HTTP2。当红色和绿色三个流并发传输时,??TCP层仍然会被序列化。假设先发送红色流,如果红色报文丢失,即使接收端收到了完整的蓝色和绿色流,TCP也不会交给HTTP2,因为TCP本身必须保证数据包在命令。这样并发性没有保障,就是队头阻塞的问题。队头阻塞的解决方案是绕过TCP,使用UDP协议实现HTTP。例如,Google的GQUIC协议就是这样做的。B站几年前用它来提供服务。UDP协议本身不能保证可靠传输,所以GQUIC需要在UDP之上重新实现TCP已经完成的功能。这是HTTP的发展方向,所以HTTP3目前是在GQUIC的基础上制定标准的。最后,从网络信息安全的角度,谈谈如何做优化。它实际上与编码、信道、传输路径有关,但实际上是一个独立的环节,所以放在最后讨论。4.信息安全优化互联网世界的信息安全始于1995年的SSL3.0,到现在很多大型网站已经更新到2018年推出的TLS1.3,TLS1.2有什么问题?最大的问题是它支持现在不安全的古老密钥协商协议。比如2015年出现的FREAK中间人攻击,可以使用亚马逊上的虚拟机分分钟攻击支持旧算法的服务器。针对这种情况,TLS1.3取消了在当前计算能力下不再具有数学安全性的非对称密钥协商算法。在Openssl的最新实现中,仅支持5个安全套件:TLS1.3的另一个优势是握手速度。在TLS1.2中,由于需要两次RTT来协商密钥,因此诞生了sessioncache和sessionticket这两个工具。它们都将协商密钥的握手减少到一个RTT。但是,这两种方法都不能应对重放攻击。TLS1.2中的安全套件协商和ECDHE公钥交换两步在TLS1.3中合并为一步,大大提高了握手速度。如果你还在使用TLS1.2,尽快升级到1.3,除了安全,还有性能上的好处。总结HTTP的性能优化方法有很多。从这四个维度出发,可以建立一个树状的知识体系,覆盖绝大部分的HTTP优化点。编码效率优化包括httpheader和body,可以使传输的数据更短更紧凑,从而获得更低的延迟和更高的并发。同时,一个好的编码算法也可以减少编解码时的CPU消耗。信道利用率的优化可以从多路复用、错误检测与恢复、资源分配三个角度入手,让快的底层信道有效承载慢速的应用层信道。传输路径的优化,包括各级缓存、慢启动、消息传输方式等,可以让消息更及时地发送到浏览器,提升用户体验。当前互联网中的信息安全主要基于TLS协议。TLS1.3在安全性和性能上有了很大的提升,要及时升级。希望这些知识可以帮助大家全面高效地优化HTTP协议!
