背景

最近开发水印相机,遇到了个难缠的问题。这里记录分享一下。

之前上传图片功能的开发,一般都是修改用户头像之类的,所以印象中上传图片,没有什么难处理的,使用 AFNetworking的 formData 进行上传,直接就可以了。但是这次用户大批量使用水印相机后大量反馈上传慢、上传不成功的问题。

排查

用户反馈有问题后,开始排查;在用户反馈上传不成功的同一时间,在开发环境和线上环境尝试拍摄上传均可正常上传。

网络问题

初步判断是网络问题,由于用户需要在工地现场使用此功能,所以猜测是用户网络环境的问题。建议用户尝试切换网络重新上传。同时让用户使用 SpeedTest.cn测速,发现部分用户月底流量限速网速被限制了,上传网速很低,导致上传不成功。

安全组策略限制

然而还是有部分用户,测速显示上传速度28M/s,但是上传依旧超时失败。继续排查后,有同事在测试环境出现了一直上传失败的情况,排查后发现安全组有策略:同一IP单位时间访问请求的数量超出几千次后,当前 IP 会被限制,任何操作都会限制。但是进一步排查后,发现线上因为这个原因被限制的用户并没有太多。

客户端超时

继续排查后,发现有用户反馈提示上传超时,但是实际上传成功。查看后发现,当网速不好时,客户端设置超时时间6秒,但是服务端的超时是12秒,所以当上传时间超出6秒时,客户端AFNetworking请求因超时,返回上传失败,但实际上传服务端成功的情况。针对这种情况,修改客户端超时时间大于等于服务端超时,即,上传超时的判断由服务端来判断而不是客户端。

并发请求问题

过程中还发现用户反馈,选择多张上传失败,单张上传能成功的情况。这种情况排查后发现,同样是网络不好的情况下,超时时间已修改为15秒,3G 网络,选择多张时上传失败,单张则可以上传成功。排查后发现是并发请求的问题。(最开始的多张照片是打包上传,即多张照片,在 AFN 的FormData中添加组合,然后使用一个请求发出,后来发现有上传失败后,服务端说照片的打包上传并没有意义,因为压缩不了大小,让客户端修改为一张一个请求)于是选择多张上传时,是每张照片一个请求,使用 DispatchGroup判断是否所有请求是否成功,相当于假设拍摄了9张照片,点击上传,是同时发起了9个上传请求,然后等待9个请求的结果,这对于上传网速不太好的用户,很大的概率出现上传失败。分析原因是,假设上传网速有50K,这50K 如果都用来上传同一张照片,可能15秒内上传成功;但是如果用来同时上传9张,则一张也成功不了。这也是网盘类 APP上传每次同时只有两三个任务开启的原因。针对这种情况,修改上传为NSOperationQueue队列上传,设置队列最大并发数为2。

token 失效被网关拦截

用户反馈上传失败问题时,服务端那边却看不到超时或者失败,甚至请求的日志也看不到。联系网络组协助排查后发现,有 token 过期网关拦截的日志,项目中的 token 有效期为7天,刷新 token 的逻辑与主工程的 Controller 绑定,而上传照片的项目是 CocoaPods 方式添加到项目中的,所以 token 过期后触发网络请求基类的通知,但是主工程的 Controller接受到后,判断当前页面没有显示就没有处理。针对这种情况,把 token 过期刷新逻辑从和 Controller 的绑定中抽出到单独的处理类,确保项目中任何地方的 token 过期都能够触发刷新处理逻辑。

localDNS 为空

同时,从听云后台看到,针对上传接口失败的日志,有些请求中的 localDNS 为空,针对这种没有 localDNS 的情况,项目添加阿里的 HTTPDNS,每次上传前,获取上传域名接口的 IP,然后替换请求链接中的域名,设置域名到 Host,再发起请求。

总结

上传图片失败.png