作者:风起

开篇吹一波阿里云功能测验服务PTS [1] ,PTS 在 2021 年 5 月份已经上线了对 HTTP2 协议的支撑(底层依赖 httpclient5),在压测时会经过与服务端协商的成果来决议运用 HTTP1.1 或许 HTTP2 协议。

布景

写这篇文章的原因是某天某个客户找过来,问咱们是不是不支撑 HTTP2,因为他在 XX 云上购买了 2 个域名,其间一个开启了 HTTP2,而在 PTS 压测过程中,支撑 HTTP2 的接口总是报错:

如何定位并修复 HttpCore5 中的 HTTP2 流量控制问题

起先怀疑是 HTTP2 支撑的问题,经过在本地强制运用 HTTP2 协议,拜访淘宝主页,发现是没问题的,怀疑是用户在 XX 云上的装备问题,但紧接着经过在本地 Postman、curl 以及压测引擎强制运用 HTTP1.1 协议时都可以正常拜访该网页,意识到大概率是 PTS 引擎侧的问题。

经过本地 debug,看到是因为恳求 URL 时,客户端窗口巨细被调整为大于 2^32 -1 导致的反常。

如何定位并修复 HttpCore5 中的 HTTP2 流量控制问题

那正好借这个时机看下这儿的窗口巨细指的是什么。

HTTP2 流控

说到窗口,就要说到 HTTP2 相比于 HTTP1.1 支撑的新特性:流控(Flow Control),其实 HTTP1.1 依赖于传输层 TCP 的滑动窗口相同可以完成流控,那么为什么 HTTP2 要在应用层再完成一个流控呢?原因在于 HTTP2 引入了流和多路复用,经过流控可以到达使多个流协同的效果。

一些流控的基本概念:

  1. 流控是针对衔接而言的,不是针对端到端的,而是在两头中的每一跳;主要指有署理的情况下,署理与两头都存在流控
  2. 流控是根据WINDOW_UPDATE 帧的,接纳者可以经过流控操控发送者的速度
  3. 流控既可以作用于 stream 也可以作用于 connection
  4. 对于衔接与所有新开启的流而言,流控窗口巨细默许都是 65535,且最大值为 2^32 – 1
  5. 流控无法禁用

为了便于理解,先简单列一下 HTTP2 帧的类型:

  • DATA:带着恳求或呼应中的数据
  • HEADERS:用于新建一个流(恳求或呼应),包括对应的 Headers
  • PRIORITY:用于装备流的优先级
  • RST_STREAM:强制完毕某个流,仅用于某一端撤销流,并不适用于正常流的完毕
  • SETTINGS:H2 建联的一些装备
  • PUSH_PROMISE:服务端推送呼应到客户端
  • PING:向远端发送一条 PING,远端必须回来该 PING
  • GOAWAY:用于某一端将要完毕衔接
  • WINDOW_UPDATE:更新流控窗口巨细
  • CONTINUATION:如果 headers 过大,单个 HEADERS 帧难以带着,经过该帧发送额定的 headers

接下来,咱们要点看下贱控相关的帧,主要是 SETTING 与 WINDOW_UPDATE,在衔接建立时会经过 SETTINGS 帧来调整对方的窗口巨细,之后在传输过程中,窗口巨细会随着数据的发送逐渐减小,直到收到对方发送的 WINDOW_UPDATE 帧,然后更新窗口巨细。SETTINGS 帧主要包括以下内容:

  • SETTINGS_HEADER_TABLE_SIZE:HPACK(一种header紧缩算法) header 表的最大长度,默许值 4096
  • SETTINGS_ENABLE_PUSH:客户端发向服务端的装备,若设置为 true,客户端将答应服务端推送呼应,默许值 true
  • SETTINGS_MAX_CONCURRENT_STREAMS:一起翻开的 stream 最大数量,通常意味着同一时刻可以一起呼应的恳求数量,默许无限
  • SETTINGS_INITIAL_WINDOW_SIZE:流控的初始窗口巨细,默许值 65535
  • SETTINGS_MAX_FRAME_SIZE:对端可以承受帧的最大长度,默许值16384
  • SETTINGS_MAX_HEADER_LIST_SIZE:对端可以承受的 header 列表最大长度,默许不约束

流控的完成如上所述,每发送一批 DATA 帧,即将窗口巨细减小。需要留意的是流控仅针对 DATA 帧。

如何定位并修复 HttpCore5 中的 HTTP2 流量控制问题

前面说到流控既可以作用于 stream 又可以作用于 connection,那具体是怎么履行的呢?connection 的流控与 上述 stream 流控逻辑相似,每次发送 DATA 帧,connection 与 stream 窗口都会减小,但不同的是,WINDOW_UPDATE 要么独自作用于 stream,要么独自作用于 connection(streamid 为 0 时,表明作用于 connection)。

问题定位

那么回到开篇的问题,咱们以 URL ​www.sysgeek.cn/​为例,经过在本地做代码 debug 发现,最终抛反常的原因在于接纳到 WINDOW_UPDATE 帧后,更新后窗口巨细值大于 2^32 – 1 导致抛反常:

如何定位并修复 HttpCore5 中的 HTTP2 流量控制问题

而从这儿的代码可以看出,524288 是当时窗口巨细,而delta是对方告知的 WINDOW_UPDATE 巨细,经过剖析,发现 524288 这个值不同于默许值 65535,那继续看这个值是什么时刻改动的:

如何定位并修复 HttpCore5 中的 HTTP2 流量控制问题

发现是接纳 SETTINGS 指令后,初始化窗口巨细时修正的,但这儿与RFC 7540 [2] 的描绘(connection 窗口巨细仅在接纳到 WINDOW_UPDATE 后才或许修正)是抵触的:

如何定位并修复 HttpCore5 中的 HTTP2 流量控制问题

因此咱们断定是 httpcore5 的源代码有 bug,在删除标记的这行代码后,恳求可以正常履行了。

惋惜的是在准备给 httpcore5 提 PR 的过程中发现这个 bug 已经在 commit 中被修正了。

参考资料

[1] PTS:

help.aliyun.com/document_de…

[2] RFC 7540:

datatracker.ietf.org/doc/html/rf…

• ​​https://datatracker.ietf.org/doc/html/rfc7540#section-5.2​​

• ​​https://undertow.io/blog/2015/04/27/An-in-depth-overview-of-HTTP2.html​​

• ​​https://laike9m.com/blog/rfc7540-bi-ji-wu-flow-control,106/​​

点击​​此处​​,前往 PTS 官网检查更多~