作者:风起
开篇吹一波阿里云性能测试服务 PTS [1],PTS 在 2021 年 5 月份曾经上线了对 HTTP2 协定的反对(底层依赖 httpclient5),在压测时会通过与服务端协商的后果来决定应用 HTTP1.1 或者 HTTP2 协定。
背景
写这篇文章的起因是某天某个客户找过去,问咱们是不是不反对 HTTP2,因为他在 XX 云上购买了 2 个域名,其中一个开启了 HTTP2,而在 PTS 压测过程中,反对 HTTP2 的接口总是报错:
起初狐疑是 HTTP2 反对的问题,通过在本地强制应用 HTTP2 协定,拜访淘宝主页,发现是没问题的,狐疑是用户在 XX 云上的配置问题,但紧接着通过在本地 Postman、curl 以及压测引擎强制应用 HTTP1.1 协定时都可能失常拜访该网页,意识到大概率是 PTS 引擎侧的问题。
通过本地 debug,看到是因为申请 URL 时,客户端窗口大小被调整为大于 2^32 -1 导致的异样。
那正好借这个机会看下这里的窗口大小指的是什么。
HTTP2 流控
提到窗口,就要提到 HTTP2 相比于 HTTP1.1 反对的新个性:流控(Flow Control),其实 HTTP1.1 依赖于传输层 TCP 的滑动窗口一样能够实现流控,那么为什么 HTTP2 要在应用层再实现一个流控呢?起因在于 HTTP2 引入了流和多路复用,通过流控能够达到使多个流协同的成果。
一些流控的基本概念:
- 流控是针对连贯而言的,不是针对端到端的,而是在两端中的每一跳;次要指有代理的状况下,代理与两端都存在流控
- 流控是基于 WINDOW_UPDATE 帧的,接收者能够通过流控管制发送者的速度
- 流控既能够作用于 stream 也能够作用于 connection
- 对于连贯与所有新开启的流而言,流控窗口大小默认都是 65535,且最大值为 2^32 – 1
- 流控无奈禁用
为了便于了解,先简略列一下 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 帧。
后面提到流控既能够作用于 stream 又能够作用于 connection,那具体是怎么执行的呢?connection 的流控与 上述 stream 流控逻辑相似,每次发送 DATA 帧,connection 与 stream 窗口都会减小,但不同的是,WINDOW_UPDATE 要么独自作用于 stream,要么独自作用于 connection(streamid 为 0 时,示意作用于 connection)。
问题定位
那么回到开篇的问题,咱们以 URL https://www.sysgeek.cn/ 为例,通过在本地做代码 debug 发现,最终抛异样的起因在于接管到 WINDOW_UPDATE 帧后,更新后窗口大小值大于 2^32 – 1 导致抛异样:
而从这里的代码能够看出,524288 是以后窗口大小,而 delta 是对方告知的 WINDOW_UPDATE 大小,通过剖析,发现 524288 这个值不同于默认值 65535,那持续看这个值是什么工夫改变的:
发现是接管 SETTINGS 指令后,初始化窗口大小时批改的,但这里与 RFC 7540 [2] 的形容(connection 窗口大小仅在接管到 WINDOW_UPDATE 后才可能批改)是抵触的:
因而咱们判定是 httpcore5 的源代码有 bug,在删除标记的这行代码后,申请能够失常执行了。
遗憾的是在筹备给 httpcore5 提 PR 的过程中发现这个 bug 曾经在 commit 中被修复了。
参考资料
[1] PTS:
https://help.aliyun.com/docum…
[2] RFC 7540:
https://datatracker.ietf.org/…
• 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 官网查看更多~