iOS Swift URLSession POST请求调用响应慢的API时出现重复发送问题
问题根因
iOS 系统底层的 URLSession 网络栈(尤其是后台会话由系统接管调度的场景),对于长时间未收到响应头的请求,会默认触发自动重试逻辑,这就是你看到相同时间戳的重复请求的来源,和上层业务代码无关。
你代码中存在的已知问题
- 会话配置修改不生效:
URLSessionConfiguration实例在传入URLSession初始化方法后就会变为只读状态,你在getMapUrlsFromServer方法中修改已创建会话的timeoutIntervalForRequest、timeoutIntervalForResource参数完全不会生效,相关配置必须在创建会话前就设置完成。 - 重复创建后台会话:你每次调用
download方法都会用相同的 identifier 创建新的后台URLSession,如果旧会话未调用invalidateAndCancel()销毁,系统会自动恢复旧会话中未完成的任务,可能导致重复请求。后台会话全局只需要创建一次即可,建议做成单例。 - 不必要的管线化配置:你开启了
request.httpShouldUsePipelining = true,管线化机制会在连接异常恢复后自动重试未完成的请求,对于长等待的非幂等POST请求会显著提升重复请求概率,这个配置完全不适合你的场景。
解决方案
客户端侧优化
- 优先修复上述代码问题:
- 后台
URLSession改为单例实现,所有配置在创建会话前完成设置 - 关闭
httpShouldUsePipelining,设置为false - 正确设置超时参数:
timeoutIntervalForRequest建议设置为120(连接/单次数据间隔超时),timeoutIntervalForResource设置为你能接受的最长总等待时间,比如1800秒(30分钟)
- 后台
- iOS 15及以上版本可以显式控制重试策略,明确禁止重试POST这类非幂等请求:
if #available(iOS 15.0, *) { config.retryPolicy = URLSessionRetryPolicy(neverRetry: [.post]) } - 从架构层面规避长连接等待:不要用同步等待的POST请求触发长耗时任务,改成异步调度模式:第一次请求服务端立刻返回唯一任务ID,客户端每隔固定时间轮询任务状态,待任务完成后再返回文件URL触发下载,从根源上避免长连接超时重试的问题。
- 添加请求幂等标识:每个请求生成全局唯一的Request ID,和请求参数一起传给服务端,就算出现重试,服务端也可以根据Request ID去重,不会重复执行文件生成逻辑。
服务端侧优化
- 长耗时请求先返回接收确认:收到这类生成大文件的请求后,立刻返回
202 Accepted状态码,告诉客户端请求已经被接收处理,让客户端网络栈不会认为请求丢失触发重试,待文件生成完成后再返回最终结果。 - 调整长连接保活配置:服务端(包括中间的Nginx、CDN等网关)开启TCP保活机制,每30秒发送一次保活包,避免连接因为长时间空闲被运营商网关、中间节点断开,连接断开会触发客户端自动重试。
- 实现请求幂等校验:根据客户端传的唯一Request ID做请求去重,相同ID的请求直接返回当前任务状态,不要重复触发文件生成任务。
内容的提问来源于stack exchange,提问作者Marc
相关产品推荐
相关产品推荐

