HTTP 协议经过发展,目前 HTTP2.0 作为主流 HTTP 协议,已经得到一定普及,虽然国内仍然有很多连 HTTPS 都没上的网站,但不影响 HTTP 协议的发展。

HTTP2.0 我们知道相较于 HTTP1.1 版本的协议,进行了很多的优化,可以参考之前公众号的一篇文章《如何优化你的HTTPS》,里面有关于 HTTP2.0 的优化方面的所有介绍,现在 HTTP-over-QUIC 出了也有一段时间了,虽然还是实验性协议,但是 IETF HTTP 和 QUIC 工作组主席 Mark Nottingham 正式提出将 HTTP-over-QUIC 重命名为 HTTP/3.0 到现在已经有一年多的时间了,所以 HTTP-over-QUIC 成为 HTTP/3.0 算是没跑了,所以还是早点认识一下这个新版本的协议

QUIC 全名是(Quick UDP Internet Connections),它是 google 在 2013 年开发实现的

我们都知道,TCP 和 UDP 最本质的区别在于可靠传输,HTTP2.0 及之前的协议建立在 TCP 上,TCP 为了达到可靠传输,必须要有确认机制,需要来回握手确认来建立链接,即便 HTTP2.0 中做了很多优化,但仍然摆脱不了 TCP 的三次握手,而且 TCP 协议在处理包时是有严格顺序的,当其中一个数据包遇到问题时,TCP 链接需要等待整个包重传之后才能继续进行,虽然 HTTP2.0 中通过多个 stream,使得逻辑上一个 TCP 链接上的并行内容,进行多路数据传输,然而这中间没有关联的数据,当 stream2 的帧没有收到,后面 stream1 的帧也会因此阻塞

所以 google 在 QUIC 协议中基于 UDP 协议,跳出 TCP 协议,它是在两个端点之间创建链接,且支持多路复用,并且在设计之初就考虑希望能够提供等同于 SSL/TLS 层级的安全保障的同时,减少数据传输及创建链接时的延迟时间,双向控制带宽,从而达到更快速的体验,先通过一个动图直观对比下

接着看下 QUIC 有什么优势,已经通过什么方法解决 TCP 的一些限制及问题

新定义连接机制

在 TCP 连接中,一条 TCP 连接是由四元组标识的,分别是源 IP、源端口、目的 IP、目的端口,一旦一个元素发生变化时,就会断开重连,重新进行三次握手,导致一定的延时

在基于 UDP 的 QUIC 中,是在自己的逻辑里面维护连接的机制,不再是以四元组标识,而是以一个 64 位的随机数作为 ID 来标识,而且 UDP 是无连接的,所以当 IP 或端口变化的时候,只要 ID 不变,就不需要重新建立连接

新定义重传机制

在 TCP 连接中,为了保证可靠性,通过使用序号和应答机制,来解决顺序问题和丢包问题,如上面说到的,即便 HTTP2.0 使用 stream 的方式,也还是存在阻塞的问题

在 TCP 中,任何一个序号的包发过去,都要在一定时间内得到应答,超时之后,就会触发重传,重新发送这个序号的包,而 RTO(重传超时时间)的计算相对复杂,现在都是通过自适应算法设定 RTO 的值,而这个计算的不准确会直接导致网络的吞吐量和网络资源利用率

在 QUIC 中,也有个序列号 PN(Packet Number),是递增的,任何一个序列号的包,只发送一次,下次就要加 1,根据 Packet Number 值就可以计算出是重传响应还是原始响应,这样可以准确计算 RTT 时间

但是有一个问题,就是 UDP 无连接,所以没法确认两个包是否是同样的内容,也就是没有办法确定发送出去的包是不是重传包,QUIC 虽然基于 UDP,但它也是一个可靠数据传输协议,所以 QUIC 又定义了一个 offset 概念,在发送的数据流里面有个偏移量 offset,可以通过 offset 查看数据发送到了哪里,这样只有这个 offset 的包没有来,就要重发,如果来了,按照 offset 拼接成一个完整的数据流

对于重传,QUIC 有个特性就是关键包短时间内发送多次,这样以确保重要的节点不被 Delay

没有 HOL 的多路复用

QUIC 的多路复用和 HTTP2 类似,在一条 QUIC 连接上可以并发发送多个 HTTP 请求,但是 QUIC 的多路复用比 HTTP2 有一个很大的优势,那就是 QUIC 一个连接上的多个 stream 之间没有依赖,这样,假如 stream2 丢了一个 udp packet,也只会影响 stream2 的处理,不会影响 stream2 之前以及之后的 stream 的处理,这就很大程度上缓解了 HOL 阻塞的问题

当然并不是所有的 QUIC 数据丢失都不会受到 HOL 阻塞影响,比如 QUIC 使用 Hpack 压缩算法的时候,由于算法的限制,丢失一个头部数据时,可能遇到 HOL 阻塞

流量控制

UDP 没有流量控制机制,由于 QUIC 的多路复用机制,其流量控制分为 Stream 和 Connection 两种级别的控制

Stream 就可以理解为一条 HTTP 请求

Connection 可以理解为一条 TCP 连接

具体的实现机制是

  • 通过 window_update 帧告诉对端自己可以接收的字节数,这样发送方就不会发送超过这个数量的数据
  • 通过 BlockFrame 告诉对端由于流量控制被阻塞了,无法发送数据

QUIC 的流量控制和 TCP 的有点区别,TCP 是使用了滑动窗口机制来进行流量控制,为了保证可靠性,窗口左边沿向右滑动的长度取决于已经确认的字节数,如果中间出现丢包,就算接收到了更大序列号的 Segment,窗口也无法超过这个序列号,但是 QUIC 不同,就算此前有些 Packet 没有接收到,它的滑动只取决于接收到的最大偏移字节数

对于 Connection 级别的流量窗口,其接收窗口大小就是各个 stream 接收窗口大小之和

而在 Connection 中,不同的 stream 互相独立,不会引起 HOL 阻塞

所以在弱网环境下,QUIC 优势更明显

HTTP/3.0 综上所述,有这么多优点,必须要体验一下,下面介绍下

Nginx 支持 HTTP/3.0

Nginx 原生不支持 HTTP/3.0,这里需要借助 Cloudflare 提供的一个补丁来让 Nginx 支持 HTTP/3.0

这里因为要补丁编译安装,所以需要下载 nginx 源码包,我这里仍然用 nginx1.17.7 版本来测试,下载包解压包就不说了

接着通过 git 下载 QUIC 补丁

然后因为要用 patch 打补丁,所有通过 yum 安装 patch

接着就开始打补丁

这里只有 nginx1.16 的补丁,我也不确定,看着是打上了,编译试一下

重要的是 with-http_v3_module、openssl 用 quiche/deps 下面的 boringssl,另外就是–with-quiche 指向 quiche 目录

接着 make,这里因为没有 cmake 报了个错误,通过 yum 安装 cmake3,

注意要用 cmake3.0 以上版本,所以用 yum install cmake3,这个要开启 epel 源

然后重新 make,这里还需要安装 gcc-c++,cargo、golang 环境,否则在编译 boringssl 的时候会报错,编译不过去

接着是漫长的等待,如果服务器性能好的话,可以用 make -j 加速编译

在后面使用 cargo 编译的时候,因为默认是 creates.io 的仓库,实在太慢了,所以这里建议更换为中科大的源,在/root/.cargo 下面新建 config 文件,内容如下:

更多 cargo 的可以查看 cargo 中文社区

另外就是编译 boringssl 的时候,编译出的库为静态库,最后 ld 链接的时候无法连接,需要在 nginx 编译之后生成的 objs/Makefile 中修改 cmake 编译参数,设置为-fPIC,生成共享库,否则编译失败

之后即可编译成功

在 objs 下面将 nginx 二进制文件覆盖掉原来的 nginx,开始配置 nginx 配置文件

在 quiche 中通过 cargo 构建客户端,通过 http3-client 测试请求

客户端请求

请求日志查看

抓包查看

如果是 chrome 浏览器开启 QUIC

需要重启 chrome 才能生效

快速体验

当然如果不想像我一样折腾,直接用 docker 就可以体验了

docker run -it –p 443:443 –p 443:443/udp  -v $PWD/nginx.conf:/usr/local/nginx/conf/nginx.conf  -v /root/cert/haid.com.cn.pem:/etc/ssl/certs/server.crt  -v /root/cert/haid.com.cn.key:/etc/ssl/private/server.key  nwtgck/nginx-http3

折腾环境太费劲了,后面有机会做 quic 和 http2 的对比,以及分析 quic 包,折腾不易,欢迎转发、在看!

本文作者: InfoQ

余下全文(1/3)

本文最初发表在wkee.net,文章内容属作者个人观点,不代表本站立场。

分享这篇文章:

请关注我们:

发表评论

邮箱地址不会被公开。 必填项已用*标注