背景
最近开发水印相机,遇到了个难缠的问题。这里记录分享一下。
之前上传图片功能的开发,一般都是修改用户头像之类的,所以印象中上传图片,没有什么难处理的,使用 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,再发起请求。