目录
http1.x
- 冗余的header:每个请求都会带上冗余重复的Header,导致传输的体积很大
- http是无状态的协议,每个请求没有做特殊的标识:无法通过一个TCP链接并发的发送多个请求,只能等上一个请求的rsp返回了,才能发送下一个请求。(只能串行的发送)
- 因为http1.x是基于”文本“的协议,请求的内容打包在header/body中,内容通过CRLF来分割,同一个TCP链接中,无法区分req/rsp是属于哪个请求
- 于是http1.x提出了pipeline:允许请求方一口气的发送多个请求,但是有一个严重的弊端,需要对应的rsp按照req的顺序严格排列(因为每个请求没有唯一的标识,所以不按照顺序排列就无法区分rsp是属于哪个req)。==> 若前面的req迟迟不来,后面的请求都会要等待
- Chrome浏览器,一个域名,只能有6个TCP链接在同时工作
http2.0
相较于http1.0的改进点
- header压缩
- 静态字典:常见的头部名称,如,get/post/cookie等
- 动态字典:比如一个req过来,会将字段保存在动态字典中,再一个req,若发现相同的字段,无需传递,只需要使用动态字典中的标识
- 流/帧 + 多路复用
- http2.0提出了流的概念,每个请求对应一个流,每个流都有唯一的ID,用来区分不同的req/rsp
- 基于流的概念,又提出了帧:一个请求的数据被分成多个帧,方便进行数据传输。每个帧都属于某一个流ID
- 多路复用:在一个TCP链接上,可以同时发起无数个请求,并且响应可以同时返回
- 请求划分优先级
- 多路复用带来的一个问题是,在共享连接的基础上会存在一些关键请求被阻塞,SPDY 允许给每个请求设置优先级,这样重要的请求就会优先得到响应
- 服务端推送
- 在HTTP1.x中,访问一个页面,浏览器首先获取HTML资源,然后在解析页面时增量地获取其他资源,服务器必须等待浏览器发出请求后才下发页面内资源。而服务器实际上是知道页面内资源有哪些的,如果服务器能够在浏览器显式请求资源之前就将资源推送到浏览器,页面加载速度将会大大提示,这也是本篇的主旨。
- 简单来讲,就是当用户的浏览器和服务器在建立链接后,服务器主动将一些资源推送给浏览器并缓存起来,这样当浏览器接下来请求这些资源时就直接从缓存中读取,不会在从服务器上拉了,提升了速率
http2.0存在的问题:http2.0只是通过多路复用技术解决了http层面上的队首阻塞,但是tcp层面的毫无办法。TCP 的队头阻塞并没有彻底解决。TCP 为了保证可靠传输,有一个“超时重传”机制,丢失的包必须等待重传确认
TCP队头阻塞
我们都知道,TCP是一种可靠传输,这个可靠就是体现在它能够“按序到达”,然后再被上层接收,这里的按序到达指的是最终顺序是按序排列的,也就是说每当有一个或几个Packet丢失的时候,会等待它到达后合并,然后再向上交付。因此,很容易可以理解,当一个流的第一个数据包丢失了,那么即使后面的数据包都到达了,后面的这些数据包也不能被处理,而是要等第一个数据包到了之后才能被上层接收处理,那么这个时候不止是第一个数据包需要额外等待一个或多个RTT,后面的第二个Packet、第三个Packet......都需要同样多等待那么多时间才能处理,但实际上他们是按时到达了的,这也就是所谓的TCP队头阻塞。
http3.0
http3.0彻底弃用了TCP协议,改用可靠的UDP协议(QUIC)
TCP协议存在的缺点:其实也不能怪 TCP 协议,它本来设计目的就是为了保证数据的有序性
1. TCP建立连接的延迟
2. TCP存在队头阻塞问题:
1. 发送窗口的队头阻塞:TCP 发送出去的数据,都是需要按序确认的,只有在数据都被按顺序确认完后,发送窗口才会往前滑动。
案例:客户端发送了第 5~9 字节的数据,但是第 5 字节的 ACK 确认报文在网络中丢失了,那么即使客户端收到第 6~9 字节的 ACK 确认报文,发送窗口也不会往前移动。这就是发送窗口的头部阻塞问题
2. 接收窗口的队头阻塞:接收窗口什么时候才能滑动?当接收窗口收到有序数据时,接收窗口才能往前滑动,然后那些已经接收并且被确认的「有序」数据就可以被应用层读取。(但是,当接收窗口收到的数据不是有序的)
案例:比如收到第 33~40 字节的数据,由于第 32 字节数据没有收到, 接收窗口无法向前滑动,那么即使先收到第 33~40 字节的数据,这些数据也无法被应用层读取的。只有当发送方重传了第 32 字节数据并且被接收方收到后,接收窗口才会往前滑动,然后应用层才能从内核读取第 32~40 字节的数据
QUIC如何解决队头阻塞问题的?
1. 在一条 QUIC 连接上可以并发发送多个 HTTP 请求 (Stream)。
2. 但是,QUIC 给每一个 Stream 都分配了一个独立的滑动窗口,这样使得一个连接上的多个 Stream 之间没有依赖关系,都是相互独立的,各自控制的滑动窗口
QUIC如何做流量控制的?
TCP 流量控制是通过让「接收方」告诉「发送方」,它(接收方)的接收窗口有多大,从而让「发送方」根据「接收方」的实际接收能力控制发送的数据量 。在前面说到,
1. TCP 的发送窗口在收到对已发送数据的顺序确认 ACK后,发送窗口才能往前滑动,否则停止滑动。
2. TCP 的接收窗口在收到有序的数据后,接收窗口才能往前滑动,否则停止滑动;
QUIC 是基于 UDP 传输的,而 UDP 没有流量控制,因此 QUIC 实现了自己的流量控制机制。不过,QUIC 的滑动窗口滑动的条件跟 TCP 有所差别的。QUIC实现了2中级别的流量控制:stream、connection。
1. stream级别的流量控制
回想一下 TCP,当发送方发送 seq1、seq2、seq3 报文,由于 seq2 报文丢失了,接收方收到 seq1 后会 ack1,然后接收方收到 seq3 后还是回 ack1(因为没有收到 seq2),这时发送窗口无法往前滑动。
是,QUIC 就不一样了,即使中途有报文丢失,发送窗口依然可以往前滑动,具体怎么做到的呢?我们来看看。
① 最开始,接收方的接收窗口初始状态如下:
② 接着,接收方收到了发送方发送过来的数据,有的数据被上层读取了,有的数据丢包了,此时的接收窗口状况如下:
可以看到,即使中间有数据包丢失了,也不会影响后面数据的接收。接收窗口的左边界取决于接收到的最大偏移字节数,此时的接收窗口 = 最大窗口数 - 接收到的最大偏移数
,这里就跟 TCP 不一样了。
那接收窗口触发的滑动条件是什么呢?看下图:
当图中的绿色部分数据超过最大接收窗口的一半后,最大接收窗口向右移动,同时给对端发送「窗口更新帧」。当发送方收到接收方的窗口更新帧后,发送窗口也会往前滑动,即使中途有丢包,依然也会滑动,这样就防止像 TCP 那样在出现丢包的时候,导致发送窗口无法移动,从而避免了无法继续发送数据
在前面我们说过,每个 Stream 都有各自的滑动窗口,不同 Stream 互相独立,队头的 Stream A 被阻塞后,不妨碍 StreamB、C的读取。而对于 TCP 而言,其不知道将不同的 Stream 交给上层哪一个请求,因此同一个Connection内,Stream A 被阻塞后,StreamB、C 必须等待。
总结:QUIC 协议中同一个 Stream 内,滑动窗口的移动仅取决于接收到的最大字节偏移(尽管期间可能有部分数据未被接收),而对于 TCP 而言,窗口滑动必须保证此前的 packet 都有序的接收到了,其中一个 packet 丢失就会导致窗口等待。
2. connection级别的流量控制
而对于 Connection 级别的流量窗口,其接收窗口大小就是各个 Stream 接收窗口大小之和。
Connection 流量控制
上图所示的例子,所有 Streams 的最大窗口数为 120,其中:
Stream 1 的最大接收偏移为 100,可用窗口 = 120 - 100 = 20
Stream 2 的最大接收偏移为 90,可用窗口 = 120 - 90 = 30
Stream 3 的最大接收偏移为 110,可用窗口 = 120 - 110 = 10
那么,整个 Connection 的可用窗口 = 20 + 30 + 10 = 60
QUIC如何做拥塞控制的?
QUIC 协议当前默认使用了 TCP 的 Cubic 拥塞控制算法(我们熟知的慢开始、拥塞避免、快重传、快恢复策略),同时也支持 CubicBytes、Reno、RenoBytes、BBR、PCC 等拥塞控制算法,相当于将 TCP 的拥塞控制算法照搬过来了,QUIC 是如何改进 TCP 的拥塞控制算法的呢?
QUIC 是处于应用层的,应用程序层面就能实现不同的拥塞控制算法,不需要操作系统,不需要内核支持。这是一个飞跃,因为传统的 TCP 拥塞控制,必须要端到端的网络协议栈支持,才能实现控制效果。而内核和操作系统的部署成本非常高,升级周期很长,所以 TCP 拥塞控制算法迭代速度是很慢的。
而 QUIC 可以随浏览器更新,QUIC 的拥塞控制算法就可以有较快的迭代速度。
TCP 更改拥塞控制算法是对系统中所有应用都生效,无法根据不同应用设定不同的拥塞控制策略。但是因为 QUIC 处于应用层,所以就可以针对不同的应用设置不同的拥塞控制算法,这样灵活性就很高了。
QUIC更快的建立连接?
对于 HTTP/1 和 HTTP/2 协议,TCP 和 TLS 是分层的,分别属于内核实现的传输层、openssl 库实现的表示层,因此它们难以合并在一起,需要分批次来握手,先 TCP 握手(1RTT),再 TLS 握手(2RTT),所以需要 3RTT 的延迟才能传输数据,就算 Session 会话服用,也需要至少 2 个 RTT。
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:
RPC和http有什么区别
- RPC是方法,http是传输协议。其实两者不是一个类型,没有什么可比性
- 传输协议
- RPC:tcp协议、http协议
- http:http协议
- 传输效率
- RPC ①如果使用自定义的TCP协议,可以让请求头的信息更少 ②使用http2.0协议,传输效率也很高
- http:请求头中包含很多无用的信息,比如refer,keepalivetime,last-modify等信息。在流量大的时候,每次请求多几个字节,影响效率
- 性能
- RPC:可以基于thrift或pb来实现二进制传输
- http:也可以使用pb,但是目前主流的浏览器大部分是使用json文本传输
protobuf压缩
optional uint64 id = 1;
序列号 + 类型 = 序列号<<3 | 类型
id保存的值:采用base128压缩