SPDY 在 HTTP 和 TCP 之间插了一层:把请求切成带流号的帧,在一条连接上交错发送,再给首部做压缩、 给流排优先级、还能主动推送。这套东西后来原样变成了 HTTP/2.0。
上一节把三个瓶颈量出来了,这一节看第一个动手的方案。 它是谷歌 2009 年的实验品,从没成为正式标准,但它的思路全活了。
一、插在中间的那一层
SPDY 不改 HTTP 的语义——方法还是那些方法,状态码还是那些状态码, 它只在 HTTP 和 TCP 之间加了一层「会话层」。 这一层干的事很朴素:把每个请求和响应切成小片,每片带一个流号, 然后按流号交错地塞进同一条 TCP 连接。接收端照流号重新拼起来。 因为有了流号,响应不必按请求顺序回来。 前面那条「响应必须按序」的硬规定就被绕过去了——这是它最核心的一步。
二、四件具体的事
多路复用:一条连接上跑很多条流,互不排队。这是主菜,其余三样是配菜。 请求优先级:给流标上优先级,让 HTML 和 CSS 这类「挡路的」先走,大图往后排。 没有优先级的话,一条大视频流会把整条连接占满。 首部压缩:首部里有大量重复,SPDY 用一张固定的表加一套编码把它压下来。 这套做法后来演进成 HTTP/2 的 HPACK(9.4 会实算一次)。 服务器推送:服务端可以主动把「你八成还要」的资源先送过去,不用等你发现再请求。 这是 HTTP 从来没有的能力。
三、帧交错的实测
三条流各切四片,每片 125 字节。信道有限,每发一帧占 4 毫秒; 流 1 的数据要 100 毫秒后才备好,流 2、流 3 立刻就绪。 交错发的完成时刻是 116/30/35 毫秒,排队发是 115/135/155 毫秒。 两种发法的总字节完全一样,都是 1500 字节。 看流 1:两种发法都是一百一十几毫秒,因为它自己慢,谁也救不了。 但流 2 提前了 105 毫秒,流 3 提前了 120 毫秒——差别全在这里。 所以多路复用的收益不是「让总时间变短」,是不让快的去等慢的。 这句话值得记下来,后面判断一个优化值不值,就看它有没有做到这一点。
四、动手 Demo:同样的 1500 字节,两种排法
用一对 socket 做一个真实的字节流,自己定义帧格式:4 字节流号加 1 字节片号,后面跟 120 字节负载。 三条流各四片,交错发一遍、排队发一遍,量每片到达的时刻。
def frame(sid, seq):
return struct.pack(u'>IB', sid, seq) + bytes([64 + sid]) * 120
# 交错:谁就绪就发谁,轮着来
cand = [s for s in (1, 2, 3) if pos[s] < 4 and READY[s] <= now]
s = cand[rot % len(cand)]
sendall(frame(s, pos[s]))
实跑输出:
--------------------------------------------------------------------
帧交错:把「排队」换成「穿插」
--------------------------------------------------------------------
三条流,每条切成四片,每片 125 字节(5 字节帧头 + 120 字节负载)。
信道是有限的:每发一帧占 4 毫秒。流 1 的数据要 100 毫秒后才备好,
流 2、流 3 立刻就绪。两种发法各走一遍,量每片真正到达的时刻。
【交错发】接收顺序(毫秒/流/片号)
流2-0@0 流3-0@5 流2-1@10
流3-1@15 流2-2@20 流3-2@25
流2-3@30 流3-3@35 流1-0@101
流1-1@107 流1-2@112 流1-3@117
【排队发】接收顺序(毫秒/流/片号)
流1-0@101 流1-1@106 流1-2@111
流1-3@116 流2-0@121 流2-1@126
流2-2@131 流2-3@135 流3-0@140
流3-1@145 流3-2@150 流3-3@155
发法 流 1 完成 流 2 完成 流 3 完成 字节
交错 117 30 35 1500
排队 116 135 155 1500
两种发法字节数 1500 对 1500(一个字节都没差)
流 2 提前 105 毫秒
流 3 提前 120 毫秒
看流 1 那一列:两种发法都是 100 毫秒出头才完成,因为它自己慢,没救。
但流 2、流 3 在交错发里几十毫秒就回来了,在排队发里被拖到 130 毫秒之后。
这就是多路复用的真实收益:不是让总时间变短,是不让快的去等慢的。
SPDY 正是把「一条连接 = 一条队伍」改成了「一条连接 = 很多条流」,
交错发的接收顺序是「流2-0、流3-0、流2-1、流3-1……」,三条流交替前进,每条都在动。 排队发的接收顺序是「流1-0、流1-1、流1-2、流1-3、流2-0……」,流 1 全走完才轮得到流 2,一条一条清空。 字节数 1500 对 1500,一个字节都没差。所以多路复用不是省带宽的技术,是省等待的技术。 流 2 提前 105 毫秒、流 3 提前 120 毫秒,而流 1 一动不动。 这就是「不让快的等慢的」的具体数值。
30 秒实验
- 把流 1 的就绪时间改成 0,看两种发法的差别还剩多少。
- 把每帧的信道占用从 4 毫秒改成 1 毫秒,看提前量怎么变。
- 想一想:如果三条流的数据量差十倍,没有优先级会发生什么?
常见误区
- 「多路复用能减少传输的字节」——字节一个不少,省的是等待。
- 「SPDY 是一个新协议,和 HTTP 无关」——语义全沿用,只是换了传输方式。
- 「交错发一定更快」——如果只有一条流,两种发法没区别。
自测三问(点开对答案)
1. SPDY 那一层插在哪里? —— 插在 HTTP 和 TCP 之间,把报文切成带流号的帧,在一条连接上交错发送。 2. 多路复用解决了哪个具体问题? —— 响应必须按序返回导致的队头阻塞——有了流号,响应可以乱序交错。 3. 多路复用省的是带宽还是等待? —— 等待。总字节数一点没变。听起来完美。可它只动了 HTTP 那一层——底下还有一层没动。
- ☐ (本节完,下一节 9.2.3 SPDY消除Web瓶颈了吗)