📘 第九章 基于HTTP的功能追加协议
📄 一句话概括

只消除了一半。HTTP 层的排队没了,TCP 层的队头阻塞还在——一个包丢了,整条连接上的所有流都得停。 而且它得浏览器和服务器同时支持才有效,这注定它活不长。

📝 本节和前面几节的关系

上一节把 SPDY 的好处量出来了,这一节量它的边界。 结论是:它治的是应用层的病,传输层的病它碰不到。

一、它治的确实是应用层

上一节那 105 毫秒、120 毫秒是真的省下来了,但那是在「网络本身没问题」的前提下测的。 一旦底下丢一个包,情况就反过来。 TCP 的交付是按字节序的:第 5 个段丢了,第 6、7、8 个段就算已经到了,也得在接收缓冲区里等着, 直到第 5 个重传回来。这叫 TCP 层队头阻塞。 HTTP/1.1 里这个问题不严重,因为一条连接上本来就只有一队。 SPDY 把几十条流塞进一条 TCP,一丢包,几十条流一起停——受害面反而变大了。 这一条本机造不出可控丢包,我不给数字,只把机制说清楚。 谁要验证,得在能配丢包率的路由器或 netem 上做。

二、它需要两边同时改

SPDY 不是浏览器单方面能用的东西:浏览器要支持,服务器也要支持,中间那些代理和防火墙最好别捣乱。 任何一环不支持,就得退回 HTTP/1.1。 这就注定它是一个过渡品。 2014 年写这本书的时候,谷歌、推特、脸书这些大站已经上了,但绝大多数网站没有。 更麻烦的是它从没成为正式标准——只有草案,没有 RFC。 没有标准,就没有「必须支持」的压力,也就没有生态。

三、它还活着吗:去问一台真服务器

TLS 握手时有个 ALPN 扩展,双方在这一步商量「接下来跑什么协议」。 只报 spdy/3.1 去问,回来的选择是空——服务器不认识它。 换成报 h2 和 http/1.1,服务器立刻选了 h2。 同一台机器、同一个端口,态度完全不同:SPDY 已经不在它的名单里了。 时间线是这样:SPDY 2009 年出草案,HTTP/2 在 2015 年成为 RFC 7540, 浏览器在 2016 年前后陆续把 SPDY 删掉。 所以这本书里「SPDY 是 HTTP/2 的基础」这句话是对的,但 SPDY 本身已经不在了。 再往后一步:HTTP/2 仍然跑在 TCP 上,所以 TCP 层那个队头阻塞还在。 这正是后来 QUIC 把传输换成 UDP 的原因——那是这本书之后的事,这里只提一句,不展开。

四、动手 Demo:拿 ALPN 去问,再量一次省下的到底是什么

连一台真实的服务器,分别只报 SPDY、只报 h2、只报 HTTP/1.1,看它各选什么。 然后回到本机,量 60 个请求走 1 条连接和走 6 条连接的差别。

ctx = ssl.create_default_context()
ctx.set_alpn_protocols([u'spdy/3.1'])        # 只报 SPDY
s = ctx.wrap_socket(connect(HOST, 443), server_hostname=HOST)
print(s.selected_alpn_protocol())            # 空

ctx.set_alpn_protocols([u'h2', u'http/1.1'])  # 只报 h2 和 1.1
print(s.selected_alpn_protocol())            # h2

实跑输出:

--------------------------------------------------------------------
SPDY 还在不在:拿 ALPN 去问服务器
--------------------------------------------------------------------
  只报 SPDY:谈成 (空) / TLSv1.3
  只报 h2+1.1:谈成 h2 / TLSv1.3
  只报 1.1:谈成 http/1.1 / TLSv1.3

  第一行是关键:服务器不认识 spdy/3.1,所以没得选。
  SPDY 从来没成为正式标准(没进 RFC),只以草案形式存在;
  浏览器 2016 年前后陆续移除它,接替者是 2015 年成为 RFC 7540 的 HTTP/2。

  ── 那多路复用省下的是什么:连接数
  60 个请求走 1 条连接       360.9 毫秒
  60 个请求走 6 条连接       63.1 毫秒
  差                         297.8 毫秒,5.7 倍

  这是 SPDY/HTTP/2 想解决的那个数:少排队就快。但要说清楚两点——
  1. 它没有减少字节,一个字节都没少,只是让等的时间不叠在一起;
  2. TCP 层自己的队头阻塞它治不了:一个包丢了,整条连接的流都得停。
     这一条本机造不出可控丢包,我不给数字,只说机制。

只报 SPDY,谈成的是空,TLS 版本照旧 TLSv1.3。 意思是:加密握手成功了,但应用协议没谈拢——服务器不认识 spdy/3.1。 只报 h2+1.1,谈成 h2;只报 1.1,谈成 http/1.1。 同一台服务器,三行输出就把 SPDY 的结局说清楚了。 回到本机:60 个请求走 1 条连接 360.9 毫秒,走 6 条连接 63.1 毫秒,差 5.7 倍。 这就是多路复用要抢的那块时间。 但注意第二句:它一个字节都没少。所以它不是「压缩」技术,是「别让人排队」的技术。

30 秒实验

  1. 把 spdy/3.1 换成 h3 再问一次,看能不能谈成——想想为什么。
  2. 把 60 个请求的耗时改成每个 20 毫秒,看 1 条与 6 条的倍数怎么变。
  3. 想一想:如果你的网站只有一个大文件要下载,多路复用还有用吗?

常见误区

  1. 「多路复用能治所有队头阻塞」——TCP 层那一层它治不了,反而受害面更大。
  2. 「SPDY 是 HTTP/2 的旧名字」——思路被继承,但协议不是同一个,SPDY 已废弃。
  3. 「多路复用能省带宽」——它省的是等待,字节数不变。
自测三问(点开对答案) 1. SPDY 没解决的那个队头阻塞在哪一层? —— TCP 层:一个段丢了,后面已到达的段也得等重传,整条连接的所有流一起停。 2. 为什么 SPDY 注定是过渡品? —— 它需要浏览器和服务器同时支持,而且从未成为 RFC 正式标准。 3. 只用 ALPN 报 spdy/3.1,结果是什么? —— 选择为空——服务器已经不支持它了;改报 h2 立刻谈成 h2。

SPDY 的位置让给了 HTTP/2.0。但 HTTP/2 也是「还跑在 TCP 上」的那个 HTTP/2。

相关笔记