基础-http2厉害在哪里


HTTP1.1协议的性能问题

现在站点相比以前的变化:

  • 消息的大小变大了
  • 页面资源变多了
  • 内容形式变多样了
  • 实时性要求变高了

这些变化带来的最大性能问题就是HTTP1.1的高延迟,延迟高必然影响的就是用户体验,主要原因如下:

  • 延迟难以下降:虽然带宽变大,但延迟降到一定程度后,很难继续下降
  • 并发连接有限:谷歌浏览器最大并发连接是6个,而且每个链接都要经过TCP和TLS握手耗时,以及TCP慢启动过程给流量带来的影响
  • 对头阻塞问题:同一连接只能在完成一个HTTP请求和响应后,才能处理下一个事务
  • HTTP头部巨大且重复:由于HTTP协议是无状态的,每个请求都得携带HTTP头部,特别是对于有携带Cookie的头部,而Cookie的大小通常很大
  • 不支持服务器推送消息:当客户端需要获取通知时,只能通过定时器不断地拉取消息,浪费了大量带宽和服务器资源

为了解决HTTP1.1性能问题,举几个例子:

  • 图片合并
  • 图片Base64编码,减少网络请求
  • js文件合并
  • 同一个页面的资源分散到不同域名

因为这些都是对HTTP1.1的外部优化,关键点如请求响应模型,头部巨大且重复,并发连接耗时,服务器不能主动推送等,于是设计了HTTP2.

兼容HTTP1.1

  1. HTTP2没有在URI里引入新的协议名,仍然用http://表示明文https://表示加密,所以只需要浏览器和服务器在背后自动升级协议,在用户无感的前提下实现平滑升级。
  2. 只在应用层做改变,还是基于TCP协议传输,应用层方面为了保持功能上的兼容,HTTP2吧HTTP分解成语义和语法两个部分,语义成不做改动,比如请求方法、状态吗、头字段等规则保持不变。
  3. 语法层做了很多改造,基本改变了HTTP报文的传输格式

头部压缩

HTTP报文室友Header和Body构成的,对于Body部分,HTTP1.1协议用头字段Content-Encoding指定Body的压缩方式,比如用gzip压缩,节约带宽,但是没有对Header的优化手段。

HTTP1.1报文Header部分存在的问题:

  • 含有很多固定字段,比如Cookie,User Agent,Accept等,这些字段加起来高达几百甚至几千字节。需要压缩
  • 大量请求和响应的报文里有很多字段是重复的,这样会使大量贷款被这些冗余数据占用。需要避免重复
  • 字段是ASCII编码的,人易读但效率低。需要改成二进制编码

HTTP2没有使用常见的gzip压缩方式压缩头部,而是开发了HPACK,HPACK主要包含三个部分:

  • 静态字典
  • 动态字典
  • Huffman编码(压缩算法)

客户端和服务端都会建立和维护字典,用长度较小的索引号表示重复的字符串,再用Huffman编码压缩数据,可以达到50%-90%的高压缩率

静态表编码

HTTP2为高频出现在头部的字符串和字段建立一张静态表,它是写在HTTP2框架里的,不会变化,共61组。表中有些Index没有对应的Value,因为这些Value并不是固定的,这些Value经过Huffman编码后才会发送出去。由于基于二进制编码,所以头部不需要\r\n作为分隔符,于是改用字符串长度ValueLength来分割Index和Value

img

动态表编码

静态表值只包含61种高频出现的头部字符串,不在静态表范围内的头部字符串就要自行构建动态表,它的Index从62起步,会在编码解码时随时更新。

比如第一次发送头部中的User-Agent字段有上百个字节,经过Huffman编码发送出去后,客户端和服务端都会更新自己的动态表,添加一个姓的Index号62.那么在下一次发送的时候,就不用重复发送这个字段数据了,只用发送一个字节的Index,双方都可以根据自己的动态表获取到字段数据

所以使动态表生效有一个前提,必须同一个链接上,重复传输完全相同的HTTP头部。如果消息字段只被发送了一次,或者重复传输室字段总是变化,动态表就无法被充分利用了。

动态表越大,占用的内存也就越大,如果占用太多内存,是会影响服务器性能的,因此Web服务器都会提供类似http2_max_requests的配置,用于限制一个连接上能够传输的请求数量,避免动态表无限增大,请求数量达到上限后,就会关闭HTTP2来释放内存。

二进制帧

HTTP2厉害的地方在于将HTTP1的文本格式改成二进制传输数据,极大提高了HTTP传输效率,而且二进制数据使用位运算能高效解析。

HTTP/1 与 HTTP/2

HTTP2把响应报文划分为两类帧,Header首部和Data消息负载,也就是说一条HTTP响应,划分为两类帧传输,并采用二进制编码。

HTTP二进制帧结构:

image-20240105143208962

并发传输

HTTP1.1是基于请求-响应模型的。同一个链接中,HTTP完成一个事务,才能处理下一个事务,也就是说在发出请求等待响应的过程中,没办法继续做其他事情,后续请求无法发送,造成对头阻塞。

HTTP2通过Stream,多个Stream复用一条TCP连接,达到并发效果。

  • 一个TCP连接包含多个Stream
  • Stream里可以包含一个或多个Message,对应HTTP1中的请求或响应,由HTTP头部和包体构成
  • Message里包含一条或多个Frame,Frame是HTTP2最小单位,以二进制压缩格式存放内容

不同Stream的帧可以乱序发送,因此可以并发不同的Stream,因为每个帧头部会携带StreamID,所以接收端可以通过StreamID有序组装HTTP消息,而同一个Stream内部的帧必须是严格有序的。

客户端和服务端都可以建立Stream,客户端建立Stream必须是奇数号,服务器必须是偶数号。同一个链接StreamID不能复用,只能递增。

Nginx中,通过http2_max_concurrent_streams配置来设置Stream上限,默认128个。

HTTP2还可以对Stream设置优先级,比如客户端访问HTML和图片资源时,希望服务端先传递HTTP再传图片,那么就可以设置Stream的优先级来实现,提高用户体验。

服务器主动推送资源

在Nginx中,如果希望用户访问/text.html时,服务器可以直接推送css文件,减少消息传达次数。可以配置:

location /test.html {
    http2_push /test.css
}
image-20240105143355912

  目录