本文介绍: 一个 HTTP 超时问题最近有同事反映我们app网络正常的情况下偶尔会出现请求超时。我的第一反应是某个服务挂掉了(因为最近服务端再搞重构),就反馈给了服务层。但是服务层的同事排查下来发现 api 层并没有产生异常日志应该不是服务本身或者依赖的中台…

一个 HTTP 超时问题

最近有同事反映我们app网络正常的情况下偶尔会出现请求超时。
我的第一反应是某个服务挂掉了(因为最近服务端再搞重构),就反馈给了服务层。
但是服务层的同事排查下来发现 api 层并没有产生异常日志应该不是服务本身或者依赖的中台服务挂掉了。

定位

想起来 NSURLSession 有个默认的单个Host最大连接数,超过之后会进入排队,可能导致后续服务超时。

Objective-C

/* The maximum number of simultanous persistent connections per host */
@property NSInteger HTTPMaximumConnectionsPerHost;

利用 Xcode调试模式来看一下,结果惊掉下巴,随便点了几下就建立了这么多连接

说好的 The default value is 6 in macOS, or 4 in iOS 的呢,难道是Xcode调试面板问题吗?再次用 Wireshark 抓包确认一下,得到的结果是确实每次请求都会新建一个连接(主要体现在端口,TLS 协议)。

下面贴 2 个连接截图

端口 53929

端口 53930

这样肯定是有问题的:

1 HTTP 1.1 默认开启了 Keep-Alive 属性(这点在之前的 Charles 抓包中也有证实),为什么链接没有复用
2NSURLSession默认单个 Host 最大连接数在 iOS 上为 4,为什么会这么多。

直接代码吧,我们现在项目里有两套网络请求的框架一套是Objective-C的基于AFNetworking的老代码一套是为了向Swift迁移实现,一番折腾,在以前的基础库里找到了这段看似平平无奇的代码

Objective-C

AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:@""]];
[manager POST:api parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {...}

这段代码实现里 AFHTTPSessionManager 每次都是新创建出来的,虽然系统默认开启了 Keep-Alive ,但是 TCP 通道并不会复用,相当于一个披着 HTTP 1.1 外衣的 1.0连接,这就回答了第一个疑问。

至于第二个,我重新翻看了官方文档

This limit is per session, so if you use multiple sessions, your app as a whole may exceed this limit. Additionally, depending on your connection to the Internet, a session may use a lower limit than the one you specify.

意思就是这个最大连接数只是一个 NRLSession 的限制

修复

修复简单,我们把上面的 Manager 修改成了属性,保证了唯一性。完了使用 Xcode 的 Network 工具再看一下。嗯,看起来是复用了。

这里解决一个连接不复用的问题。分别观察了两个版本代码一段时间修改问题似乎不再出现了,旧版本的话用 Wireshark 抓到了详细的超时的时候数据包

看上去问题是去服务端握手时候服务端没回应,反馈服务端同事,一开始以为是单个 Linux 主机有个理论上的最大连接限制 65536之类的问题,后来觉得不可能。最终我们猜测是服务端之前设置了单个 api 的最大连接数,或者说防火墙那边会有一段时间内的单个 ip 的最大连接数限制造成的。这个问题的根本应该是在服务端,但是客户端修复了的连接方式会缓解这个问题。

验证

修改代码上线后,我们观察了 2 个版本线上没有再报类似的问题了。请求超时的错误率也稍有下降。嗯,最棒的是因为复用了连接 TCP 慢启动,TLS 握手都省了,我们请求时间也缩短了。相当于被动的做了优化

修复

修复

代码真不是随便写写,一不小心就埋雷。

其他
我们还发现了 iOS 上关于 TCP 挥手的行为异常,我们跟踪下来,发现了和其他一些疑似使用原生网络框架的连接,断开连接的时候都不是典型的四次挥手

如图客户端发送了 FIN ACK 之后并没有等到服务端回复 ACK 和 FIN ACK,直接关闭了连接,之后这个通道收到任何信息都会回复 RST 通知服务端关闭连接,一开始以为是异常,后来跟踪下来发现并不是,可能是 iOS 内部实现做了优化吧。

原文地址:https://blog.csdn.net/peterjava123/article/details/125361993

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_41784.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注