您现在的位置是:首页 >技术交流 >【HTTP/1.1、HTTP/2、HTTP/3】网站首页技术交流
【HTTP/1.1、HTTP/2、HTTP/3】
简介【HTTP/1.1、HTTP/2、HTTP/3】
文章目录
HTTP/1.1 如何优化?
三种优化思路来优化 HTTP/1.1 协议:
尽量避免发送 HTTP 请求;
在需要发送 HTTP 请求时,考虑如何减少请求次数;
减少服务器的 HTTP 响应的数据大小;
避免发送HTTP请求
- 对于一些具有重复性的 HTTP 请求,比如每次请求得到的数据都一样的,我们可以把这对「请求-响应」的数据都
缓存在本地
,那么下次就直接读取本地的数据。
如何实现缓存的呢?
- 客户端会把第一次请求以及响应的数据保存在本地磁盘上,其中将请求的 URL 作为 key,而响应作为 ,value,两者形成映射关系。后续请求时,先在本地磁盘上通过 key 查到对应的 value。
缓存不是最新的?
- 在请求的 Etag 头部带上第一次请求的响应头部中的摘要,这个摘要是唯一标识响应的资源,当服务器收到请求后,会将本地资源的摘要与请求中的摘要做个比较。
- 如果不同,服务器在响应中带上最新的资源。
- 如果相同,说明客户端的缓存还是可以继续使用的,服务器仅返回不含有包体的 304 Not Modified 响应,告诉客户端仍然有效
减少HTTP次数
减少 HTTP 请求次数自然也就提升了 HTTP 性能,可以从这 3 个方面入手:
- 减少重定向请求次数;
- 合并请求;
- 延迟发送请求;
减少重定向
- 服务器上的一个资源可能由于迁移、维护等原因从 url1 移至 url2 后,而客户端不知情,它还是继续请求 url1,此时服务器就会出错。
重定向的工作交由代理服务器完成,就能减少 HTTP 请求次数了
合并请求
- 把多个访问小文件的请求合并成一个大的请求,虽然传输的总资源还是一样,但是减少请求,也就意味着
减少了重复发送的 HTTP 头部。
延迟发送请求
- 请求网页的时候,没必要把全部资源都获取到,而是只获取当前用户所看到的页面资源,当用户向下滑动页面的时候,再向服务器获取接下来的资源,这样就达到了延迟发送请求的效果。
- HTTP/1.1 是请求响应模型,如果第一个发送的请求,未收到对应的响应,那么后续的请求就不会发送
- 于是为了防止单个请求的阻塞,所以
一般浏览器会同时发起 5-6 个请求,每一个请求都是不同的 TCP 连接,那么如果合并了请求,也就会减少 TCP 连接的数量,因而省去了 TCP 握手和慢启动过程耗费的时间。
- 于是为了防止单个请求的阻塞,所以
减少 HTTP 响应的数据大小
- 对于 HTTP 的请求和响应,通常 HTTP 的响应的数据大小会比较大,也就是服务器返回的资源会比较大。于是
资源进行压缩:
- 无损压缩;
- 有损压缩;
无损压缩
- 无损压缩是指资源经过压缩后,信息不被破坏,还能完全恢复到压缩前的原样,适合用在文本文件、程序可执行文件、程序源代码。
- HTTP 请求中通过头部中的 Accept-Encoding 字段告诉服务器:
'请求头部'
Accept-Encoding: gzip, deflate, br
'响应头部'
Content-Encoding: gzip
有损压缩
- 解压的数据会与原始数据不同但是非常接近。
- 将次要的数据舍弃,牺牲一些质量来减少数据量、提高压缩比,这种方法经常用于压缩多媒体数据,比如音频、视频、图片。
HTTP/2
HTTP/1.1性能问题
现在网站比之前变化太多了
消息的大小变大了
,从几 KB 大小的消息,到几 MB 大小的消息;页面资源多了
,从每个页面不到 10 个的资源,到每页超 100 多个资源;内容形式多样了
,从单纯到文本内容,到图片、视频、音频等内容;实时性高了
,对页面的实时性要求的应用越来越多;
给HTTP/1.1带来高延迟的主要原因
延迟难以下降
,虽然现在网络的「带宽」相比以前变多了,但是容易到达延迟的下限;并发连接有限
,谷歌浏览器最大并发连接数是 6 个,而且每一个连接都要经过 TCP 和 TLS 握手耗时,以及 TCP 慢启动过程给流量带来的影响;队头阻塞问题
,同一连接只能在完成一个 HTTP 事务(请求和响应)后,才能处理下一个事务;HTTP 头部巨大且重复
,由于 HTTP 协议是无状态的,每一个请求都得携带 HTTP 头部,特别是对于有携带 Cookie 的头部,而 Cookie 的大小通常很大;不支持服务器推送消息
,因此当客户端需要获取通知时,只能通过定时器不断地拉取消息,这无疑浪费大量了带宽和服务器资源。
HTTP/2兼容HTTP/1.1
- HTTP/2 没有在 URI 里引入新的协议名,仍然用「http://」表示明文协议。但是在
浏览器和服务器
背后自动升级协议 - 只在应用层做了改变,还是基于 TCP 协议传输。
HTTP/2 把 HTTP 分解成了「语义」和「语法」两个部分
,「语义」层不做改动
,与 HTTP/1.1 完全一致,比如请求方法、状态码、头字段等规则保留不变。HTTP/2 在「语法」层面做了很多改造
,基本改变了 HTTP 报文的传输格式
HTTP/2的性能优化
头部压缩
- HTTP/1.1的协议报文是由「Header + Body」构成的,对于 Body 部分,「Content-Encoding」指定 Body 的压缩方式,比如用 gzip 压缩,可以节约带宽。
但是Header部分没有进行优化
- HTTP/1.1 报文中 Header 存在的问题:
- 含很多固定的字段,比如 Cookie、User Agent、Accept 等(压缩)
- 大量的请求和响应的报文里有很多字段值都是重复的(避免重复)
- 字段是 ASCII 编码的,但效率低(二进制编码)
- HTTP/2 没使用 gzip 压缩方式来压缩头部,而是开发了
HPACK 算法
,HPACK 算法主要包含三个组成部分:- 静态字典;
- 动态字典;
- Huffman 编码(压缩算法);
客户端和服务器两端都会建立和维护「字典」,用长度较小的索引号表示重复的字符串,再用 Huffman 编码压缩数据
静态表编码
静态表+ Huffman 编码
index
表示索引,header Value
表示key
对应的value
(Index 为 2 代表 GET,Index 为 8 代表状态码 200。)- 表中有的 Index 没有对应的 Header Value, 因为这些 Value 是变化的,这些 Value 都会经过 Huffman 编码后,才会发送出去。
动态表编码
- 静态表只包含了 61 种高频出现在头部的字符串,但是有些不再静态表中的头部字符串就需要自己
构建动态表
,它的 Index 从 62 起步,会在编码解码的时候随时更新。
- 比如,第一次发送时头部中的「User-Agent 」字段数据有上百个字节,经过 Huffman 编码发送出去后,客户端和服务器双方都会更新自己的动态表,添加一个新的 Index 号 62。
那么在下一次发送的时候,就不用重复发这个字段的数据了,只用发 1 个字节的 Index 号就好了,因为双方都可以根据自己的动态表获取到字段的数据。
- 使得动态表生效有一个前提:
必须同一个连接上,重复传输完全相同的 HTTP 头部
总结
- HTTP/2 头部的编码通过「静态表、动态表、Huffman 编码」共同完成的。
二进制帧(重点)
- 将 HTTP/1 的
文本格式改成二进制格式
传输数据,极大提高了 HTTP 传输效率,而且二进制数据使用位运算能高效解析。
- HTTP/2 把响应报文划分成了两类帧(Frame),图中的
HEADERS(首部)和 DATA(消息负载)
是帧的类型,也就是说一条 HTTP 响应,划分成了两类帧来传输,并且采用二进制来编码。
- 在 HTTP/2 对于状态码 200 的二进制编码是 10001000,只用了 1 字节就能表示
- 最前面的 1 标识该 Header 是静态表中已经存在的 KV。
- 静态表内容,“:status: 200 OK”其静态表编码是 8,即 1000。因此,整体加起来就是 10001000
- HTTP/2 二进制帧的结构如下图:
并发传输
- HTTP/1.1 的实现是基于请求-响应模型的。同一个连接中,HTTP 完成一个事务(请求与响应),才能处理下一个事务。如果响应不来,那就会造成
队头阻塞问题
- HTTP/2 通过 Stream 这个设计,
多个 Stream 复用一条 TCP 连接,达到并发的效果
,解决了 HTTP/1.1 队头阻塞的问题,提高了 HTTP 传输的吞吐量。
- HTTP/2 中的
Stream、Message、Frame
这 3 个概念。
- 1 个 TCP 连接包含一个或者多个 Stream,Stream 是 HTTP/2 并发的关键技术;
- Stream 里可以包含 1 个或多个 Message,Message 对应 HTTP/1 中的请求或响应,由 HTTP 头部和包体构成;
- Message 里包含一条或者多个 Frame,Frame 是 HTTP/2 最小单位,以二进制压缩格式存放 HTTP/1 中的内容(头部和包体);
总结:
多个 Stream 跑在一条 TCP 连接,同一个 HTTP 请求与响应是跑在同一个 Stream 中,HTTP 消息可以由多个 Frame 构成, 一个 Frame 可以由多个 TCP 报文构成。
- 在 HTTP/2 连接上,
不同 Stream 的帧是可以乱序发送的
(因此可以并发不同的 Stream ),因为每个帧的头部会携带 Stream ID 信息,所以接收端可以通过 Stream ID 有序组装成 HTTP 消息,而同一 Stream 内部的帧必须是严格有序的。
- 同一个连接中的 Stream ID 是不能复用的,只能顺序递增,所以当 Stream ID 耗尽时,需要发一个控制帧 GOAWAY,用来关闭 TCP 连接。
当 HTTP/2 实现 100 个并发 Stream 时,只需要建立一次 TCP 连接,而 HTTP/1.1 需要建立 100 个 TCP 连接,每个 TCP 连接都要经过 TCP 握手、慢启动以及 TLS 握手过程,这些都是很耗时的。
服务器主动推送资源
- HTTP/1.1 不支持服务器主动推送资源给客户端,都是由客户端向服务器发起请求后,才能获取到服务器响应的资源。
- 比如,客户端通过 HTTP/1.1 请求从服务器那获取到了 HTML 文件,而 HTML 可能还需要依赖 CSS 来渲染页面,这时客户端还要再发起获取 CSS 文件的请求,需要两次消息往返
- 在 HTTP/2 中,客户端在访问 HTML 时,服务器可以直接主动推送 CSS 文件,减少了消息传递的次数。
推送的实现
- 客户端发起的请求,必须使用的是奇数号 Stream,服务器主动的推送,使用的是偶数号 Stream。服务器在推送资源时,会通过
PUSH_PROMISE 帧传输 HTTP 头部,并通过帧中的 Promised Stream ID 字段告知客户端
,接下来会在哪个偶数号 Stream 中发送包体。
HTTP/2问题
- HTTP/2 通过 Stream 的并发能力,解决了 HTTP/1 队头阻塞的问题。
但是 HTTP/2 还是存在“队头阻塞”的问题,只不过问题不是在 HTTP 这一层面,而是在 TCP 这一层。
原因
- HTTP/2 是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用,那么当「前 1 个字节数据」没有到达时,后收到的字节数据只能存放在内核缓冲区里,只有等到这 1 个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据,这就是 HTTP/2 队头阻塞问题。
HTTP/3不再使用TCP协议,而是使用UDP 协议作为传输层协议
总结
第一点:对于常见的 HTTP 头部通过
静态表和 Huffman 编码的方式,将体积压缩了近一半,而且针对后续的请求头部,还可以建立动态表,将体积压缩近 90%,大大提高了编码效率,同时节约了带宽资源。- 不过,动态表并非可以无限增大, 因为动态表是会占用内存的,动态表越大,内存也越大,容易影响服务器总体的并发能力,因此服务器需要限制 HTTP/2 连接时长或者请求次数。
第二点:HTTP/2 实现了 Stream 并发
,多个 Stream 只需复用 1 个 TCP 连接,节约了 TCP 和 TLS 握手时间,以及减少了 TCP 慢启动阶段对流量的影响。不同的 Stream ID 可以并发,即使乱序发送帧也没问题,但是同一个 Stream 里的帧必须严格有序。第三点:服务器支持主动推送资源
,大大提升了消息的传输性能,服务器推送资源时,会先发送 PUSH_PROMISE 帧,告诉客户端接下来在哪个 Stream 发送资源,然后用偶数号 Stream 发送资源给客户端。
HTTP/3
HTTP/2的性能问题
- 队头阻塞;
- TCP 与 TLS 的握手时延迟;
- 网络迁移需要重新连接;
队头阻塞
- HTTP/2 多个请求是跑在一个 TCP 连接中的,那么当 TCP 丢包时,整个 TCP 都要等待重传,那么就会阻塞该 TCP 连接中的所有请求。
- 因为 TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且有序的,如果序列号较低的 TCP 段在网络传输中丢失了,即使序列号较高的 TCP 段已经被接收了,应用层也无法从内核中读取到这部分数据。只有当丢失的数据包进行重传接收后,应用层才可以从内核中读取数据
TCP 与 TLS 的握手时延迟
- 发起 HTTP 请求时,需要经过 TCP 三次握手和 TLS 四次握手(TLS 1.2)的过程,因此共需要 3 个 RTT 的时延才能发出请求数据。
- TCP 由于具有「拥塞控制」的特性,所以刚建立连接的 TCP 会有个「慢启动」的过程,它会对 TCP 连接产生“减速”效果。
网络迁移需要重新连接
- 一个 TCP 连接是由四元组(源 IP 地址,源端口,目标 IP 地址,目标端口)确定的,这意味着如果 IP 地址或者端口变动了,就会导致需要 TCP 与 TLS 重新握手,比如 4G 网络环境切换成 WiFi。无论应用层的 HTTP/2 在怎么设计都无法逃脱,要解决这个问题,就必须把
传输层协议替换成 UDP
。
QUIC 协议
- UDP 是一个简单、不可靠的传输协议。UDP 是不需要连接的,也就不需要握手和挥手的过程,所以天然的就比 TCP 快。
- HTTP/3 不仅仅只是简单将传输协议替换成了 UDP,还基于 UDP 协议在「应用层」实现了 QUIC 协议,它具有类似 TCP 的连接管理、拥塞窗口、流量控制的网络特性,相当于将不可靠传输的 UDP 协议变成“可靠”的了,所以不用担心数据包丢失的问题。
- QUIC协议特点:
- 无队头阻塞;
- 更快的连接建立;
- 连接迁移;
无队头阻塞
- QUIC 协议也有类似 HTTP/2 Stream 与多路复用的概念。也是可以在同一条连接上并发传输多个 Stream,Stream 可以认为就是一条 HTTP 请求。
- QUIC 使用的传输协议是 UDP,UDP 不关心数据包的顺序,数据包丢失也不关系
QUIC 协议会保证数据包的可靠性
- 每个数据包都有一个序号唯一标识。当某个流中的一个数据包丢失了,即使该流的其他数据包到达了,数据也无法被 HTTP/3 读取,直到 QUIC 重传丢失的报文,数据才会交给 HTTP/3。
但是与HTTP/2不同的是,HTTP/2中只要有一个stream数据丢失,那么其他流都会受影响,而HTTP/3多个 Stream 之间并没有依赖,都是独立的,某个流发生丢包了,只会影响该流,其他流不受影响。
更快的连接建立
- HTTP/1 和 HTTP/2 协议,TCP 和 TLS 是分层的,分别属于内核实现的传输层,OpenSSL 库实现的表示层,因此需要先 TCP 握手,再 TLS 握手。
- HTTP/3 在传输数据前虽然需要 QUIC 协议握手,这个握手过程只需要 1 RTT,握手的目的是为确认双方的「连接 ID」
连接迁移
- 基于 TCP 传输协议的 HTTP 协议,由于是通过四元组(源 IP、源端口、目的 IP、目的端口)确定一条 TCP 连接。
- 当移动设备的网络从 4G 切换到 WiFi 时,意味着 IP 地址变化了,那么就必须要断开连接,然后重新建立连接,而建立连接的过程包含 TCP 三次握手和 TLS 四次握手的时延,以及 TCP 慢启动的减速过程
- QUIC 协议使用连接 ID 来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己。即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”复用原连接
- 但是 HTTP/3 的 QUIC 协议并不是与 TLS 分层,而是 QUIC 内部包含了 TLS,它在自己的帧会携带 TLS 里的“记录”,再加上 QUIC 使用的是 TLS 1.3,因此仅需 1 个 RTT 就可以「同时」完成建立连接与密钥协商,甚至在第二次连接的时候,应用数据包可以和 QUIC 握手信息(连接信息 + TLS 信息)一起发送,达到 0-RTT 的效果。
将TCP和TLS握手整合在一起了,所以只需要1RTT
- 如果需要恢复加密通信,只需要使用加密参数就可以了,直接是0RTT
HTTP/3协议
- HTTP/3 协议在 HTTP 这一层做了什么变化。
- HTTP/3 同 HTTP/2 一样采用二进制帧的结构,不同的地方在于 HTTP/2 的二进制帧里需要定义 Stream而 HTTP/3 自身不需要再定义 Stream,直接使用 QUIC 里的 Stream,于是 HTTP/3 的帧的结构也变简单了。
- HTTP/3 在头部压缩算法使用 QPACK,与 HTTP/2 中的 HPACK 编码方式相似,也采用了静态表、动态表及 Huffman 编码。
- 静态表的变化,HTTP/2 中的 HPACK 的静态表只有 61 项,而 HTTP/3 中的 QPACK 的静态表扩大到 91 项。
- 动态表编解码方式不同。QPACK 通过两个特殊的单向流来同步双方的动态表
- Huffman 编码相同
总结
QUIC 协议的特点:
无队头阻塞
,QUIC 连接上的多个 Stream 之间并没有依赖,都是独立的,也不会有底层协议限制,某个流发生丢包了,只会影响该流,其他流不受影响;建立连接速度快
,因为 QUIC 内部包含 TLS 1.3,因此仅需 1 个 RTT 就可以「同时」完成建立连接与 TLS 密钥协商,甚至在第二次连接的时候,应用数据包可以和 QUIC 握手信息(连接信息 + TLS 信息)一起发送,达到 0-RTT 的效果。连接迁移
,QUIC 协议没有用四元组的方式来“绑定”连接,而是通过「连接 ID 」来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以复用原连接
另外 HTTP/3 的 QPACK 通过两个特殊的单向流来同步双方的动态表,解决了 HTTP/2 的 HPACK 队头阻塞问题。
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。