网络性能分析优化
# 网络性能分析与优化
📊 学习成本预估 | 难度:⭐⭐⭐⭐(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 / 嵌入式(联网设备)
- 前置阅读:
- 本文核心命题:
网络性能 = 协议层效率 × 业务层调度 × 弱网容错 × 监控可观测——四个维度缺一不可。 协议层(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
2
3
这是物理硬约束:任何"加速"都不能让数据包跑得比光快。优化只能是"减少往返次数"。
约束二:网络协议有固定开销
每次连接都要:
DNS 查询 50-500ms(视缓存)
TCP 握手 1 RTT(~30-150ms)
TLS 握手 1-2 RTT(HTTPS)
HTTP 请求 1 RTT(请求 + 响应)
─────────────────────────────────
首次请求 3-5 RTT = 100-700ms(仅协议开销)
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 个反直觉问题
带着这些问题阅读:
- HTTPS 比 HTTP 慢多少?是不是必须用 HTTPS?
- HTTP/2 一定比 HTTP/1.1 快吗?
- 多请求并发是不是越多越快?
- 连接复用真的能省那么多吗?
- 弱网下重试会让情况变好还是变坏?
- CDN 加速真的有用吗?
- WebSocket 比 HTTP 长轮询快多少?
- 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 + 体积/带宽
2
3
4
5
三个推论:
- 首次请求被 RTT 主导:100ms RTT 下首次请求 ≥ 400ms(仅协议开销)。
- 复用后被 TTFB 主导:仅 1 个 RTT,~50ms。
- 大文件被带宽主导: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
...
2
3
4
5
6
7
8
实际影响:
下载 100KB 文件:
- cwnd 增长到能装下 100KB 需要 4 个 RTT
- 总耗时 ≈ 4 × RTT
- RTT = 100ms → 400ms
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 重传等待)
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 内核网络栈 硬件
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 / 自定义)
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 库的入口和出口——但用户感知还包含:
- UI 框架的延迟(异步切回主线程)。
- 数据解析(JSON 反序列化、UI 更新)。
- 重试/降级链路。
真正的"用户感知延迟"应该从"用户操作"开始打点到"屏幕变化完成"——这才是"端到端"延迟。这种"业务延迟" 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)
├─ 合理超时
├─ 降级 / 缓存
└─ 重试策略
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%
传输 剩余
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 决定保留时间)
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 直接连
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)
}
}
}
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
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 ───────── │
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
2
3
4
5
6
TLS 1.3(新):1 RTT 握手 + 0-RTT 重连
Client Server
│ │
│ ── ClientHello + KeyShare ──▶ │
│ ◀── ServerHello + Cert + Fin │ RTT 1
│ ── Finished + Application ──▶ │
2
3
4
5
0-RTT 模式(首次握手过的服务):
Client 用上次的 PSK(Pre-Shared Key)直接发请求
│
▼
── ClientHello + 加密 Application Data ──▶ Server
│
▼
直接处理(0 RTT 业务延迟)
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 → 服务端关闭
│
▼
下次请求重新握手
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 ─▶ ...
│ │
必须串行 必须串行
2
3
4
陷阱:响应 1 慢 → 请求 2 等待——这就是 HTTP 层"队头阻塞"。
Pipelining(并行请求):理论可行,但因为响应顺序必须与请求一致,实际部署有问题,浏览器禁用。
应对:浏览器/客户端开多个 TCP 连接(典型 6 个)——但每个都是独立连接,开销大。
# 9.2 HTTP/2 多路复用
一个 TCP 连接上:
请求 1 ─┐
请求 2 ─┤
请求 3 ─┼─▶ TCP 连接 ─▶ 服务端
请求 4 ─┤
...
响应可任意顺序返回,并行不阻塞
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 内置
2
3
QUIC 解决的问题:
- TCP 队头阻塞 → QUIC 在用户态实现"流",每个流独立——单流丢包不影响其他流。
- TCP 握手 + TLS 握手分离 → QUIC 一次性完成(1 RTT 或 0 RTT)。
- 连接迁移 → 客户端 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)
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 切换时连接池必须清空——因为:
- 路由变化:原 IP 路径不可达。
- NAT 重置:移动网关 NAT 端口变了。
- TCP 状态错乱:连接以为还活着,实际已断。
应对:
// Android 监听网络变化
val connectivityManager = context.getSystemService(ConnectivityManager::class.java)
connectivityManager.registerDefaultNetworkCallback(object : NetworkCallback() {
override fun onAvailable(network: Network) {
okHttpClient.connectionPool.evictAll() // 清空连接池
}
})
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) │ ← 网络制式低 / 限速
├────────────────────────────┤
│ 不稳定(波动大) │ ← 移动 / 信号变化
└────────────────────────────┘
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
}
}
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
}
}
}
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
}
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!!
}
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:默认数据
}
}
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()
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()
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();
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())
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 }
}
2
3
4
5
做法(REST batch):
POST /api/v1/batch
{
"requests": [
{ "method": "GET", "path": "/user" },
{ "method": "GET", "path": "/feed" },
{ "method": "GET", "path": "/recommendations" }
]
}
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)
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)
}
}
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)
}
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
}
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!!
}
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()
}
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 三层反思
探索性思考:为什么"弱网治理"在国内应用更需要? 因为国内地理 + 网络生态特殊:
- 东西部差距:北京/上海 vs 西藏/青海,RTT 差 30-50ms。
- 地铁/隧道/电梯:经常性弱网。
- 三四线城市信号差:移动用户基数大。
- 节假日拥塞:基站超载。
国内应用如果不做弱网治理 = 至少 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) }
}
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)
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% ─┘
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
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]
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
↓
灰度验证 → 全量发布
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 五条核心原则
- 协议优化 + 业务优化 + 弱网容错 + 监控四层缺一不可:§02 案例 是只治一层 3 周失败的真实证据。
- 复用最有效:HTTPS / HTTP/2 / HTTP/3 / 连接池都基于复用,§17.1 给出硬证据。
- 弱网容错是必修课:识别 + 动态超时 + 指数退避 + 降级链路,不是拉长超时和无脑重试。
- 数据驱动:每次请求都有阶段拆解 + 长尾分析 + 地区维度细分。
- 请求合并 + 预取:§17.5 弱网 -64%,是首屏类最大杠杆。
# 21.2 五个常见误区
- "HTTPS 总是慢":错(复用后无差异,§17.1)。
- "超时设大点稳":错(§17.2 30s+ 边际为零;§02 案例 第 1 周翻车)。
- "重试越多越稳":错(弱网下雪上加霜,§02 案例 第 2 周打挂服务端)。
- "HTTP/3 永远更快":错(§17.4 强网无差,价值在弱网/切换)。
- "全部并发更快":错(§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 天方法派"的反差,正是这条路径的最锋利证据。