HTTP 到 QUIC
HTTP存在的问题
HTTP1.1开始有了巨大的改进。最大的一点是建立了持久连接的机制,默认开启,在 HTTP 头中,包含了一个 Connection
字段,对是否保持、重用连接进行说明。当 Connection: keep-alive
时,连接将被保持,之后客户端可以继续在这个TCP连接上发送新的请求。当客户端确实需要关闭连接时,发送的请求要明确说明Connection: close
,服务器端在处理完成后就会关闭连接。
HTTP1.1以前或者不开持久连接,请求结束后就将连接断开,这样我们需要为每一个 HTTP 请求都重新创建 TCP 连接。每一个新的 TCP 连接在创建时需要经历握手和慢启动的机制,客户端在使用新的连接发送HTTP请求时,都要经历可观的延迟。
问题1:
由于 HTTP 协议采用请求-响应的模型,在一个 TCP 连接上,同一个时刻只能有一个请求,请求发送后,客户端必须等待返回。这种同步、阻塞的方式限制了连接的吞吐量。
一般解决:
- 为了提高吞吐量,不同的客户端可以支持打开多个的链接,并重用它们
- 切分域名(这其实是为了更大的发出更多的请求)
- HTTP1.1 Pipelining,允许客户端在响应返回前直接发送下一个请求。一旦启用了 HTTP Pipelining,至多可以同时发送 32 个请求,之后只需要等待这些请求依次返回即可。
- 这种特性绝非没有代价,Pipelining 机制对顺序有严格的要求。如果响应返回的顺序与请求的顺序不一致,就要求客户端在更高层面增加顺序判断的机制,否则就将引起混乱。
HTTP2.0引入了多路复用,客户端和服务器仍然保持一个连接,但是会基于多路复用的机制在这个连接上抽象出一层stream,请求-响应全部搭载在 stream 之上,在同一个 stream 上仍然保持原先的请求-响应模型。但是两端之间可以创建多组 stream,这样就避免了排队等待的问题。
问题2:
虽然有了多路复用的机制,可以大大提升网络请求的性能,但是只是解决了应用层的顺序问题。但是在TCP层,TCP 本身是要求顺序的,对顺序的要求就会带来排队的问题。
如果传输中出现了丢包的情况,操作系统必须等到之前的数据重传完成后才会交付到上层的应用程序。在这部分等待的数据中,很可能就包含其他 stream 的完整数据包,而它们原本可以提前交付给应用层。
解决:
QUIC
QUIC
基于UDP完成网络请求。与 HTTP/2 + TLS + TCP 相比,QUIC 承担了 HTTP/2 中的整个 stream 管理部分、TLS 安全连接部分和 TCP 的重传、顺序、流控等机制。针对上一节描述的问题,QUIC 可以将 TCP 对顺序的要求进一步细化到 stream 上。在 QUIC 中,重传的粒度被提高到 stream 的层面,这样使得上层的抽象与下层的实现达成了一致。QUIC 重新将 HTTP/2 中较复杂的流管理(在应用层显得不伦不类)移到了本该存在传输协议中,也简化了 HTTP/2 协议,使这个应用层协议可以专注于 HTTP 的语义本身。