如何定位并修复 HttpCore5 中的 HTTP2 流量控制问题
作者:风起
开篇吹一波阿里云性能测试服务 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/document_detail/145501.html
[2] RFC 7540:
https://datatracker.ietf.org/doc/html/rfc7540#section-6.9.2
• 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 官网查看更多~
如何定位并修复 HttpCore5 中的 HTTP2 流量控制问题相关推荐
- go技术日报(2021-10-20)——定位并修复 Go 中的内存泄露
每日一谚:Go trusts the programmer to write down what is meant. go中文网每日资讯--2021-10-20 一.Go语言中文网 不要写破坏性的 G ...
- 现在的编译器还需要手动展开循环吗_一例 Go 编译器代码优化 bug 定位和修复解析...
缘起 某日,一位友人在群里招呼我,"看到有人给 Go 提了个编译器的 bug,挺有意思,感觉还挺严重的,要不要来看看?"于是我打开了 issue 40367 .彼时,最新一条评论是 ...
- 服务器拒绝访问如何定位及修复
服务器拒绝访问如何定位及修复 第一.问题定位 1.使用top查看当前系统资源占用情况,包括cpu.内存.硬盘 2.查看系统连接数据 netstat - an | awk '/^tcp/ {++S[ $ ...
- 死锁如何定位,修复?及其预防?
title : 每日深耕,勤练不缀之死锁定位,修复,预防 模拟定位问题,就选取常见的jstack,可以使用jps或者系统的ps命令.任务管理器等工具,确定进程ID 其次,调用jstack获取线程栈: ...
- SQL数据库无法附加 系统表损坏修复 数据库中病毒解密恢复
SQL数据库无法附加 系统表损坏修复 数据库中病毒解密恢复 开发此工具是为了 让手工恢复数据库物理故障时 更加简单便捷直观, 本工具用于物理修复独立处理大部分问题以及与DBCC配合完成修复各种数据库错 ...
- 综述|深度学习在SLAM定位与建图中的应用(近250篇参考文献)
作者丨Jasper@知乎 编辑丨Realcat 计算机视觉SLAM " 近年来深度学习被广泛应用于定位与建图中.相比于传统的手动建模方法,深度学习提供了一种数据驱动的解决方案,并逐步发展成 ...
- 综述|深度学习在SLAM定位与建图中的应用(近250篇参考文献)_3D视觉工坊的博客-CSDN博客
综述|深度学习在SLAM定位与建图中的应用(近250篇参考文献)_3D视觉工坊的博客-CSDN博客
- 如何修复 Flutter 中的“正在检查 Dart SDK 版本... << 此时出乎意料”错误?
如何修复 Flutter 中的"正在检查 Dart SDK 版本- << 此时出乎意料"错误? 每当我尝试从终端运行任何应用程序或 flutter 命令时,我都会遇到问 ...
- Java黑皮书课后题第9章:**9.13(Location类)设计一个名为Location的类,定位二维数组中的最大值及其位置。
Java黑皮书课后题第9章:**9.13(Location类)设计一个名为Location的类,定位二维数组中的最大值及其位置 题目 破题 代码 Test13 Test13_Location 运行结果 ...
最新文章
- 少儿编程python线上课程-少儿编程课堂|python – 用游戏学编程
- java set第n位_数据结构与算法——常用数据结构及其Java实现
- 移动端布局三种视口_移动端开发知识[系列] - 视口viewport
- Swift5.x的UITableView纯代码演练
- 流程平台:子表控件(二) - 属性、事件、方法
- 网络游戏中网络模块浅析
- 使用Python将字符串转换为格式化的日期时间字符串
- ping TCP端口的实用小工具tcping
- UI自动化测试工具-AirTest
- 基于《Arbitrary Style Transfer with Style-Attentional Networks》的视频风格迁移
- matlab uicontrol 居中,MATLAB有关uicontrol的问题~
- visual studio 2015无法打开包括文件
- 《高效能人士的7个习惯》PDF,复习笔记(上)
- 使用vba宏/python代码更新word目录——只更新页码
- 易语言雷电模拟器adb模块制作实现一键模拟器多开
- Android 获取照相机图片或本地图片
- 逾 200 家港企参与! GoGBA大湾区发展日(广州)圆满举行
- python对mp3音乐剪切
- VC 格式化代码快捷键
- 以python自定义模块的方式共享API(数据,短信通知、邮件通知,代理等)
热门文章
- 【Prometheus】问题记录
- 1.4 Set集合:HashSet和TreeSet类
- Python爬虫学习获取腾讯新闻并存入Excel
- ACM之【运算符重载结合STL】
- 2021暑假每日一题 【week8 完结】
- 【PAT乙级】1062 最简分数 (20 分)
- 第三届“传智杯”全国大学生IT技能大赛(初赛B组)【C++】
- Exception in thread “main“ org.apache.ibatis.exceptions.PersistenceException奇葩解决方案
- WdatePicker控件的使用
- java去除不为null,JAVA代码中去掉 “!=null”