• HTTP复习(二)


    一、HTTP/1.1的优化

    1、尽量避免发送 HTTP 请求

    避免HTTP请求并不意味着就不进行交互,而是使用缓存技术来减少通过网络获得服务器的响应。

    HTTP缓存两种
    (1)强制缓存
    (2)协商缓存
    缓存部分已经总结过了,可以看这个链接(建议先看HTTP复习(一)再看本篇文章)→HTTP复习(一)

    2、减少HTTP请求次数

    (1)减少重定向请求次数

    什么是重定向请求?
    服务器上的一个资源可能由于迁移、维护等原因从 url1 移至 url2 后,而客户端不知情,它还是继续请求 url1,这时服务器不能粗暴地返回错误,而是通过 302 响应码Location 头部,告诉客户端该资源已经迁移至 url2 了,于是客户端需要再发送 url2 请求以获得服务器的资源。
    1、性能最差:
    在这里插入图片描述
    2、提高一定性能:
    在这里插入图片描述
    3、让代理服务器知道要重定向:
    在这里插入图片描述

    (2)合并请求

    如果把多个访问小文件的请求合并成一个大的请求,虽然传输的总资源还是一样,但是减少请求,也就意味着减少了重复发送的 HTTP 头部。为了防止单个请求的队头阻塞问题,所以一般浏览器会同时发起 5-6 个请求,每一个请求都是不同的 TCP 连接,那么如果合并了请求,也就会减少 TCP 连接的数量,因而省去了 TCP 握手和慢启动过程耗费的时间。

    合并请求的方式就是合并资源,以一个大资源的请求替换多个小资源的请求。

    但是这样的合并请求会带来新的问题,当大资源中的某一个小资源发生变化后,客户端必须重新下载整个完整的大资源文件,这显然带来了额外的网络消耗。

    (3)延迟发送请求

    不要解析html文件时候,遇到一个资源的URL就发送HTTP请求请求该资源。我们可以按需请求,用到了我们再去请求。不用到就不用
    在用户看来,浏览一个网页时候,往下滑动页面时才会加载新内容,而不是一上来就全部加载出来。

    3、如何减少 HTTP 响应的数据大小?

    通过压缩数据,减少HTTP响应报文的大小

    (1)无损压缩

    无损压缩是指资源经过压缩后,信息不被破坏,还能完全恢复到压缩前的原样,适合用在文本文件、程序可执行文件、程序源代码

    客户端支持的压缩算法,会在 HTTP 请求中通过头部中的 Accept-Encoding 字段告诉服务器。

    压缩后,通过响应头部中的 content-encoding 字段告诉客户端该资源使用的压缩算法。

    (2)有损压缩

    有损压缩主要将次要的数据舍弃,牺牲一些质量来减少数据量、提高压缩比,这种方法经常用于压缩多媒体数据,比如音频、视频、图片

    可以通过 HTTP 请求头部中的 Accept 字段里的「 q 质量因子」,告诉服务器期望的资源质量。

    4、提高并发连接数

    将同一个页面的资源分散到不同域名,提升并发连接上限,因为浏览器通常对同一域名的 HTTP 连接最大只能是 6 个;

    二、HTTP/2 强在哪里?

    1、头部压缩

    HTTP/1.1存在的问题:
    1、报文Header部分上,每一个报文都有完全相同的字段,每个都传输一样的值,会有浪费
    2、有一些Header字段是固定的
    3、字段是ASCII编码,方便人看了,但是对于计算机没啥用,还浪费空间,改成二进制编码。

    HTTP/2 使用了 HPACK算法 ,其中主要包含三部分
    1、静态字典
    2、动态字典
    3、哈夫曼编码(压缩算法)
    客户端和服务器两端都会建立和维护「字典」,用长度较小的索引号表示重复的字符串,对于动态变化的部分,再用 Huffman 编码压缩数据。

    (1)静态表编码

    在这里插入图片描述

    (2)动态表编码

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

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

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

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

    (3)哈夫曼编码

    大体就是根据不同字母出现次数的不同,构建二叉树,保证每个字母都能用最短的不重复的比特位表示,从而达到压缩的效果。

    总结,具体如何压缩 Header Name: Header Value
    (1)Header Name 和 Header Value都存在
    Index(一字节)
    (2)Header Name存在 和 Header Value不存在
    Index(一字节) + 表示Header Value长度字节(首bit位表示Value部分进行哈夫曼编码)+Header Value编码的后的值
    (3)都不存在
    将整体编码,记录在动态表
    在这里插入图片描述

    2、二进制帧

    将HTTP/1.1的文本格式变成了二进制格式传输数据,极大提高了HTTP传输效率。并且二进制数据使用位运算能高效解析。
    在这里插入图片描述
    一个HTTP请求或者响应报文成为一个Message。

    而一个Message由两类Frame(Header Frame & Data Frame),一个Message可以有多个Header Frame和多个Data Frame中。

    Header Frame的Payload承载的是经过HPACK算法加工后的报头数据
    Data Frame的Payload承载的是经过HPACK算法加工后的报文数据
    在这里插入图片描述
    帧头(Frame Header)很小,只有 9 个字节
    帧开头的前 3 个字节表示帧数据(Frame Playload)的长度。
    帧类型,HTTP/2 总共定义了 10 种类型的帧,一般分为数据帧和控制帧两类,如下表格:
    在这里插入图片描述
    标志位,可以保存 8 个标志位,用于携带简单的控制信息。如:

    • END_HEADERS 表示头数据结束标志,相当于 HTTP/1 里头后的空(“\r\n”);
    • END_Stream 表示单方向数据发送结束,后续不会再有数据帧。
    • PRIORITY 表示流的优先级;

    流标识符(Stream ID):最高位被保留不用。用来标识该 Frame 属于哪个 Stream,接收方可以根据这个信息从乱序的帧里找到相同 Stream ID 的帧,从而有序组装信息。
    帧数据:存放经过HPACK算法压缩过的HTTP报头或者数据(具体由帧类型决定)

    3、并发传输

    为了解决HTTP的队头阻塞的问题,设计了Stream
    在这里插入图片描述

    • 一个TCP链接包含一个或者多个Stream;
    • Stream包含一个或多个Message,Message 对应 HTTP/1 中的请求或响应,由 HTTP 头部和包体构成;
    • Message 里包含一条或者多个 Frame,Frame 是 HTTP/2 最小单位,以二进制压缩格式存放 HTTP/1 中的内容(头部和包体);

    那是如何做到并发的呢?
    一个TCP链接中的多个Stream进行单向传输。 客户端建立的 Stream 必须是奇数号,而服务器建立的 Stream 必须是偶数号。同一个连接中的 Stream ID 是不能复用的,只能顺序递增,所以当 Stream ID 耗尽时,需要发一个控制帧 GOAWAY,用来关闭 TCP 连接。在 Nginx 中,可以通过 http2_max_concurrent_Streams 配置来设置 Stream 的上限,默认是 128 个。

    不同Stream之间可以乱序传输,只需要通过Stream ID,即可区分到底来源于哪一个Stream。

    但是Stream内部每一个Frame必须是有序的,一旦一个Frame阻塞,那么这个Stream就会发生阻塞。但是不会影响其他Stream。

    HTTP/2 通过 Stream 实现的并发,比 HTTP/1.1 通过 TCP 连接实现并发要牛逼的多,因为当 HTTP/2 实现 100 个并发 Stream 时,只需要建立一次 TCP 连接,而 HTTP/1.1 需要建立 100 个 TCP 连接,每个 TCP 连接都要经过TCP 握手、慢启动以及 TLS 握手过程,这些都是很耗时的。

    4、服务器主动推送资源

    HTTP/1.1每次都要客户端主动发起链接请求新资源。HTTP/2时候,服务器就可以主动发送给客户端其需要的内容。
    在这里插入图片描述

    那 HTTP/2 的推送是怎么实现的?
    客户端发起的请求,必须使用的是奇数号 Stream,服务器主动的推送,使用的是偶数号 Stream。服务器在推送资源时,会通过 PUSH_PROMISE 帧传输 HTTP 头部,并通过帧中的 Promised Stream ID 字段告知客户端,接下来会在哪个偶数号 Stream 中发送包体。
    在这里插入图片描述

    三、HTTP/3

    1、HTTP/2 的缺陷

    (1)TCP的队头阻塞
    (2)TCP和TLS握手带来的延迟
    (3)网络迁移的重新连接

    2、QUIC协议的特点

    HTTP/3 不仅仅只是简单将传输协议替换成了 UDP,还基于 UDP 协议在「应用层」实现了 QUIC 协议,它具有类似 TCP 的连接管理、拥塞窗口、流量控制的网络特性,相当于将不可靠传输的 UDP 协议变成“可靠”的了,所以不用担心数据包丢失的问题。

    QUCI的优点:
    (1)无队头阻塞
    (2)更快的链接建立
    (3)连接迁移

    (1)无队头阻塞

    QUIC 协议也有类似 HTTP/2 Stream 与多路复用的概念,也是可以在同一条连接上并发传输多个 Stream,Stream 可以认为就是一条 HTTP 请求。

    由于 QUIC 使用的传输协议是 UDP,UDP 不关心数据包的顺序,如果数据包丢失,UDP 也不关心。

    不过 QUIC 协议会保证数据包的可靠性,每个数据包都有一个序号唯一标识。当某个流中的一个数据包丢失了,即使该流的其他数据包到达了,数据也无法被 HTTP/3 读取,直到 QUIC 重传丢失的报文,数据才会交给 HTTP/3。

    而其他流的数据报文只要被完整接收,HTTP/3 就可以读取到数据。这与 HTTP/2 不同,HTTP/2 只要某个流中的数据包丢失了,其他流也会因此受影响。

    所以,QUIC 连接上的多个 Stream 之间并没有依赖,都是独立的,某个流发生丢包了,只会影响该流,其他流不受影响。

    (2)更快的连接建立

    HTTP/3 在传输数据前虽然需要 QUIC 协议握手,这个握手过程只需要 1 RTT,握手的目的是为确认双方的「连接 ID」,连接迁移就是基于连接 ID 实现的。

    但是 HTTP/3 的 QUIC 协议并不是与 TLS 分层,而是QUIC 内部包含了 TLS,它在自己的帧会携带 TLS 里的“记录”,再加上 QUIC 使用的是 TLS1.3,因此仅需 1 个 RTT 就可以「同时」完成建立连接与密钥协商,甚至在第二次连接的时候,应用数据包可以和 QUIC 握手信息(连接信息 + TLS 信息)一起发送,达到 0-RTT 的效果。

    HTTP/3 当会话恢复时,有效负载数据与第一个数据包一起发送,可以做到 0-RTT:

    (3)连接迁移

    QUIC 协议没有用四元组的方式来“绑定”连接,而是通过连接 ID来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”地复用原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能。

    3、HTTP/3 协议

    在这里插入图片描述
    HTTP/3 在头部压缩算法这一方面也做了升级,升级成了 QPACK。与 HTTP/2 中的 HPACK 编码方式相似,HTTP/3 中的 QPACK 也采用了静态表、动态表及 Huffman 编码。

    对于静态表的变化,HTTP/2 中的 HPACK 的静态表只有 61 项,而 HTTP/3 中的 QPACK 的静态表扩大到 91 项。

    HTTP/2 和 HTTP/3 的 Huffman 编码并没有多大不同,但是动态表编解码方式不同。

    所谓的动态表,在首次请求-响应后,双方会将未包含在静态表中的 Header 项更新各自的动态表,接着后续传输时仅用 1 个数字表示,然后对方可以根据这 1 个数字从动态表查到对应的数据,就不必每次都传输长长的数据,大大提升了编码效率。

    可以看到,动态表是具有时序性的,如果首次出现的请求发生了丢包,后续的收到请求,对方就无法解码出 HPACK 头部,因为对方还没建立好动态表,因此后续的请求解码会阻塞到首次请求中丢失的数据包重传过来。HPACK的队头阻塞问题

    那QPACK又是如何解决这个问题的呢?
    QUIC 会有两个特殊的单向流,这两个单向流的用法:

    • QPACK Encoder Stream, 用于将一个字典(key-value)传递给对方,比如面对不属于静态表的 HTTP 请求头部,客户端可以通过这个 Stream 发送字典;

    • QPACK Decoder Stream,用于响应对方,告诉它刚发的字典已经更新到自己的本地动态表了,后续就可以使用这个字典来编码了。

    这两个特殊的单向流是用来同步双方的动态表,编码方收到解码方更新确认的通知后,才使用动态表编码 HTTP 头部。

    本文总结于小林coding

  • 相关阅读:
    毕业设计源码基于JAVA的课程设计管理系统的设计与实现
    json schema实际运用
    Google SGE 正在添加人工智能图像生成器,现已推出:从搜索中的生成式 AI 中获取灵感的新方法
    高精度乘除法(超详细)
    别在被MySQL中count(*)和count(1)的区别文章带偏了,count语句特性
    CSS 属性计算过程
    使用 Tkinter Canvas 小部件添加放大镜功能?
    基础学习——读txt数据、字符串转list或数组、画PR曲线、画Loss曲线
    课题学习(九)----阅读《导向钻井工具姿态动态测量的自适应滤波方法》论文笔记
    算法书学习笔记
  • 原文地址:https://blog.csdn.net/cdzg_zzk/article/details/127502145