加入收藏 | 设为首页 | 会员中心 | 我要投稿 汽车网 (https://www.0577qiche.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 移动互联 > 通讯 > 正文

iOS推送频繁掉线?12年技术负责人三步重构稳如磐石

发布时间:2026-11-03 08:09:26 所属栏目:通讯 来源:DaWei
导读:  掘金这地方,老熟人多,一开口就是“你家 APNs 连接池是不是又在凌晨三点自爆了”——不解释,不寒暄,就这句,够了。我原以为 2025 年 2 月这波掉线是证书轮转漏了,结果查到凌晨四点,发现连 token refresh 都没进 channel,lo

  掘金这地方,老熟人多,一开口就是“你家 APNs 连接池是不是又在凌晨三点自爆了”——不解释,不寒暄,就这句,够了。我原以为 2025 年 2 月这波掉线是证书轮转漏了,结果查到凌晨四点,发现连 token refresh 都没进 channel,log 里连一次 didRegisterForRemoteNotifications 的回调影子都没有。对讲机在桌上震了三下,没人接,后来才想起运维组今早调休,留着值班的是新来的实习生,正蹲机房吃盒饭。


  冷风从空调出风口直吹后颈,那台老 UPS 一直嗡——不是高频那种尖啸,是低频的、带点金属疲劳感的嗡,像十年前某次大促前夜它在顶楼机房发出来的声儿。我盯着监控看,眼看着三百七十二个设备在十分钟内陆续标记为 offline,而告警群已经九十九加,没人点开,更没人撤回。林工在 Slack 里发了个 ???? 表情,三分钟后又撤了——他当时在地铁过隧道,手机根本收不到推送,更别说 APNs 的心跳包了。


  第一步:砍掉所有重连逻辑里的 try-catch 包裹。不是加日志,不是降级,是直接删。这个我敢打包票:iOS 17.4 之后,APNs TCP 层一旦被中间网关 reset,任何 catch 住的 error 都不是 NSError.domain == NSURLErrorDomain 里那几个标准码,而是 CFNetworkErrorDomain 的 303 或 305,但 Swift 的 URLSessionTaskMetrics 就是不暴露它——我们以前还写了个 wrapper 想捕获,结果 runtime 打印出来全是 nil;后来翻了 iOS 18 beta3 的 dyld_shared_cache 符号表,确认是苹果故意阉了。所以别 wrap,别 log,别 alert——让 connection 直接 dead,dead 得干净,dead 得能被 watchdog 一眼认出来。


  食堂剩饭今天又是咖喱鸡块,汤里浮着一层油膜,筷子一搅就裂成细片。我盯着那层油,突然想起上周跟安卓组吵那一架:他们说“你们推不动就推不动呗,反正我们有 FCM 保底”,我说“你们 push token 过期率比我们 certificate 还高”,对方甩来一张图——MTTR 三十八秒,而我们当时是二百一十一秒。其实那图我没敢细看,因为后台日志里,真正失败的那批设备,metrics 时间戳全乱序,有两百多个 request 发起时间比 response timestamp 早了十七秒。我说不准这算不算时钟漂移,也没敢问 Apple 支持能不能复现——上次提 case,回复是 “Please refer to the Human Interface Guidelines”。


  第二步:把 deviceToken 注册流程从 UIApplicationDelegate 剥离,挪到独立的 PushManager 单例,且只允许 init 时注册一次。不是 lazy load,不是 on-demand,就是死规矩:new PushManager() → 立刻调用 register —— 不等 viewDidLoad,不等用户登录完成,不等网络状态监听器 ready。这个决定我犹豫了四天,因为怕冷启动时 network stack 还没 up,结果跑通那天发现,iOS 真的会在 UIApplication.isFinishedLaunching == false 的时候,就把 APNs socket 握上手了。我们之前所有 fallback 都卡在 “等登录成功后再 register”,结果用户登了半小时才连上推送,连上那一刻还顺手清空了 pending notification queue。林工当场在会议里摔了笔盖,滚到咖啡机底下,没人捡。


文章配图,仅供参考

  第三步最蠢,也最有效:所有 token refresh 请求,无论成功失败,都强制走一个全局队列串行。不是并发数设成 1,是真·串行——dispatch_semaphore_wait + dispatch_semaphore_signal,哪怕排队等到下一个静默期。这个改法上线前,测试组骂我反人类,说“你这是拿性能换稳定性”,我说“不是换,是认输”。因为去年双十一压测时,我们看到过同一批设备连续发出三个 identical device token 的 refresh request,APNs 后端返回三个 success,但实际只有最后一个生效。苹果不保证幂等,我们也不配假装它保证。


  现在?掉线周期拉长到平均四天半,最长一次撑了六天零八小时。但这不叫稳如磐石。石头不掉线,是因为它根本没连——而我们要连。所以我说“稳如磐石”是反话,是赌气,是跟当年那个信誓旦旦说“APNs 有内置重试”的实习生自己较劲。他现在在隔壁做 Rust 写嵌入式,微信头像换成电路板,朋友圈发“中断优先级没配对,硬件直接锁死”,我点了个赞,没评论。


  告警群还九十九加,不过今天我看见张姐发了一条:“刚才切生产证书,顺便摸了下 token 刷新链路——嗯,它还在跑。”她没截图,没附 log,就这一句。我回了个 ????,然后切去 Xcode 把 PushManager.init() 里那行 print(“PushManager: registered”) 给注释掉了。留着没用,反而容易被 QA 当成埋点漏测。


  UPS 还在嗡。我关了监控大屏,起身时碰倒了桌角那瓶快见底的风油精——瓶子滚了两圈,在键盘缝隙里停住,没碎,只是漏了一小滴,味道混着咖喱油味,在冷风里浮着。接下来两周,我要把后台的 token cache 从 Redis 挪到 SQLite,不为性能,就为 crash 后能本地 recover。这事我不打算写 RFC,也不想拉会。谁想看,自己 checkout develop 分支,搜 // MARK: - local token persistence,那里已经有三行注释,写得挺糙,但我懒得润色了。

(编辑:汽车网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!