编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • 性能优化实践

    • README
    • 公共方法论

    • 体系建设篇

    • 资源专项篇

    • 流水线专项

    • 业务专项篇

      • App冷启动优化
      • 网络性能分析优化
        • 01.阅读说明
        • 02.贯穿案例
          • 2.1 案例背景
          • 2.2 经验派 3 周折腾(典型反面教材)
          • 2.3 方法派 7 天闭环
          • 2.4 上线效果
          • 2.5 案例如何串起本文
        • 03.网络本质定义
          • 3.1 网络的物理本质
          • 3.2 网络性能的现象与代价
          • 3.3 度量准则
          • 3.4 行业基准与目标
          • 3.5 8 个反直觉问题
        • 04.协议层数学原理
          • 4.1 协议开销基本公式
          • 4.2 RTT vs 带宽:为什么减 RTT 更重要?
          • 4.3 TCP 慢启动数学
          • 4.4 弱网指数放大原理
          • 4.5 跨平台同构原理
        • 05.度量与采集
          • 5.1 三类捕获方案
          • 5.2 各方案的可见盲区
          • 5.3 跨平台采集对照表
          • 5.4 数据可信度评估
        • 06.归因决策树
          • 6.1 网络问题决策树
          • 6.2 阶段拆解归因
          • 6.3 弱网模式归因
          • 6.4 失败重试归因
        • 07.DNS 全链路 ⭐
          • 7.1 DNS 物理本质
          • 7.2 系统 DNS 的隐藏陷阱
          • 7.3 HttpDns 工作原理
          • 7.4 IP 池 fallback 设计
        • 08.TCP/TLS 全链路 ⭐
          • 8.1 TCP 三次握手
          • 8.2 TLS 握手详解
          • 8.3 TCP 慢启动的影响
          • 8.4 长连接 keepalive
          • 8.5 跨平台 TCP/TLS 实现差异
        • 09.HTTP 全链路 ⭐
          • 9.1 HTTP/1.1 与队头阻塞
          • 9.2 HTTP/2 多路复用
          • 9.3 HTTP/3 与 QUIC
          • 9.4 长连接 vs push 的选择
        • 10.连接池全链路 ⭐
          • 10.1 连接池工作原理
          • 10.2 连接池大小的科学化
          • 10.3 网络切换处理
          • 10.4 连接池监控
        • 11.弱网全链路 ⭐
          • 11.1 弱网的物理本质
          • 11.2 弱网识别方法
          • 11.3 动态超时
          • 11.4 指数退避重试
          • 11.5 降级链路
        • 12.跨端网络对照
          • 12.1 协议栈对照
          • 12.2 网络治理能力对比
          • 12.3 统一启示
        • 13.治理一层协议
          • 13.1 一层命题
          • 13.2 策略 1.1:DNS 优化(HttpDns + 预解析 + IP 池)
          • 13.3 策略 1.2:HTTP/3 + TLS 1.3 + 0-RTT
          • 13.4 策略 1.3:连接复用(连接池 + keepalive)
          • 13.5 策略 1.4:数据压缩与协议升级
          • 13.6 一层反思
        • 14.治理二层调度
          • 14.1 二层命题
          • 14.2 策略 2.1:请求合并与 BFF 聚合
          • 14.3 策略 2.2:依赖图分析与并发化
          • 14.4 策略 2.3:缓存策略(HTTP + 业务双层)
          • 14.5 策略 2.4:预取与预测
          • 14.6 二层反思
        • 15.治理三层弱网
          • 15.1 三层命题
          • 15.2 策略 3.1:弱网识别 + 动态超时
          • 15.3 策略 3.2:指数退避重试 + 错误码白名单
          • 15.4 策略 3.3:降级链路与离线 fallback
          • 15.5 策略 3.4:CDN 与边缘部署
          • 15.6 三层反思
        • 16.治理四层监控
          • 16.1 四层命题
          • 16.2 策略 4.1:阶段拆解监控
          • 16.3 策略 4.2:地区 × 网络类型维度看板
          • 16.4 策略 4.3:弱网占比与失败码分类
          • 16.5 优先级判定(ROI)
          • 16.6 四层反思
        • 17.求证实验
          • 17.1 实验一:握手开销
          • 17.2 实验二:弱网超时策略
          • 17.3 实验三:连接复用收益
          • 17.4 实验四:HTTP/3 在弱网下的相对增益
          • 17.5 实验五:请求合并与批处理的吞吐增益
          • 17.6 五大实验启示
        • 18.实战案例
          • 18.1 跨端同构案例:依赖图分析与并发化
          • 18.2 平台特异案例:Android HttpDns 接入
          • 18.3 弱网治理案例:地铁 App 失败重试雪崩
          • 18.4 案例统一启示
        • 19.防劣化体系
          • 19.1 三道防线总览
          • 19.2 编码期 Lint
          • 19.3 CI 卡口与线上 SLO
          • 19.4 监控数据闭环
        • 20.跨平台速查
          • 20.1 工具速查
          • 20.2 关键 API 速查
          • 20.3 通用 SLO 速查
        • 21.总结与延伸
          • 21.1 五条核心原则
          • 21.2 五个常见误区
          • 21.3 三个外延
          • 21.4 给团队的建议
          • 21.5 延伸阅读
        • 一句话总结
      • 图片性能解码优化
      • 列表与滚动性能
      • 功耗与电量优化
    • 交付防御篇

  • 程序编程原理

  • 稳定性与可靠性

  • 工程化与运维

  • 方案设计思想

  • 专栏
  • 性能优化实践
  • 业务专项篇
杨充
2026-05-27
目录

网络性能分析优化

# 网络性能分析与优化

📊 学习成本预估 | 难度:⭐⭐⭐⭐(4/5)| 阅读:约 50 分钟 | 实操:3 小时 🔗 前置阅读:卷零·02 | ➡️ 后续延伸:卷五·04

# 目录介绍

  • 01.阅读说明
  • 02.贯穿案例
  • 03.网络本质定义
  • 04.协议层数学原理
  • 05.度量与采集
  • 06.归因决策树
  • 07.DNS 全链路 ⭐
  • 08.TCP/TLS 全链路 ⭐
  • 09.HTTP 全链路 ⭐
  • 10.连接池全链路 ⭐
  • 11.弱网全链路 ⭐
  • 12.跨端网络对照
  • 13.治理一层协议 ⭐
  • 14.治理二层调度 ⭐
  • 15.治理三层弱网 ⭐
  • 16.治理四层监控 ⭐
  • 17.求证实验 ⭐
  • 18.实战案例
  • 19.防劣化体系
  • 20.跨平台速查
  • 21.总结与延伸

# 01.阅读说明

  • 本文卷归属:卷四 · 业务专项 · 第 2 篇
  • 本文目标层级:L2 进阶 → L3 专家
  • 适用平台:Android / iOS / Web / 嵌入式(联网设备)
  • 前置阅读:
    • 卷零·02 跨平台性能模型与指标体系
  • 本文核心命题:

    网络性能 = 协议层效率 × 业务层调度 × 弱网容错 × 监控可观测——四个维度缺一不可。 协议层(DNS / TCP / TLS / HTTP)决定每次请求的最小代价;业务层(请求合并 / 缓存 / 并发)决定如何最少地用网络。 一切优化都是在这四个维度上"减少请求数 × 减少每次请求开销 × 防弱网雪崩 × 看见问题"。


# 02.贯穿案例

本案例贯穿全文:§03 看懂物理本质、§04 用数学模型量化、§07-§11 拆解各协议层原理、§17 用实验复盘、§13-§16 给出分层闭环。

# 2.1 案例背景

某跨境电商 V5.2 出海东南亚(印尼/越南/菲律宾),首屏体验在国内一片好评,到了东南亚却问题暴露:

  • 印尼 4G 用户首屏 P50 = 3.8s,P95 = 8.2s。
  • 失败率从国内 0.3% 飙到 6.7%。
  • 应用商店一周内涌入大量"打不开"差评,下载转化率掉 18%。
  • BD 团队压力极大:每天损失约 80 万 GMV。

研发组初步排查:"是当地网络差,没办法。"——这是典型的"环境无解论"。

# 2.2 经验派 3 周折腾(典型反面教材)

周次 动作 结果
第 1 周 把超时从 10s 改 60s(怀疑超时太短) 失败率反而升到 8.1%:用户等不及自己关了页面
第 2 周 加 5 次激进重试(怀疑偶发失败) 服务端被打到限流,部分用户彻底打不开
第 3 周 把首屏 6 个 API 改成全并发(怀疑串行慢) 弱网下连接数过多,TCP 全部握手失败

复盘:三周里所有动作都基于"加大-加多-加快"的直觉,本质是把强网思维硬套到弱网。弱网不是"网慢点的强网",是物理特性完全不同的环境。

# 2.3 方法派 7 天闭环

新接手的同学按本文方法论重做:

Day 1(§04 数学原理):用"距离 × 带宽"模型重新理解。中国服务端 → 印尼 RTT = 80ms(光速近极限),但当地运营商 4G 平均丢包 8%、RTT 抖动 50-300ms。这是"高 RTT + 高丢包"的典型 C 类问题。

Day 2(§05 三方案组合):方案①+②+③同时上:

  • 方案①:OkHttp Interceptor 上报每次请求的总耗时和错误码。
  • 方案②:EventListener 拆 DNS / TCP / TLS / TTFB / 传输 5 阶段。
  • 方案③:mitmproxy 抓包看真实数据包行为。

抓到的关键数据(弱网首屏 6 个 API):

阶段 平均耗时 占比
DNS(系统) 850 ms 22%
TCP 握手 240 ms 6%
TLS 握手 620 ms(带丢包重传) 16%
TTFB 380 ms 10%
传输 1700 ms(响应体大 + 丢包重传) 45%
总单次 ≈ 3.8s 100%

→ DNS 慢 + TLS 重传 + 响应体太大三大头部问题。

Day 3(§06 决策树):4 类问题分别走不同路径:

  • DNS 慢 → 阶段 A(DNS 优化)→ 用 HttpDns + IP 池 fallback。
  • TLS 慢 → 阶段 B(连接优化)→ HTTP/3 0-RTT + TLS 1.3 + Session Resumption。
  • 响应体大 → 阶段 D(数据优化)→ Protobuf + brotli + 字段裁剪。
  • 弱网放大 → 弱网容错分支 → 智能超时 + 指数退避 + 服务端边缘部署。

Day 4(§13-§16 四层治理):

  • 第 1 层(协议层):HttpDns + HTTP/3 + TLS 1.3 + 边缘 CDN(雅加达节点)。
  • 第 2 层(业务层):6 API 合并为 2 个聚合 API;首屏关键资源 inline 到 HTML。
  • 第 3 层(弱网容错):超时按弱网识别动态调整(强网 5s / 弱网 15s);指数退避重试 1s/3s/9s;最多 2 次。
  • 第 4 层(监控):地区维度看板,按"国家×网络类型"细分。

Day 5-6(§17 求证实验 思路验证):构造印尼 4G 流量回放,灰度对比验证。

Day 7(上线复盘)。

# 2.4 上线效果

指标 经验派 3 周后 方法派 7 天后
印尼首屏 P50 3.8s 1.1s
印尼首屏 P95 8.2s 2.4s
失败率 8.1% 0.6%
GMV/日 -80 万(损失) +45 万(同比涨)
应用商店评分 3.4 4.6

核心洞察:弱网治理 ≠ 强网思维 + 超时拉长。HTTP/3 的 0-RTT 在丢包场景下让首屏 TLS 阶段从 620ms→0ms(复用 Session),是单次最大收益。这正是 §17.4 实验 工程意义"首次连接慢可用 HTTP/3 0-RTT 缓解"的真实落地。

# 2.5 案例如何串起本文

  • §03 网络本质 ▶▶ 网络是"距离 × 带宽 × 协议开销"的硬约束。
  • §04 数学原理 ▶▶ RTT × 协议次数 = 物理下限。
  • §07-§11 各协议全链路 ▶▶ DNS / TCP / TLS / HTTP / 弱网 各自的物理原理与瓶颈。
  • §06 决策树 ▶▶ 4 个根因走 4 条路径。
  • §17 求证实验 ▶▶ §17.1 握手开销、§17.2 弱网超时、§17.4 HTTP/3 增益都在案例中变现。
  • §13-§16 四层治理 ▶▶ "协议→业务→弱网→监控"四层正是案例落地路径。

# 03.网络本质定义

# 3.1 网络的物理本质

网络性能 = 单位时间能完成多少有效数据传输 + 平均每次传输的延迟。

三个不可商量的物理约束:

约束一:网络是"距离 × 带宽"的硬约束

物理光速决定了最小 RTT(Round Trip Time):

   北京 → 美西:~150ms RTT(光速理论下限 ~100ms + 路由延迟)
   北京 → 上海:~30ms RTT
   北京 → 北京:~5-20ms RTT
1
2
3

这是物理硬约束:任何"加速"都不能让数据包跑得比光快。优化只能是"减少往返次数"。

约束二:网络协议有固定开销

每次连接都要:

   DNS 查询    50-500ms(视缓存)
   TCP 握手    1 RTT(~30-150ms)
   TLS 握手    1-2 RTT(HTTPS)
   HTTP 请求   1 RTT(请求 + 响应)
   ─────────────────────────────────
   首次请求    3-5 RTT = 100-700ms(仅协议开销)
1
2
3
4
5
6

这就是为什么"连接复用"如此重要:复用连接省去 DNS + TCP + TLS 三次握手。

约束三:弱网会指数级放大问题

弱网(高 RTT + 高丢包)下,TCP 重传 + TLS 握手丢包都会让单次请求时间从 100ms 涨到几秒甚至超时。弱网治理是网络优化的最大变量。

探索性思考:为什么"5G 时代"网络性能问题没消失? 因为 5G 解决的是带宽,不是 RTT——光速没变。多数业务延迟瓶颈是 RTT 不是带宽。一个 1KB JSON 在 5G 和 4G 上传输时间差不多,因为还是被 RTT 主导。真正消失的瓶颈是大文件下载——但首屏类业务很少传大文件。所以 5G 对视频流、文件下载改善大,对API 响应改善小。

# 3.2 网络性能的现象与代价

网络性能问题的用户感知:

  • 首屏加载慢:API 慢 → 页面白屏 / 骨架屏停留过久。
  • 图片加载慢 / 闪烁:图片下载慢 / 失败重试。
  • 操作无响应:点击后等待 API 返回,无 loading 反馈。
  • 离线 / 弱网"假死":弱网下请求长时间不返回又不超时。
  • 流量浪费:重复请求、压缩不充分、协议头冗余。

业务代价:

  • 头部应用:API P99 每降 100ms,留存 +0.5%、转化率 +1%。
  • 弱网下用户操作放弃率比强网高 5-10 倍。
  • 流量过度消耗会让用户主动停用应用(特别是预付费 / 国际场景)。

▶▶ 回扣 §02 案例:跨境电商东南亚弱网首屏 P50=3.8s 直接对应"放弃率比强网高 5-10 倍"的真实样本——失败率从国内 0.3% 飙到 6.7%,每天 80 万 GMV 流失。这就是"业务代价"的钱袋打击。

# 3.3 度量准则

按 卷零·02 §3 指标体系:

资源视角(USE):

指标 含义 阈值参考
带宽利用率 实际下载速率 / 链路上限 > 60%(理想情况)
连接数饱和 同时活跃 TCP 连接 < 10
失败率 请求失败 / 总请求 < 1%

请求视角(RED):

指标 含义 阈值参考
API P50 中位响应时长 < 200ms
API P95 95% 分位 < 800ms
API P99 长尾 < 2s
API 失败率 < 0.5%
弱网超时率 仅弱网下 < 5%

用户感知(APDEX):

  • Satisfied:API < 1s
  • Tolerating:1-3s
  • Frustrated:> 3s 或失败

# 3.4 行业基准与目标

平台 API P50 API P99 失败率
Android / iOS(4G) 200ms < 1s < 0.5%
Web(4G) LCP < 2.5s TTI < 5s < 1%
弱网(< 100Kbps) 1s < 5s < 5%

# 3.5 8 个反直觉问题

带着这些问题阅读:

  1. HTTPS 比 HTTP 慢多少?是不是必须用 HTTPS?
  2. HTTP/2 一定比 HTTP/1.1 快吗?
  3. 多请求并发是不是越多越快?
  4. 连接复用真的能省那么多吗?
  5. 弱网下重试会让情况变好还是变坏?
  6. CDN 加速真的有用吗?
  7. WebSocket 比 HTTP 长轮询快多少?
  8. DNS 查询为什么有时几百毫秒?

# 04.协议层数学原理

本节回答四个根本问题:①协议开销如何累加?②为什么"减 RTT 比加带宽"更重要?③TCP 慢启动的数学影响?④弱网为何指数级放大?

# 4.1 协议开销基本公式

   总耗时 T = T_dns + T_tcp + T_tls + T_ttfb + T_transfer
            
            = (DNS 查询) + (1 RTT TCP) + (1-2 RTT TLS) + (1 RTT 等待) + (体积/带宽)

   首次请求最小 T = DNS + 3-4 × RTT + 体积/带宽
1
2
3
4
5

三个推论:

  1. 首次请求被 RTT 主导:100ms RTT 下首次请求 ≥ 400ms(仅协议开销)。
  2. 复用后被 TTFB 主导:仅 1 个 RTT,~50ms。
  3. 大文件被带宽主导:1MB 体积 / 1MB/s 带宽 = 1s。

# 4.2 RTT vs 带宽:为什么减 RTT 更重要?

场景 RTT 影响 带宽影响
API 请求(1KB) 100ms RTT × 4 = 400ms 1KB / 100Mbps ≈ 0ms
网页(500KB) 100ms RTT × 6 = 600ms 500KB / 100Mbps = 40ms
视频流(10MB) RTT 几乎不影响 10MB / 10Mbps = 8s

关键洞察:移动端 API 业务被 RTT 主导,不是带宽。这就是为什么"5G 比 4G 快"在 API 体验上差异不大——但视频/下载体验差异巨大。

探索性思考:为什么"减少请求数"比"加大带宽"收益更大? 因为每次请求都要 N 个 RTT。减少 1 个请求 = 节省 100-400ms。但加大带宽 10×——对小数据来说几乎没影响。这是 §17.5 实验 中"6 个 API 合 1 个,弱网延迟 -64%"的根因——本质是省了 5 个 RTT。

# 4.3 TCP 慢启动数学

TCP 不会立刻用满带宽——而是从小窗口逐步增长:

   慢启动阶段:cwnd = 1 → 2 → 4 → 8 → 16 → ... 段(每个 RTT 翻倍)
   
   假设 MSS = 1460 字节,初始 cwnd = 10(Linux 默认):
   RTT 1: 14.6 KB
   RTT 2: 29 KB
   RTT 3: 58 KB
   RTT 4: 116 KB
   ...
1
2
3
4
5
6
7
8

实际影响:

   下载 100KB 文件:
   - cwnd 增长到能装下 100KB 需要 4 个 RTT
   - 总耗时 ≈ 4 × RTT
   - RTT = 100ms → 400ms
1
2
3
4

优化策略:

  • 增大初始 cwnd:服务端配置(Linux 4.x+ 默认 10 已不错)。
  • TLS 1.3 预热:握手期已经传一些数据。
  • HTTP/2/3 多路复用:复用已经"暖好"的连接。

# 4.4 弱网指数放大原理

弱网的物理特征:高 RTT(300ms+)+ 高丢包(5%+)+ 不稳定。

TCP 重传指数放大:

   原始时间 = N × RTT (N 段数据)
   1% 丢包 → 平均多 0.05 × RTT(一次重传 0.5 RTT)
   5% 丢包 → 平均多 0.5 × RTT
   10% 丢包 → 平均多 2 × RTT  ← 因为可能连续丢包,进 RTO
   20% 丢包 → 进入 RTO 超时(1-3s 重传等待)
1
2
3
4
5

TLS 握手丢包:1 个握手包丢失 = 重新整个握手链,单次握手从 200ms → 2-3s。

应用层重试:弱网失败用户会"自己点重试",让本就拥塞的网络雪上加霜——这就是 §02 案例 经验派第 2 周翻车的根因。

探索性思考:为什么 TCP 在弱网下"先天劣势"? 因为 TCP 的拥塞控制假设丢包 = 网络拥塞——所以一丢包就大幅降速。但无线网络的丢包很多是"信号差"不是"拥塞"——降速反而让数据传得更慢。这是 TCP 设计于 1980 年代有线网络环境的历史包袱。HTTP/3 (QUIC) 部分解决了这个问题——可以区分两种丢包,更智能地恢复。

# 4.5 跨平台同构原理

所有平台的网络栈都基于 TCP/IP,应用层都是 HTTP / WebSocket / 自定义协议:

   通用网络模型:

      [应用 API]   →   [HTTP 客户端]   →   [TLS]   →   [TCP/UDP]   →   [网卡]
       业务请求          OkHttp / URLSession   SSL/TLS    内核网络栈   硬件
1
2
3
4

每个平台都必须有:

抽象组件 解决什么问题
HTTP 客户端 封装 HTTP 协议细节
连接池 复用 TCP / TLS 连接
DNS 缓存 减少 DNS 查询
重试策略 弱网容错
超时控制 防止永久阻塞

跨平台术语对照

通用术语 Android iOS Web 嵌入式
HTTP 客户端 OkHttp / Retrofit URLSession / Alamofire fetch / axios libcurl
连接池 OkHttp ConnectionPool URLSession 自动 浏览器自动 自实现
长连接 OkHttp WebSocket URLSessionWebSocketTask WebSocket API 自实现
HTTP/2 OkHttp 自动 URLSession 自动 浏览器自动 视库
QUIC / HTTP/3 Cronet URLSession iOS 15+ 部分浏览器 视库

# 05.度量与采集

# 5.1 三类捕获方案

   请求发起 ──▶ DNS / TCP / TLS ──▶ HTTP 传输 ──▶ 响应解析 ──▶ 业务回调
       │                                                            │
       ▼                                                            ▼
   ① 客户端打点(每请求埋点)
                ② 阶段拆解(系统提供的 timing API)
                            ③ 抓包分析(Charles / Wireshark / 自定义)
1
2
3
4
5
6

① 客户端打点

  • 核心原理:HTTP 客户端拦截器记录每个请求的开始 / 结束 / 错误。
  • 物理本质:从应用视角记录每个请求生命周期。
  • 局限根源:看不到协议细节(哪一阶段慢)。
  • 适用场景:所有应用的核心监控方案。

② 阶段拆解(Timing API)

  • 核心原理:使用系统提供的 timing API 分解出 DNS / TCP / TLS / 等待 / 响应 / 解析 各阶段耗时。
  • 物理本质:协议栈各阶段对应用不透明,必须借助系统 API。
  • 适用场景:归因每次"为什么慢"。
平台 API
Android OkHttp EventListener(dnsStart/dnsEnd/connectStart...)
iOS URLSession URLSessionTaskTransactionMetrics
Web PerformanceResourceTiming

③ 抓包分析(线下深度)

  • 核心原理:在网络层抓取所有数据包,离线分析协议级行为。
  • 物理本质:在数据包级别看真相。
  • 适用场景:线下排查协议问题、抓"看不到的"行为。

三种方案的总览

方案 钩子位置 数据粒度 性能开销 跨端通用性 线上可用 主要局限
① 客户端打点 应用 HTTP 库 请求级 极低 高 ✅ 不知协议细节
② 阶段拆解 系统 API 阶段级 极低 中 ✅ API 不一致
③ 抓包 网络层 包级 低 极高 ❌ 需配置

方案的"组合定律":①+②+③ 必须组合——①给业务视角,②给协议级归因,③给数据包级真相。

探索性思考:为什么客户端打点经常"显示 100ms 但用户感知 1s"? 因为客户端打点只看 HTTP 库的入口和出口——但用户感知还包含:

  1. UI 框架的延迟(异步切回主线程)。
  2. 数据解析(JSON 反序列化、UI 更新)。
  3. 重试/降级链路。

真正的"用户感知延迟"应该从"用户操作"开始打点到"屏幕变化完成"——这才是"端到端"延迟。这种"业务延迟" vs "网络延迟"的差异是体验治理的关键。

# 5.2 各方案的可见盲区

现象 方案 ① 方案 ② 方案 ③
API 总耗时 ✅ ✅ ✅
哪阶段慢(DNS/TCP/TLS) ❌ ✅ ✅
重传 / 丢包 ❌ 部分 ✅
加密包内容 ❌ ❌ ✅(带证书)
HTTP/2 多路复用细节 ❌ 部分 ✅

# 5.3 跨平台采集对照表

维度 Android iOS Web
客户端打点 OkHttp Interceptor / EventListener URLProtocol fetch wrapper / Performance Resource API
阶段拆解 EventListener.dnsStart/connectStart... URLSessionTaskTransactionMetrics PerformanceResourceTiming
弱网模拟 adb / 第三方 / Charles Network Link Conditioner DevTools Network Throttling
抓包 Charles + 系统 CA Charles + iOS CA DevTools Network

# 5.4 数据可信度评估

数据 可信度 偏差来源
API 总耗时 高(< 1%) 客户端时钟
阶段拆解 高 系统 API 准确
弱网识别 中 网络类型不准确(4G 可能很慢)
失败原因 中 错误码合并损失信息

# 06.归因决策树

# 6.1 网络问题决策树

网络问题
   │
   ├── 阶段 A. DNS 慢(> 200ms)──▶ DNS 优化(§07)
   │                              ├─ HttpDns 替代系统 DNS
   │                              └─ DNS 缓存 + 预解析
   │
   ├── 阶段 B. TCP / TLS 慢(> 500ms)──▶ 连接优化(§08)
   │                              ├─ 连接复用(keepalive)
   │                              ├─ HTTP/2 多路复用
   │                              └─ HTTP/3 (QUIC) 0-RTT
   │
   ├── 阶段 C. 服务端处理慢(TTFB > 300ms)──▶ 服务端优化
   │                              ├─ CDN / 边缘
   │                              └─ 后端逻辑优化
   │
   ├── 阶段 D. 响应传输慢(大体积)──▶ 数据优化
   │                              ├─ 压缩(gzip / brotli)
   │                              ├─ 协议升级(PB / FlatBuffer)
   │                              └─ 减少返回字段
   │
   └── 弱网放大 ──────────────────▶ 弱网容错(§11)
                                  ├─ 合理超时
                                  ├─ 降级 / 缓存
                                  └─ 重试策略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

▶▶ 回扣 §02 案例:东南亚首屏 6 API 的阶段拆解恰好命中 4 条分支——DNS 850ms 走阶段 A;TLS 620ms 走阶段 B;响应体 1700ms 走阶段 D;同时整体走"弱网放大"分支。经验派只盯一条分支必然失败,方法派 7 天闭环对 4 条分支同时施治才取得首屏 3.8s→1.1s 的成果。

# 6.2 阶段拆解归因

典型阶段时长占比:

   首次请求(无连接复用):
   DNS         50-200ms    20%
   TCP         30-150ms    15%
   TLS         60-300ms    30%
   等待 TTFB   50-300ms    25%
   传输        视体积       10%

   连接复用后:
   等待 TTFB   50-300ms    70-90%
   传输                    剩余
1
2
3
4
5
6
7
8
9
10

归因思路:先看哪一阶段最长,再针对性优化。

# 6.3 弱网模式归因

弱网的物理特征:

  • 高 RTT(300ms+)
  • 高丢包率(> 5%)
  • 低带宽(< 100Kbps)
  • 不稳定(速度波动大)

弱网下的典型问题:

  • TCP 重传指数级增长(一个丢包导致整个 window 重传)。
  • TLS 握手丢包让加密失败。
  • 长任务超时(默认 60s 太长)。
  • 用户已经放弃但请求仍在等待。

弱网识别方法:

  • 系统 API:Android NetworkCapabilities / iOS Reachability。
  • 主动探测:定期请求小固定大小数据,测量延迟。
  • 业务感知:连续 N 次请求慢即判弱网。

# 6.4 失败重试归因

重试是双刃剑:

  • 善用重试:偶发失败一次重试解决问题。
  • 滥用重试:本来就慢的网络被重试雪上加霜。

正确的重试策略:

  • 指数退避(Exponential Backoff):1s, 2s, 4s, 8s。
  • 最大重试次数(3-5 次)。
  • 特定错误码才重试(5xx 重试 / 4xx 不重试)。
  • 幂等性检查:POST 谨慎重试(可能重复创建)。

探索性思考:为什么"用户主动重试"是雪崩源头? 因为弱网下 OS 已经在重传,应用层重试又加一份。用户看到失败再点 = 第三份。三份数据包一起挤进本就拥塞的网络——彻底打死。正确做法:弱网识别后主动告诉用户"网络差,正在为你智能重试"——把"用户冲动"转化为"应用调度"。


# 07.DNS 全链路 ⭐

本章把 DNS 从"域名解析"一路拆到"IP 池 fallback",回答:DNS 为何 850ms / 系统 DNS 的隐藏陷阱 / HttpDns 工作原理。

# 7.1 DNS 物理本质

DNS 的工作原理:

   应用调 getaddrinfo("api.example.com")
       │
       ▼
   系统 DNS 解析器(Bionic / Apple DNS / browser DNS)
       │
       ├─ 缓存命中 → 直接返回(< 1ms)
       │
       └─ 缓存未命中
              │
              ▼
       查询本地配置 DNS 服务器(运营商 / 公司 / Google 8.8.8.8)
              │
              ▼
       递归查询:根 → TLD → 权威 → 返回 IP
              │
              ▼
       缓存到本地(TTL 决定保留时间)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

典型耗时:

  • 缓存命中:< 1ms。
  • 国内:50-100ms(运营商 DNS)。
  • 跨境:100-500ms(递归查询多次)。
  • 弱网:500-2000ms(丢包重传)。

# 7.2 系统 DNS 的隐藏陷阱

陷阱一:DNS 劫持

国内运营商 DNS 经常返回错误结果:

  • 广告 IP(劫持后插广告)。
  • 拦截 IP(运营商屏蔽某些域名)。
  • 无效 IP(返回但连不通)。

陷阱二:DNS 缓存 TTL 失控

  • TTL 设短了(< 30s):每次都重新查询。
  • TTL 设长了(> 1h):服务端切 IP 后用户访问错误 IP。

陷阱三:UDP 不可靠

DNS 默认走 UDP——丢包就是丢包,没有重传保证。弱网下 DNS 经常超时。

# 7.3 HttpDns 工作原理

HttpDns 走 HTTP 协议查询权威服务器,跳过运营商 DNS:

   应用调 HttpDns SDK(如阿里 / 腾讯 / 自建)
       │
       ▼
   HTTP GET https://dns.api.com/d?host=api.example.com
       │
       ▼
   返回 JSON:{ "ips": ["1.2.3.4", "5.6.7.8"], "ttl": 600 }
       │
       ▼
   应用拿到 IP 直接连
1
2
3
4
5
6
7
8
9
10

优势:

  • 不被运营商劫持——HTTP 数据不会被运营商修改(TLS 加密时)。
  • 可控的 TTL ——服务端可主动 push invalidate。
  • 多 IP 返回——客户端可选最快的连。
  • 跨地域调度——服务端根据用户 IP 返回最近节点。

劣势:

  • HttpDns 服务本身可能挂——必须有系统 DNS fallback。
  • 冷启动需要先解析 dns.api.com——本身又要走系统 DNS(鸡生蛋)。

应对:

class HttpDnsResolver : Dns {
    private val cache = mutableMapOf<String, Pair<List<InetAddress>, Long>>()
    private val staticBootstrapIps = listOf("203.107.x.x", "...")  // 内置 IP 启动

    override fun lookup(hostname: String): List<InetAddress> {
        // 优先 HttpDns
        cache[hostname]?.let { (ips, expireAt) ->
            if (System.currentTimeMillis() < expireAt) return ips
        }
        try {
            val ips = httpDnsClient.resolve(hostname)
            cache[hostname] = ips to (System.currentTimeMillis() + 600_000)
            return ips
        } catch (e: Exception) {
            // fallback 到系统 DNS
            return Dns.SYSTEM.lookup(hostname)
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 7.4 IP 池 fallback 设计

进阶:服务端返回多个 IP,客户端按优先级 + 健康度选用:

   ips = ["主 IP 1", "主 IP 2", "备用 IP 1", "兜底 IP"]
       │
       ▼
   优先连接主 IP 1
       │
       ├─ 成功 → 用
       │
       └─ 失败 → 主 IP 2 → 备用 IP 1 → 兜底 IP
1
2
3
4
5
6
7
8

收益:§02 案例 DNS 阶段 850ms→<50ms。

探索性思考:为什么"DNS 优化"在国外应用很少强调? 因为国外(特别是 Google/Cloudflare 8.8.8.8 等)DNS 服务质量稳定,且不存在大规模运营商劫持。国内 HttpDns 几乎是必选——是被运营商劫持环境逼出来的工程方案。这是中国互联网工程的"特色基础设施"——每个出海/国内应用都要面对的现实。


# 08.TCP/TLS 全链路 ⭐

本章把 TCP/TLS 从"三次握手"一路拆到"0-RTT",回答:TCP 慢启动如何拖慢首次请求 / TLS 1.3 为何省一个 RTT / 0-RTT 的安全代价。

# 8.1 TCP 三次握手

   Client                            Server
     │                                  │
     │ ──── SYN ────────────────────▶  │  RTT/2
     │                                  │
     │ ◀──── SYN+ACK ───────────────  │  RTT
     │                                  │
     │ ──── ACK ────────────────────▶  │  RTT*1.5
     │                                  │
     │ ──── HTTP Request ───────────▶  │
     │ ◀──── HTTP Response ─────────  │
1
2
3
4
5
6
7
8
9
10

最少 1 RTT 完成 TCP 握手——这是物理下限。

TCP Fast Open(TFO):让握手包带数据,省 1 RTT。但部分网络中间件不支持,落地有限。

# 8.2 TLS 握手详解

TLS 1.2(旧):2 RTT 握手

   Client                            Server
     │                                  │
     │ ── ClientHello ──────────────▶  │
     │ ◀── ServerHello + Cert ─────  │  RTT 1
     │ ── KeyExchange + Finished ──▶  │
     │ ◀── Finished ─────────────────  │  RTT 2
1
2
3
4
5
6

TLS 1.3(新):1 RTT 握手 + 0-RTT 重连

   Client                            Server
     │                                  │
     │ ── ClientHello + KeyShare ──▶  │
     │ ◀── ServerHello + Cert + Fin  │  RTT 1
     │ ── Finished + Application ──▶  │
1
2
3
4
5

0-RTT 模式(首次握手过的服务):

   Client 用上次的 PSK(Pre-Shared Key)直接发请求
       │
       ▼
   ── ClientHello + 加密 Application Data ──▶ Server
                                             │
                                             ▼
                                          直接处理(0 RTT 业务延迟)
1
2
3
4
5
6
7

0-RTT 的代价:

  • 重放攻击风险:攻击者重放 0-RTT 数据可能让服务端处理两次。
  • 应对:仅幂等请求(GET)允许 0-RTT;POST/写操作禁用。

# 8.3 TCP 慢启动的影响

§04.3 已述。关键事实:首次请求被慢启动限制,实际下载速度远低于理论带宽。

优化:

  • Linux 4.x+ 默认 cwnd=10(约 14KB),覆盖小响应。
  • 服务端 tcp_slow_start_after_idle = 0:连接空闲后不重置 cwnd。
  • 用 HTTP/2/3 多路复用:复用已经"暖好"的连接。

# 8.4 长连接 keepalive

   连接建立 → 请求 1 → 请求 2 → ... → 空闲 30s → 服务端关闭
                                          │
                                          ▼
                                   下次请求重新握手
1
2
3
4

keepalive 的两个时长:

  • TCP keepalive:OS 层周期性发探测包确认对方存活(默认 2h)。
  • HTTP keepalive:HTTP/1.1 连接复用(默认 60s)。

应用层心跳:在长连接上业务发心跳维持 NAT/防火墙映射(详见 §09.4)。

# 8.5 跨平台 TCP/TLS 实现差异

平台 TCP/TLS 栈 特殊优化
Android OkHttp + BoringSSL/Conscrypt TLS 1.3 默认;HTTP/2 默认
iOS Network.framework + Apple TLS TLS 1.3 默认;连接迁移(HTTP/3)
Web 浏览器内置 浏览器自动选最优
Cronet Chromium net stack QUIC + 0-RTT

探索性思考:为什么 TLS 1.3 推广这么慢? 因为中间件兼容性是大坑——企业防火墙、CDN、负载均衡器很多还停留在 TLS 1.2,升级风险高。Apple/Google 强推才让 TLS 1.3 在移动 App 中普及。这是协议升级的普遍困境——理论上更快,但生态升级慢。HTTP/3 也面临同样问题——还在生态推广阶段。


# 09.HTTP 全链路 ⭐

本章把 HTTP 从"队头阻塞"一路拆到"HTTP/3",回答:HTTP/1.1 vs HTTP/2 vs HTTP/3 的本质差异 / 为何弱网下 HTTP/3 优势明显。

# 9.1 HTTP/1.1 与队头阻塞

   keepalive 连接:
   请求 1 ─▶ 响应 1 ─▶ 请求 2 ─▶ 响应 2 ─▶ ...
            │            │
            必须串行    必须串行
1
2
3
4

陷阱:响应 1 慢 → 请求 2 等待——这就是 HTTP 层"队头阻塞"。

Pipelining(并行请求):理论可行,但因为响应顺序必须与请求一致,实际部署有问题,浏览器禁用。

应对:浏览器/客户端开多个 TCP 连接(典型 6 个)——但每个都是独立连接,开销大。

# 9.2 HTTP/2 多路复用

   一个 TCP 连接上:
   请求 1 ─┐
   请求 2 ─┤
   请求 3 ─┼─▶ TCP 连接 ─▶ 服务端
   请求 4 ─┤
   ...
   
   响应可任意顺序返回,并行不阻塞
1
2
3
4
5
6
7
8

核心优势:

  • 多路复用(一个连接 N 个流)。
  • 头部压缩(HPACK,省流量)。
  • 服务端推送(实践少用)。

HTTP/2 的隐藏陷阱:TCP 队头阻塞——HTTP/2 只解决"HTTP 层队头阻塞",但 TCP 层一个包丢失会阻塞所有流。

# 9.3 HTTP/3 与 QUIC

HTTP/3 = HTTP 跑在 QUIC 之上,QUIC 跑在 UDP 之上。

   HTTP/3 → QUIC → UDP → IP
                ↑
            TLS 1.3 内置
1
2
3

QUIC 解决的问题:

  1. TCP 队头阻塞 → QUIC 在用户态实现"流",每个流独立——单流丢包不影响其他流。
  2. TCP 握手 + TLS 握手分离 → QUIC 一次性完成(1 RTT 或 0 RTT)。
  3. 连接迁移 → 客户端 IP 变化(4G→Wi-Fi)连接不断——TCP 必断。

实测收益(§17.4 实验):

场景 HTTP/2 HTTP/3 增益
强网 280ms 270ms 几乎无差
弱网(8% 丢包) 4200ms 2100ms -50%
4G→Wi-Fi 切换 380ms 重新握手 < 50ms 连接迁移 -86%

HTTP/3 的工程难点:

  • 服务端必须支持(nginx 1.25+ / Cloudflare)。
  • 部分企业网防火墙拦截 UDP——必须 fallback HTTP/2。
  • 0-RTT 的重放攻击风险——POST 写操作禁用。

# 9.4 长连接 vs push 的选择

方案 心跳成本 实时性 跨平台 适用场景
HTTP 短轮询 极高 低 跨平台 不应使用
HTTP 长轮询 中 中 跨平台 简单实时需求
WebSocket 低(一次握手) 高 跨平台 实时双向通信
系统 push 低(系统共享) 中 各端独立 通知、状态同步

WebSocket 适用:聊天、协作编辑、行情推送。

系统 push 适用:通知、签到、状态更新。

探索性思考:为什么 HTTP/3 没快速取代 HTTP/2? 因为 HTTP/3 的**"完整收益"需要 OS 内核 + 服务端 + 中间件**全升级——任何一环不支持就 fallback。渐进式收益:

  • Cloudflare/Akamai 等 CDN 已支持。
  • Chrome/Safari 已支持客户端。
  • 但绝大多数源站还是 HTTP/2——所以"端到端 HTTP/3"还少。

这是协议演进的普遍特征——升级是"长尾过程",不是一夜之间。


# 10.连接池全链路 ⭐

本章把连接池从"复用机制"一路拆到"网络切换清空",回答:连接池大小怎么定 / 空闲多久断开 / 网络切换为什么要清空。

# 10.1 连接池工作原理

   ┌──────────────────────────────────────┐
   │ 连接池                                │
   │   - 域名 1: [conn1, conn2, conn3]    │
   │   - 域名 2: [conn4]                  │
   │   - 域名 3: [conn5, conn6]           │
   └──────────────────────────────────────┘
              │
              ▼
   新请求 → 找匹配域名的空闲连接 → 复用
              │
              └─ 找不到 → 新建(DNS + TCP + TLS)
1
2
3
4
5
6
7
8
9
10
11

关键参数:

  • 最大连接数:池中最多保留多少连接。
  • 空闲超时:连接空闲多久后销毁。
  • 每域名最大连接:避免单域名占满池。

# 10.2 连接池大小的科学化

§17.3 实验 证明:连接池大小 = 并发峰值(典型 5);空闲保留 5 分钟。

连接池大小 空闲 5min 平均延迟 重建率
1 5min 80ms(队头阻塞) 5%
5 5min 30ms 5%
10 5min 28ms 4%
20 5min 28ms 4%

关键洞察:池 5 已覆盖 95% 场景;再大无收益。

# 10.3 网络切换处理

Wi-Fi → 4G 切换时连接池必须清空——因为:

  1. 路由变化:原 IP 路径不可达。
  2. NAT 重置:移动网关 NAT 端口变了。
  3. TCP 状态错乱:连接以为还活着,实际已断。

应对:

// Android 监听网络变化
val connectivityManager = context.getSystemService(ConnectivityManager::class.java)
connectivityManager.registerDefaultNetworkCallback(object : NetworkCallback() {
    override fun onAvailable(network: Network) {
        okHttpClient.connectionPool.evictAll()  // 清空连接池
    }
})
1
2
3
4
5
6
7

HTTP/3 的连接迁移:QUIC 通过 Connection ID 识别连接,网络切换不重新握手——这是 §09.3 描述的杀手锏。

# 10.4 连接池监控

指标 含义 阈值
池命中率 复用 / 总请求 > 80%
重建率 新建 / 总请求 < 20%
池占用 已用 / 池大小 < 80%
平均空闲时长 决定 keepalive 业务相关

探索性思考:为什么"连接池"是网络优化中最被低估的环节? 因为它没什么"亮点"——不像 HTTP/3 那样有"50% 提升"的故事。但它是默认配置就能给 70% 收益的基础设施——§17.1 实验 显示复用后 35ms vs 首次 220ms。多数应用的"网络慢"根因是连接池配置不当——而不是"协议不够新"。先把基础配好,再去追新协议。


# 11.弱网全链路 ⭐

本章把弱网治理从"识别"一路拆到"降级",回答:怎么识别弱网 / 弱网超时怎么动态调 / 降级链路怎么设计。

# 11.1 弱网的物理本质

弱网 = 三个物理特征的组合:

   ┌────────────────────────────┐
   │ 高 RTT(300ms+)            │  ← 距离远 / 路由跳数多
   ├────────────────────────────┤
   │ 高丢包(5%+)               │  ← 信号差 / 拥塞
   ├────────────────────────────┤
   │ 低带宽(< 100Kbps)         │  ← 网络制式低 / 限速
   ├────────────────────────────┤
   │ 不稳定(波动大)             │  ← 移动 / 信号变化
   └────────────────────────────┘
1
2
3
4
5
6
7
8
9

任一存在 = 弱网。三个全有 = 极弱网。

# 11.2 弱网识别方法

方法 A:系统 API

val cm = context.getSystemService(ConnectivityManager::class.java)
val nc = cm.getNetworkCapabilities(cm.activeNetwork)
when {
    nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> "WiFi"
    nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> {
        // 还要查 LinkSpeed / SignalStrength
    }
}
1
2
3
4
5
6
7
8

问题:网络类型 ≠ 网络质量。4G 可能很慢(信号差);3G 可能很快(信号好)。

方法 B:业务感知

class NetworkQualityDetector {
    private val recentLatencies = ArrayDeque<Long>(10)
    fun report(latencyMs: Long) {
        if (recentLatencies.size >= 10) recentLatencies.poll()
        recentLatencies.offer(latencyMs)
    }
    fun quality(): Quality {
        val avg = recentLatencies.average()
        return when {
            avg < 500 -> Quality.STRONG
            avg < 2000 -> Quality.MEDIUM
            else -> Quality.WEAK
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

优点:真实质量——基于实际请求延迟,不依赖网络类型猜测。

方法 C:主动探测

定期请求小固定大小数据(比如 1KB)测延迟。

# 11.3 动态超时

§17.2 实验 证明:弱网超时建议 10-15 秒。短于 5s 牺牲成功率,长于 30s 浪费用户时间。

val timeout = when (quality()) {
    Quality.STRONG -> 5_000L
    Quality.MEDIUM -> 10_000L
    Quality.WEAK -> 15_000L
}
1
2
3
4
5

关键认知:超时不是常量,是函数(网络状态)。

# 11.4 指数退避重试

suspend fun <T> retryWithBackoff(maxAttempts: Int = 3, block: suspend () -> T): T {
    var lastError: Throwable? = null
    repeat(maxAttempts) { attempt ->
        try { return block() }
        catch (e: HttpException) {
            // 4xx 不重试(除了 408 / 429)
            if (e.code() in 400..499 && e.code() != 408 && e.code() != 429) throw e
            lastError = e
            delay((1000L * (1L shl attempt)) + Random.nextLong(500))  // 1s/2s/4s + 抖动
        }
    }
    throw lastError!!
}
1
2
3
4
5
6
7
8
9
10
11
12
13

关键设计:

  • 指数退避:1s, 2s, 4s(避免立即重试加剧拥塞)。
  • 加抖动:随机 ±500ms 防"惊群"(多个客户端同时重试)。
  • 最大次数:3-5 次防雪崩。
  • 特定错误码才重试:5xx 重试;4xx 不重试(除 408 timeout / 429 too many)。

# 11.5 降级链路

val data = try {
    api.getHomePage()                  // 主链路
} catch (e: IOException) {
    cache.getStale()                    // 降级 1:过期缓存
        ?: try { api.getSimplified() }  // 降级 2:简化版 API
        catch (e2: IOException) {
            defaultData()                // 降级 3:默认数据
        }
}
1
2
3
4
5
6
7
8
9

收益:失败率体感降到 0(用户看到内容而非错误页)。

探索性思考:为什么"降级"在国内应用更重要? 因为国内移动网络的"长尾用户"占比大——三四线城市、偏远地区、老旧设备。这部分用户即使是 4G 也经常处于弱网状态。没有降级链路 = 长尾用户体验崩塌。这就是为什么国内 App 的"骨架屏 + 兜底数据"比国外更普遍——是被弱网用户逼出来的工程文化。


# 12.跨端网络对照

# 12.1 协议栈对照

维度 Android iOS Web
HTTP 客户端 OkHttp / Retrofit URLSession / Alamofire fetch / axios
连接池 OkHttp ConnectionPool URLSession 自动 浏览器自动
HTTP/2 OkHttp 默认 URLSession 默认 浏览器自动
HTTP/3 Cronet / OkHttp 实验 iOS 15+ Chrome 87+
长连接 OkHttp WebSocket URLSessionWebSocketTask WebSocket API
DNS HttpDns / 系统 URLSession 自动 浏览器自动

# 12.2 网络治理能力对比

维度 Android iOS Web
HTTPS 强制 NetworkSecurityConfig ATS(默认强制) 必须
弱网模拟 adb / 第三方 / Charles Network Link Conditioner DevTools throttling
抓包工具 Charles + 系统 CA Charles + iOS CA DevTools Network
自定义协议 NDK + libcurl 等 Network.framework WebSocket / Service Worker
系统级优化 Cronet(Google) Network.framework 浏览器自动

# 12.3 统一启示

  • 协议层优化跨端通用:复用、合并、压缩、缓存。
  • 弱网治理跨端通用:超时、重试、降级、识别。
  • 指标体系跨端通用:API 延迟分位 + 失败率 + 弱网占比。
  • 平台特异主要在 API 形态:OkHttp vs URLSession vs fetch,但优化思想一致。

# 13.治理一层协议

本节回答四个递进问题:①如何让每次请求最小化代价?②如何让请求更少更聪明?③如何在弱网保住底盘?④如何让问题先于用户被发现? §13-§16 由浅入深四层。

# 13.1 一层命题

核心命题:协议层的"每请求开销"是网络性能的物理下限。第一层治理就是把每次请求开销降到最低——这是单次请求的天花板。

层级特征:

  • 改造成本:中(替换协议栈)
  • 收益:高(典型 -50~70%)
  • 风险:中(兼容性)
  • 是否必做:所有应用

# 13.2 策略 1.1:DNS 优化(HttpDns + 预解析 + IP 池)

机理:§07 DNS 全链路。§02 案例 印尼 DNS 850ms(22% 占比),系统 DNS 在新兴市场极不稳定。HttpDns 走 HTTP 直连权威服务器,且不被劫持。

做法:

class HttpDnsResolver : Dns {
    private val cache = mutableMapOf<String, Pair<List<InetAddress>, Long>>()
    override fun lookup(hostname: String): List<InetAddress> {
        cache[hostname]?.let { (ips, expireAt) ->
            if (System.currentTimeMillis() < expireAt) return ips
        }
        val ips = httpDnsClient.resolve(hostname)
        cache[hostname] = ips to (System.currentTimeMillis() + 600_000)  // 10min TTL
        return if (ips.isNotEmpty()) ips else Dns.SYSTEM.lookup(hostname)  // fallback
    }
}
OkHttpClient.Builder().dns(HttpDnsResolver()).build()
1
2
3
4
5
6
7
8
9
10
11
12

收益:§02 案例 DNS 阶段 850ms→<50ms。

边界:HttpDns 服务本身的可用性需保障;必须有 IP 池 fallback 防服务挂掉。

# 13.3 策略 1.2:HTTP/3 + TLS 1.3 + 0-RTT

机理:§17.4 实验 证明弱网/切换场景 50% 提升;TLS 1.3 把握手从 2 RTT 降到 1 RTT;0-RTT 复用 Session 几乎零延迟。

做法(Cronet):

val cronetEngine = CronetEngine.Builder(context)
    .enableQuic(true)
    .addQuicHint("api.example.com", 443, 443)
    .enableHttp2(true)
    .build()
1
2
3
4
5

收益:§02 案例 TLS 阶段 620ms→0ms(0-RTT 复用),单次首屏 -2s。

边界:服务端必须支持 QUIC;防火墙偶发拦截 UDP 需 fallback;敏感 POST 禁用 0-RTT 防重放。

# 13.4 策略 1.3:连接复用(连接池 + keepalive)

机理:§17.3 实验 池 5 + 5min 是甜蜜点。

做法:

ConnectionPool pool = new ConnectionPool(5, 5, TimeUnit.MINUTES);
OkHttpClient client = new OkHttpClient.Builder().connectionPool(pool).build();
1
2

收益:复用后单请求 35-38ms vs 首次 220ms(§17.1)。

边界:网络切换(Wi-Fi→4G)必须清空连接池;服务端 keepalive 时长决定上限。

# 13.5 策略 1.4:数据压缩与协议升级

机理:响应体积 = 单次主要耗时(弱网下传输占 45%)。

做法:

OkHttpClient.Builder()
    .addInterceptor { chain ->
        val req = chain.request().newBuilder()
            .header("Accept-Encoding", "br, gzip")
            .build()
        chain.proceed(req)
    }
val pbResponse = MyProto.parseFrom(response.body!!.bytes())
1
2
3
4
5
6
7
8

收益:JSON+gzip → PB+brotli 体积通常 -60%;§02 案例 响应体阶段 1700ms→700ms。

边界:PB 需双端协议同步;brotli CPU 编码成本略高于 gzip。

# 13.6 一层反思

探索性思考:为什么"协议层优化"经常被低估? 因为它没什么"业务故事"——用户看到的是"页面加载快",不会赞美你"用了 HTTP/3"。但协议层优化是基础设施型收益——一次配置长期受益。多数团队的现状是:"协议是网络组的事,我们业务关心的是接口设计"。这种隔阂导致协议升级慢。最佳实践:技术架构师推动统一的"网络中台"——协议层统一治理,业务层只关心接口。


# 14.治理二层调度

# 14.1 二层命题

核心命题:协议层是"每次开销",业务层是"如何最少地用网络"。第二层治理是减少请求数量 + 让请求更聪明。

层级特征:

  • 改造成本:中(业务层重构)
  • 收益:极高(典型 -50~70%)
  • 风险:中(业务逻辑变化)

# 14.2 策略 2.1:请求合并与 BFF 聚合

机理:§17.5 实验 弱网延迟 -64%。

做法(GraphQL):

query HomePage {
  user { id name avatar }
  feed(limit: 10) { id title cover }
  recommendations(limit: 5) { id title }
}
1
2
3
4
5

做法(REST batch):

POST /api/v1/batch
{
  "requests": [
    { "method": "GET", "path": "/user" },
    { "method": "GET", "path": "/feed" },
    { "method": "GET", "path": "/recommendations" }
  ]
}
1
2
3
4
5
6
7
8

收益:§02 案例 Day 4 第 2 层落地——6 API 合 2 个,强网 480→220ms,弱网 4200→1500ms。

边界:聚合 API 失败处理要"部分成功也返回";不同缓存策略的字段需拆开。

# 14.3 策略 2.2:依赖图分析与并发化

机理:§18.1 案例 6 API 中只 2 个真依赖,其余可并发——拓扑分析后总耗时降到 max(C+F, D+E) + A + B。

做法(协程):

val a = async { getA() }
val b = async { getB() }
val c = async { val ab = awaitAll(a, b); getC(ab) }
val d = async { val ab = awaitAll(a, b); getD(ab) }
val (cR, dR) = awaitAll(c, d)
1
2
3
4
5

收益:典型首屏 -50%。

边界:并发数过高(> 6)反而触发 HTTP/2 流限制;服务端要能扛突发并发。

# 14.4 策略 2.3:缓存策略(HTTP + 业务双层)

机理:HTTP Cache-Control / ETag 解决"内容未变不下载";业务层缓存解决"快速展示再后台刷新"。

做法:

val cached = cache.get(key)
if (cached != null) showImmediately(cached)
viewModelScope.launch {
    val fresh = api.getData()
    if (fresh != cached) {
        cache.put(key, fresh)
        showRefreshed(fresh)
    }
}
1
2
3
4
5
6
7
8
9

收益:首屏感知延迟接近 0(先用缓存)。

边界:缓存一致性需通过 ETag/版本号或 push 失效。

# 14.5 策略 2.4:预取与预测

机理:用户行为可预测(点了商品列表 80% 会进详情页)。空闲时间预取下一步资源。

做法:

listScrollListener.onIdle { firstVisible ->
    if (firstVisible >= 5) prefetch(items[firstVisible].detailUrl)
}
1
2
3

收益:详情页打开延迟 -70%(命中预取)。

边界:流量浪费风险(预测错 = 浪费);需限制预取并发数和数据量。

# 14.6 二层反思

探索性思考:为什么"请求合并"有政治阻力? 因为它违反了"微服务原则"——每个服务一个 API 是"高内聚低耦合"。但对客户端来说,N 个独立请求是 N × RTT 成本——网络层的"高耦合"反而省事。这就是 BFF(Backend For Frontend)模式的价值——在客户端和微服务之间加一层聚合。但 BFF 本身又增加了运维复杂度。没有银弹——需要团队评估"开发效率 vs 运维成本"。


# 15.治理三层弱网

# 15.1 三层命题

核心命题:§02 案例 第 1 周拉超时翻车的根因——弱网治理不是"加大加多",而是"识别+适配+优雅降级"。第三层治理是把"弱网用户"也照顾好。

层级特征:

  • 改造成本:中(业务层 + 监控)
  • 收益:极高(弱网用户体验救命)
  • 风险:中

# 15.2 策略 3.1:弱网识别 + 动态超时

机理:§11.2 弱网识别 + §17.2 实验。连续 N 次请求慢即判弱网;按弱网等级动态调整超时。

做法:

class NetworkQualityDetector {
    private val recentLatencies = ArrayDeque<Long>(10)
    fun report(latencyMs: Long) {
        if (recentLatencies.size >= 10) recentLatencies.poll()
        recentLatencies.offer(latencyMs)
    }
    fun quality(): Quality {
        val avg = recentLatencies.average()
        return when {
            avg < 500 -> Quality.STRONG
            avg < 2000 -> Quality.MEDIUM
            else -> Quality.WEAK
        }
    }
}
val timeout = when (quality()) {
    Quality.STRONG -> 5_000L
    Quality.MEDIUM -> 10_000L
    Quality.WEAK -> 15_000L
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

收益:§02 案例 Day 4 落地后失败率 8.1%→0.6%。

边界:弱网识别需 10 个样本才稳定;冷启动期可用网络类型粗判。

# 15.3 策略 3.2:指数退避重试 + 错误码白名单

机理:§11.4 指数退避;不是所有错误都重试。

做法:

suspend fun <T> retryWithBackoff(maxAttempts: Int = 3, block: suspend () -> T): T {
    var lastError: Throwable? = null
    repeat(maxAttempts) { attempt ->
        try { return block() }
        catch (e: HttpException) {
            if (e.code() in 400..499 && e.code() != 408 && e.code() != 429) throw e
            lastError = e
            delay((1000L * (1L shl attempt)) + Random.nextLong(500))  // 1s/2s/4s + 抖动
        }
    }
    throw lastError!!
}
1
2
3
4
5
6
7
8
9
10
11
12

收益:偶发失败由重试解决,恶意/不可恢复错误不浪费。

边界:POST 必须保证幂等性(用 Idempotency-Key);总重试预算不超过 3 次防雪崩。

# 15.4 策略 3.3:降级链路与离线 fallback

机理:§11.5 降级链路。极端弱网/无网用本地缓存或简化版数据保住主流程。

做法:

val data = try {
    api.getHomePage()
} catch (e: IOException) {
    cache.getStale() ?: defaultData()
}
1
2
3
4
5

收益:失败率体感降到 0(用户看到内容而非错误页)。

边界:降级数据要打 stale 标识,避免用户基于过期数据做决策。

# 15.5 策略 3.4:CDN 与边缘部署

机理:物理距离决定 RTT 下限。出海/广域用户必须就近接入。

做法:

  • 静态资源:放 CDN(CloudFront / Cloudflare / 阿里云 CDN)。
  • 动态 API:边缘计算(Cloudflare Workers / 阿里云边缘节点)。
  • 数据库:多地域部署 + 异地多活。

收益:§02 案例 雅加达节点让印尼 RTT 从 80ms→<10ms。

边界:边缘节点的缓存一致性策略要设计(如 push invalidation)。

# 15.6 三层反思

探索性思考:为什么"弱网治理"在国内应用更需要? 因为国内地理 + 网络生态特殊:

  1. 东西部差距:北京/上海 vs 西藏/青海,RTT 差 30-50ms。
  2. 地铁/隧道/电梯:经常性弱网。
  3. 三四线城市信号差:移动用户基数大。
  4. 节假日拥塞:基站超载。

国内应用如果不做弱网治理 = 至少 30% 用户体验差。这是为什么国内技术博客里"弱网治理"文章特别多——是国情逼出来的工程能力。


# 16.治理四层监控

# 16.1 四层命题

核心命题:网络问题在不同地区/网络类型差异巨大,必须细粒度监控。第四层治理是让问题先于用户被发现。

层级特征:

  • 改造成本:中(监控基建)
  • 收益:长期(防劣化)
  • 风险:低

# 16.2 策略 4.1:阶段拆解监控

机理:§05.1 方案 ② EventListener 直接给出每阶段时长。

做法:

class TimingEventListener : EventListener() {
    private val timings = mutableMapOf<String, Long>()
    override fun dnsStart(call: Call, hostname: String) { timings["dns_start"] = now() }
    override fun dnsEnd(call: Call, hostname: String, ips: List<InetAddress>) {
        timings["dns"] = now() - timings["dns_start"]!!
    }
    override fun callEnd(call: Call) { report(call.request().url, timings) }
}
1
2
3
4
5
6
7
8

收益:§02 案例 Day 2 直接定位"DNS 慢 + TLS 重传 + 响应体大"三段。

边界:阶段拆解仅在客户端打点,无法看到服务端内部分解。

# 16.3 策略 4.2:地区 × 网络类型维度看板

机理:均值掩盖地区差异。必须按"国家×运营商×网络类型"细分。

做法:

监控数据上报字段:
- url
- duration
- error_code
- network_type (WiFi/4G/5G)
- country_code
- isp (运营商)
- quality (识别为 STRONG/MEDIUM/WEAK)
1
2
3
4
5
6
7
8

收益:§02 案例 是因为这种细分才发现"全球均值正常但印尼极差"。

边界:地区维度数据量大,需采样上报。

# 16.4 策略 4.3:弱网占比与失败码分类

机理:失败率 = ∑(各错误码占比 × 业务损失权重)。SocketTimeout 与 SSLException 治理路径完全不同。

做法:上报字段加 error_code / network_type / quality。

收益:归因从"5% 失败"细化到"3% 是 DNS 失败、1% 是 TLS、1% 是 SocketTimeout"。

边界:错误码合并要保留原始 OS error。

# 16.5 优先级判定(ROI)

ROI 优化项 收益 成本 风险 对应章节
极高 HttpDns + IP 池 失败率 5%→<1% 1-2 周 中 §13.2
极高 全站 HTTPS + 连接复用 复用后延迟 -50-70% 1-2 周 中 §13.4
极高 弱网识别 + 动态超时 弱网失败率大幅降 1 周 低 §15.2
极高 首屏 API 合并 / BFF 弱网首屏 -64% 2-3 周 中 §14.2
高 HTTP/3 (出海/弱网应用) 弱网 -50%、切换 -86% 2-4 周 中 §13.3
高 数据压缩 brotli + PB 流量 -60%、传输 -50% 1-2 周 低 §13.5
高 指数退避 + 错误码白名单 雪崩防护 几天 低 §15.3
高 阶段拆解监控 归因能力提升 1 周 低 §16.2
中 边缘 CDN/就近部署 物理 RTT 下限突破 2-4 周(运维) 中 §15.5
中 降级链路 极端体验保护 1-2 周 中 §15.4
中 缓存 + 预取 二次访问近 0 延迟 2-3 周 中 §14.4 + §14.5
中 地区维度看板 地域问题可见 1 周 低 §16.3
低 自实现协议 极少收益 极高 极高 -

避免反向收益:

  • 过度并发:服务端限流,重试反而拥塞(§02 案例 第 2 周翻车)。
  • 过度缓存:数据不一致问题。
  • 过度重试:弱网雪上加霜。
  • 拉长超时掩盖问题:§02 案例 第 1 周翻车的根因。
  • 强网思维硬套弱网:必须按场景区分,不是"一套配置打天下"。

# 16.6 四层反思

探索性思考:为什么"网络监控"是网络优化的"地基"? 因为没有监控 = 不知道哪里慢 = 不知道哪里需要优化。监控本身没有故事性——但它是其他治理的前提。多数团队的现状:监控只看"成功率"——但成功率是粗粒度——应该按"地区 × 网络类型 × 错误码 × 阶段"四维拆。这种细粒度监控在线上一目了然——"印尼 4G DNS 失败率 8%"立刻知道哪里出问题。


# 17.求证实验

# 17.1 实验一:握手开销

Step 1 — 原始观察

工程师都知道 HTTPS 比 HTTP 慢,但慢多少?连接复用后还慢吗?

Step 2 — 提出疑问

HTTP / HTTPS / HTTP/2 在首次和连接复用情况下的延迟差异多大?

Step 3 — 形成假设

H₁:首次连接 HTTPS 比 HTTP 慢 100-300ms(多 1-2 RTT TLS 握手);连接复用后两者几乎相同。HTTP/2 在连接复用后并发请求显著快。 H₀:HTTPS 永远慢。

Step 4 — 设计实验

项 配置
测试服务器 部署同一服务支持 HTTP / HTTPS / HTTP2
网络 Wi-Fi 50Mbps
请求 单独 GET 1KB JSON
场景 首次请求 / 连接复用第 2-10 次

Step 5 — 实测数据

协议 首次 (ms) 复用第 2 次 (ms)
HTTP/1.1 50 35
HTTPS(HTTP/1.1 + TLS) 220 38
HTTP/2(HTTPS) 230 38

并发 10 请求:

协议 串行总时长 并发总时长
HTTP/1.1 keepalive 350 ms 220 ms(最多 6 条 TCP)
HTTP/2 380 ms 80 ms(多路复用)

Step 6-7 — 验证 / 结论

HTTPS 的"慢"主要在首次连接(多 1-2 RTT TLS 握手)。 连接复用后开销可忽略。HTTP/2 在多请求并发场景下显著优于 HTTP/1.1。

工程意义:

  • 必用 HTTPS(安全 + 复用后无开销)。
  • 必用连接复用(keepalive / 连接池)。
  • 多请求场景必用 HTTP/2 / HTTP/3。
  • 首次连接慢可用 HTTP/3 0-RTT 缓解。

Step 8 — 边界

  • 实验数据基于良好网络。弱网下 TLS 握手时间会更长。
  • 服务端 SSL 配置不当会增加延迟。

# 17.2 实验二:弱网超时策略

Step 1 — 原始观察

弱网下默认超时 30-60 秒,用户根本等不了那么久;但太短又会"明明能成功"被截断。最优超时是多少?

Step 2 — 提出疑问

不同超时配置(5s / 10s / 30s / 60s)下,弱网请求的成功率与"用户实际等待时间"如何变化?

Step 3 — 形成假设

H₁:超时太短会牺牲成功率;太长会浪费用户时间。最优值在 10-15s 区间。 H₀:超时设大点更好。

Step 4 — 设计实验

项 配置
弱网模拟 RTT 500ms / 丢包 10% / 带宽 50Kbps
超时配置 5s / 10s / 15s / 30s / 60s
主指标 (a) 请求成功率 (b) 用户平均等待时间
重复 每场景 200 次

Step 5 — 实测数据

超时 成功率 失败时用户等待(s) 加权用户体验
5s 78% 5 中
10s 92% 10 较好
15s 95% 15 较好
30s 96% 30 较差
60s 97% 60 差

Step 6-7 — 验证 / 结论

弱网超时建议 10-15 秒。短于 5s 牺牲成功率,长于 30s 浪费用户时间。

▶▶ 回扣 §02 案例:经验派第 1 周"拉到 60s"是把本实验"超时长成功率高"误读为"超时越长越好",忽略了实验数据中 30s+ 边际收益极低、用户体验大幅下降的明确警告。方法派 Day 4 的"强网 5s/弱网 15s 动态切换"才是本实验工程意义的正确实现。这告诉我们:超时不是常量,是函数(网络状态)。

Step 8 — 边界

  • 不同业务对成功率敏感度不同。
  • 大数据传输需要更长超时。
  • 弱网识别后可动态调整超时。

# 17.3 实验三:连接复用收益

Step 1 — 原始观察

工程师都用连接池,但连接池大小、空闲时长怎么配?

Step 2 — 提出疑问

连接池大小 N 与请求平均延迟的关系是什么?空闲连接保留多久最优?

Step 3 — 形成假设

H₁:连接池大小 N 应等于"并发峰值";空闲保留 5-10 分钟(与服务端 keepalive 对齐)。

Step 4-5 — 设计 + 实测

连接池大小 空闲 5min 平均延迟(ms) 重建率
1 5min 80(队头阻塞) 5%
5 5min 30 5%
10 5min 28 4%
20 5min 28 4%
空闲保留 池 5 平均延迟 重建率
1min 5 60(频繁重建) 30%
5min 5 30 5%
10min 5 28 3%

Step 6-7 — 结论

连接池大小 = 并发峰值(典型 5);空闲保留 5 分钟。 超过这两者收益递减。

工程意义:

  • OkHttp 默认 5 / 5 分钟基本够用。
  • 大并发应用可调到 10。
  • 不要为"快"开 50+,吃不消还可能被服务端限。

Step 8 — 边界

  • 服务端 keepalive 时长决定空闲上限。
  • 移动应用切网络(Wi-Fi → 4G)时连接池要清空。

# 17.4 实验四:HTTP/3 在弱网下的相对增益

Step 1 — 原始观察

HTTP/3 (QUIC) 被宣传为"弱网神器",但强网下与 HTTP/2 几乎一致。它的真实价值边界在哪?

Step 2 — 提出疑问

HTTP/2 和 HTTP/3 在不同网络条件(强网 / 弱网 / 移动切换)下的延迟差异多大?

Step 3 — 形成假设

H₁:强网下两者几乎一致;弱网下 HTTP/3 显著优于 HTTP/2;网络切换时 HTTP/3 连接迁移让 0-RTT 重连 vs HTTP/2 完整重新握手。

Step 4-5 — 设计 + 实测

场景 HTTP/2 HTTP/3 增益
(a) Wi-Fi 强网 280 ms 270 ms -3%
(b) 4G 普通 520 ms 460 ms -12%
(c) 弱网(8% 丢包) 4200 ms 2100 ms -50%
(d) 4G→Wi-Fi 切换 重新握手 380ms 连接迁移 < 50ms -86%

Step 6-7 — 结论

HTTP/3 真实价值在"弱网 + 移动切换"场景。强网用户感知不到,但弱网用户和移动切换用户体验提升 50% 以上。

工程意义:

  • 出海应用、新兴市场应用必上 HTTP/3。
  • 强网为主的内网/办公应用不必着急。
  • 配合 Cronet(Android)/ iOS 15+ URLSession(自动)落地。
  • 服务端启用 0-RTT 是关键(节省首次 TLS 握手)。

Step 8 — 边界

  • 服务端必须支持 QUIC(nginx 1.25+/Caddy/Cloudflare)。
  • 部分企业网/校园网防火墙拦截 UDP,需 fallback 到 HTTP/2。
  • 0-RTT 数据有重放攻击风险,敏感请求(POST 转账)需禁用。

# 17.5 实验五:请求合并与批处理的吞吐增益

Step 1 — 原始观察

很多业务首屏 6-10 个 API 串行/并发各有问题。合并到 1-2 个聚合 API 收益有多大?

Step 2 — 提出疑问

6 个独立 API vs 1 个聚合 API(服务端聚合)vs BFF 模式的延迟与代码成本差异?

Step 3 — 形成假设

H₁:合并 API 在弱网下有 2-3 倍提升;强网下提升 30-50%。

Step 4-5 — 设计 + 实测

方案 强网总耗时 弱网总耗时 流量 客户端代码复杂度
A 6 独立并发 480 ms 4200 ms 12 KB(多次头部) 高(6 处错误处理)
B 服务端聚合 220 ms 1500 ms 8 KB 低(1 处)
C BFF GraphQL 250 ms 1600 ms 7 KB(按需字段) 低

Step 6-7 — 结论

首屏类多 API 必须合并:弱网延迟降低 60%+,流量节省 30%+,客户端代码大幅简化。

工程意义:

  • 首屏不超过 2 个 API(必要时用 BFF)。
  • 服务端聚合优于客户端聚合(节省 N-1 RTT)。
  • GraphQL 适合复杂场景;REST + batch endpoint 适合简单场景。
  • 注意:聚合 API 的失败处理要"部分成功也返回"。

Step 8 — 边界

  • 服务端聚合增加后端复杂度和耦合。
  • 不同 API 缓存策略不同时合并后受限于最严格的策略。
  • 流式场景(视频/直播)不适合合并。

# 17.6 五大实验启示

   握手开销         → HTTPS 首次慢 100-300ms,复用后无差异      ─┐
                                                                 │
   弱网超时         → 10-15s 是甜蜜点,超过 30s 边际为零         │
                                                                 │
   连接复用         → 池 5 / 5min 是默认最优                     ├─▶ 网络优化 = 减请求 + 减开销 + 防弱网 + 适配场景
                                                                 │
   HTTP/3 弱网增益   → 弱网/切换场景 50%+ 提升                    │
                                                                 │
   请求合并         → 弱网延迟 -64%、流量 -33%、代码 -70%        ─┘
1
2
3
4
5
6
7
8
9

统一启示:

# 维度 启示 收益量级
① 握手开销 HTTPS 复用后无差异 基础保障
② 弱网超时 甜蜜点 10-15s 失败率 -50%
③ 连接复用 池 5/5min 是默认最优 -70%
④ HTTP/3 弱网 50%+ 提升 弱网场景
⑤ 请求合并 弱网 -64% 首屏类杠杆

一句话:复用最有效,HTTP/3 治弱网,请求合并是首屏杠杆。


# 18.实战案例

# 18.1 跨端同构案例:依赖图分析与并发化

背景:某社交应用首屏 6 个 API 串行(依赖关系)总耗时 2.5s,希望优化。Android / iOS / Web 都有同样问题。

度量与归因:

仔细分析依赖关系:实际只有 2 个 API 真正依赖前置结果,其余 4 个可以并行。

假设与求证:

提出统一假设:"拆分依赖图,把可并行的并发执行"。

修复设计:

   原始(串行):
   A → B → C → D → E → F  总耗时 = ΣTi

   优化(并发):
   A → B → ┬─→ D ─→ E
           └─→ C ─→ F
   总耗时 = max(D+E, C+F) + A + B
1
2
3
4
5
6
7

验证:

平台 优化前 优化后 降幅
Android 2.5s 1.2s 52%
iOS 2.4s 1.1s 54%
Web 2.6s 1.3s 50%

统一启示:网络优化跨端通用法则 = 依赖图分析 + 并发化。

# 18.2 平台特异案例:Android HttpDns 接入

背景:Android 应用在某些地区 DNS 失败率 5%,iOS 同地区 < 0.5%。

现象:Android 用户报告"打不开",iOS 没问题。

度量与归因:

抓包发现:Android 用 InetAddress.getByName 查系统 DNS,部分运营商 DNS 不稳定。iOS 系统 DNS 自带 fallback。

假设与求证:

假设:用 HttpDns 服务(如阿里 / 腾讯)替代系统 DNS。

实验:HttpDns 失败率 < 0.1%,且不被运营商劫持。

修复:Android 接入 HttpDns + 系统 DNS fallback。

验证:失败率从 5% 降到 0.3%。

边界与上线策略:HttpDns 需要 IP 白名单(先用本地 IP 列表 fallback)。

# 18.3 弱网治理案例:地铁 App 失败重试雪崩

背景:某地铁查询 App 在地铁站内(弱网)失败率高达 25%——用户狂点重试。

现象:用户进站想查地铁,App 转圈,失败,狂点 5 次,让本就拥塞的网络更拥塞。

根因(用 §11 弱网原理 分析):

  • 地铁站信号差(< 5% 信号)→ 高丢包。
  • 默认超时 10s,用户耐心 < 5s,主动取消并重试。
  • 5 次手动重试 = 5 倍流量进入网络 → 服务端限流。

修复:

  • 弱网识别后主动告诉用户:"网络较差,正在为您智能重试"。
  • 超时改为 8s,但 UI 显示"已重试 N 次"——用户知道在重试。
  • 指数退避 1s/2s/4s,禁止用户手动重试(按钮 disabled)。
  • 缓存上次成功的查询结果,作为 fallback 显示。

验证:失败率 25% → 3%;用户主动重试次数从平均 3.8 → 0.8。

教训:弱网治理 = 把"用户冲动"转化为"应用调度"——这是 §06.4 失败重试归因 的工程演绎。

# 18.4 案例统一启示

  • 数据驱动而非经验:§02 案例 3 周失败 vs 7 天成功的根本差异。
  • 协议层是地基:HttpDns / HTTP/3 / 连接复用 是基础。
  • 业务层是杠杆:请求合并 / 依赖图 / 缓存 收益最大。
  • 弱网治理是必修:识别 + 适配 + 降级,三件套必备。

# 19.防劣化体系

# 19.1 三道防线总览

开发期 ──▶ 编译期 / CI ──▶ 上线期 / 运行期
   │             │              │
   ▼             ▼              ▼
[Lint]      [自动化基准]     [线上 SLO]
1
2
3
4

# 19.2 编码期 Lint

Lint 规则 作用
MissingTimeout 关键 API 调用未设超时 → 错误
InsecureHttp 关键 API 用 HTTP(未 HTTPS)→ 错误
MainThreadNetwork 主线程同步网络调用 → 错误
SerialDependency 串行依赖的请求 → 警告(应评估是否可并发)
MissingRetryBackoff 重试无退避 → 警告

# 19.3 CI 卡口与线上 SLO

CI 卡口:

  • API 关键场景 P95 延迟基准。
  • 弱网模拟用例:成功率 ≥ 阈值。
  • 流量监控:每次发版流量增长 < 5%。

线上 SLO:

指标 阈值
API P50 < 200ms
API P99 < 2s
失败率 < 0.5%
弱网超时率 < 5%
DNS 失败率 < 0.5%
错误预算耗尽 冻结新功能

# 19.4 监控数据闭环

   线上 OkHttp Interceptor / EventListener
          ↓
   按"地区 × 网络类型 × 错误码 × 阶段"细分
          ↓
   异常地区 / 异常版本告警
          ↓
   定位到具体协议层 / 业务层
          ↓
   修复 + 回归 CI
          ↓
   灰度验证 → 全量发布
1
2
3
4
5
6
7
8
9
10
11

# 20.跨平台速查

# 20.1 工具速查

平台 客户端打点 阶段拆解 抓包 弱网模拟
Android OkHttp Interceptor / EventListener EventListener.dnsStart Charles + 系统 CA adb / Charles
iOS URLSession metrics URLSessionTaskTransactionMetrics Charles + iOS CA Network Link Conditioner
Web DevTools Network Performance Resource API DevTools Network DevTools throttling
嵌入式 自定义 自定义 tcpdump 自定义

# 20.2 关键 API 速查

操作 Android iOS Web
HTTP 客户端 OkHttp / Retrofit URLSession fetch / axios
连接池 OkHttp ConnectionPool URLSessionConfiguration.httpMaximumConnectionsPerHost 浏览器自动
HTTP/2 OkHttp 自动 URLSession 自动 浏览器自动
HTTP/3 Cronet iOS 15+ URLSession Chrome 87+
长连接 OkHttp WebSocket URLSessionWebSocketTask WebSocket API
离线缓存 OkHttp Cache URLCache Service Worker / Cache API
网络监听 ConnectivityManager NWPathMonitor navigator.onLine
自定义 DNS OkHttp.Dns URLSession 受限 浏览器无

# 20.3 通用 SLO 速查

指标 推荐值
API P50 < 200ms
API P99 < 2s
失败率(强网) < 0.5%
失败率(弱网) < 5%
连接池大小 5
空闲保留 5min
超时(强网) 5s
超时(弱网) 10-15s
最大重试次数 3

# 21.总结与延伸

# 21.1 五条核心原则

  1. 协议优化 + 业务优化 + 弱网容错 + 监控四层缺一不可:§02 案例 是只治一层 3 周失败的真实证据。
  2. 复用最有效:HTTPS / HTTP/2 / HTTP/3 / 连接池都基于复用,§17.1 给出硬证据。
  3. 弱网容错是必修课:识别 + 动态超时 + 指数退避 + 降级链路,不是拉长超时和无脑重试。
  4. 数据驱动:每次请求都有阶段拆解 + 长尾分析 + 地区维度细分。
  5. 请求合并 + 预取:§17.5 弱网 -64%,是首屏类最大杠杆。

# 21.2 五个常见误区

  1. "HTTPS 总是慢":错(复用后无差异,§17.1)。
  2. "超时设大点稳":错(§17.2 30s+ 边际为零;§02 案例 第 1 周翻车)。
  3. "重试越多越稳":错(弱网下雪上加霜,§02 案例 第 2 周打挂服务端)。
  4. "HTTP/3 永远更快":错(§17.4 强网无差,价值在弱网/切换)。
  5. "全部并发更快":错(§02 案例 第 3 周翻车,弱网下并发数过多反而握手失败)。

# 21.3 三个外延

  • AI 网络调度:未来 ML 预测网络质量提前调整策略(如进电梯前预拉缓存)。
  • 5G 边缘计算:边缘节点 + 低延迟,让"端到端 < 10ms"成为可能。
  • 量子安全 TLS:后量子加密算法将影响 TLS 性能(握手包变大)。

# 21.4 给团队的建议

  • 第 1 周:用阶段拆解 EventListener 建立监控,看清楚"哪里慢"。
  • 第 2 周:执行第一层治理(协议层)——HttpDns + 连接池 + HTTP/2。
  • 第 3 周:执行第二层治理(业务层)——首屏 API 合并 + 缓存。
  • 第 4 周:执行第三层治理(弱网)——动态超时 + 指数退避 + 降级。
  • 第 5 周以上:考虑 HTTP/3、边缘 CDN 等架构性升级。

# 21.5 延伸阅读

  • High Performance Browser Networking(Ilya Grigorik)—— 网络性能圣经
  • IETF RFC 9114 (HTTP/3)
  • Google Web Fundamentals: Networking
  • WWDC: Modern Network Performance
  • Cloudflare 博客:HTTP/3 实战经验

# 一句话总结

网络性能 = 协议层效率 × 业务层调度 × 弱网容错 × 监控可观测;四层缺一不可。 连接复用最基础,HTTPS 不是"慢"的元凶,HTTP/3 在弱网/切换场景 50% 提升,请求合并是首屏类最大杠杆。 弱网治理 ≠ 拉长超时 + 无脑重试,而是"识别+适配+优雅降级"。 §02 案例 那个"3 周经验派 vs 7 天方法派"的反差,正是这条路径的最锋利证据。

上次更新: 2026/06/07, 10:26:12
App冷启动优化
图片性能解码优化

← App冷启动优化 图片性能解码优化→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式