🌐 《网络架构实战:从单机到云原生的全栈思考》博客系列
定位 :面向中高级后端/运维/DevOps/架构师
核心方法 :问题驱动 + 架构思维 + 动手验证
每讲结构 :真实痛点 → 原理图解 → 实验步骤 → 配置代码 → 避坑清单 → 扩展思考
总讲数 :29讲(开篇3讲 + 基础2讲 + 实战22讲 + 结束2讲)
实验集群:华为云香港 ECS × 4台 (ecs-ee63, c6.large.2, Ubuntu 24.04)
📑 完整目录树
🔹 开篇篇(3讲|建立认知框架)
| 编号 | 标题 | 核心问题 | 实验设计 |
|---|---|---|---|
| 第1讲 | 如何练好网络架构这门"内功心法"? | 为什么学了TCP/IP仍写不出高可用系统? | tcpdump 抓包分析 HTTP 完整生命周期 |
| 第2讲 | 导学|实验说明 & 学习指南 | 如何降低动手门槛,确保读者能复现所有实验? | Docker + Vagrant 环境搭建 |
| 第3讲 | 加餐|高可靠网络架构与故障预防 | 如何从事故反推架构设计原则? | 连接耗尽/TIME_WAIT 堆积模拟 |
🔹 基础篇(2讲|夯实底层认知)
| 编号 | 标题 | 核心问题 | 实验设计 |
|---|---|---|---|
| 第4讲 | 一个数据包的网络之旅 | 网络究竟是如何工作的? | traceroute + tcpdump 联合抓包 |
| 第5讲 | 架构设计思考:网络架构设计要考虑哪些要素? | CAP + SLA + Cost + Security + Operability | 五维评估模型实战 |
🔹 实战篇(22讲|逐层攻破核心场景)
| 编号 | 标题 | 核心问题 | 实验设计 |
|---|---|---|---|
| 第6讲 | 主备:怎样防范单点故障? | Active-Standby vs Active-Active | keepalived + nginx VIP 漂移 |
| 第7讲 | 集群:怎样实现横向扩展? | 无状态服务水平扩展 | HAProxy + 3×Nginx 负载均衡 |
| 第8讲 | 限流:怎样防止应用被打垮? | 令牌桶/漏桶/滑动窗口 | Nginx limit_req + Redis Lua 限流 |
| 第9讲 | 控影响:怎样可靠升级服务? | 灰度发布 + 流量染色 | Istio 金丝雀发布 |
| 第10讲 | 纵向扩展(上):常见低性能代码 | 同步阻塞/大对象序列化/锁竞争 | async-profiler 火焰图诊断 |
| 第11讲 | 纵向扩展(中):网络模型和协议调优 | TCP参数/epoll/BBR | BBR vs CUBIC 吞吐对比 |
| 第12讲 | 纵向扩展(下):单机架构优化 | 读写分离/缓存分层/异步化 | 同步→异步接口改造 |
| 第13讲 | 减法与重试:怎样优化弱网? | 指数退避/幂等性 | tc netem 弱网模拟 |
| 第14讲 | 分片、并发与续传:大文件上传 | Chunking/断点续传 | MinIO presigned URL 分片上传 |
| 第15讲 | DNS:域名解析系统是怎样工作的? | 递归/迭代查询 | dig +trace 完整追踪 |
| 第16讲 | CDN 架构(上):静态资源加速 | 边缘缓存/回源/预热 | Cloudflare Workers 边缘计算 |
| 第17讲 | CDN 架构(下):动态内容加速 | TCP优化/HTTP2/QUIC | HTTP/1.1 vs HTTP/2 vs QUIC |
| 第18讲 | 全球网络加速架构 | Anycast/BGP/GSLB | 跨区域延迟优化 |
| 第19讲 | SSL/TLS:公网安全传输 | TLS 1.3握手/HSTS | Nginx TLS 1.3 配置 |
| 第20讲 | VPN:构建安全企业网络 | IPSec/WireGuard/SD-WAN | WireGuard 点对点隧道 |
| 第21讲 | 多重武装:安全网络架构 | 纵深防御/红蓝对抗 | WAF + SQL注入防御 |
| 第22讲 | 兼容:网络协议存量迭代 | HTTP/1.1+HTTP/2共存 | 优雅降级 + 双写过渡 |
| 第23讲 | VPC架构:云网络多租户隔离 | 路由表/安全组/对等连接 | 跨账号 VPC Peering |
| 第24讲 | 加餐|思考题答案合集 | 前23讲关键问题深度解答 | --- |
| 第25讲 | 加餐|Nginx 限流关键概念 | limit_conn/limit_req/limit_rate | burst vs nodelay 对比 |
🔹 结束篇(2讲|总结升华)
| 编号 | 标题 | 核心问题 | 实验设计 |
|---|---|---|---|
| 第26讲 | 结束语|每一次问题,都是成长的契机 | 架构师成长心法 | 推荐书单与学习路径 |
| 第27讲 | 结课测试|来赴一场满分之约 | 综合场景设计题 | 百万设备IoT网关架构设计 |
🏗️ 实验环境总览
集群信息
┌─────────────────────────────────────────────────────────────────┐
│ ecs-ee63 TCP/IP 实验集群 │
│ 华为云香港 | c6.large.2 | Ubuntu 24.04 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Server A │ │ Server B │ │ Server C │ │
│ │ .0.5 (公) │ │ .0.207 (公) │ │ .0.111 (公) │ │
│ │ 119.8.59.15 │ │ 119.8.110.76 │ │150.40.239.207│ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └──────────────────┼──────────────────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ │ Server D │ │
│ │ .0.44 (公) │ │
│ │ 159.138.137.101 │ │
│ └─────────────────┘ │
│ │
│ 内网段: 192.168.0.0/24 │
│ 用途: TCP/IP协议实验、网络拓扑模拟、性能测试 │
└─────────────────────────────────────────────────────────────────┘
工具链速查
| 工具 | 用途 | 安装命令 |
|---|---|---|
| tcpdump | 网络抓包分析 | apt install tcpdump -y |
| Wireshark | 图形化协议分析 | 本地安装 |
| iperf3 | 带宽/吞吐测试 | apt install iperf3 -y |
| nmap | 端口扫描/服务探测 | apt install nmap -y |
| dig | DNS 查询工具 | apt install dnsutils -y |
| tc (netem) | 弱网模拟 | 内核自带 |
| ss | Socket 统计 | apt install iproute2 -y |
| hping3 | 自定义包构造 | apt install hping3 -y |
| curl | HTTP 客户端 | 系统自带 |
| openssl | TLS/SSL 工具 | 系统自带 |
博客风格规范
- 图文并茂:每讲至少包含 1 张 ASCII 架构图 + 1 张 Mermaid 流程图
- 真实数据:所有实验输出来自 ecs-ee63 集群真实执行结果
- 配置完整:每个配置参数附带详细中文注释
- 踩坑记录:标注 ⚠️ 高危操作 + 💡 最佳实践
- 对比表格:方案/算法/协议的横向对比
- 中英术语:专业术语首次出现时标注中英文
🔹 开篇篇
第1讲:如何练好网络架构这门"内功心法"?
🎯 核心问题
为什么学了TCP/IP协议栈,仍然写不出高可用系统?
很多工程师能背诵 TCP 三次握手、四次挥手,能说出 HTTP 状态码含义,但当面对真实场景------"大促期间连接池耗尽"、"微服务间偶发超时"、"跨机房延迟抖动"------却不知从何下手。
根本原因在于:网络架构 ≠ 协议背诵。
💡 三层次能力模型
┌─────────────────────────────────────────────────────────────┐
│ 网络架构能力金字塔 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ 系统层 │ 架构设计、容量规划 │
│ │ (System) │ 故障域划分、全局最优 │
│ ├─────────────┤ │
│ │ 组件层 │ 负载均衡、DNS/CDN │
│ │ (Component) │ 防火墙、VPN、限流 │
│ ├─────────────┤ │
│ │ 协议层 │ TCP/UDP/IP/HTTP │
│ │ (Protocol) │ TLS/DNS/QUIC │
│ └─────────────┘ │
│ │
│ 绝大多数教程止步于此 ↑ 本系列从这里出发 → │
└─────────────────────────────────────────────────────────────┘
| 层次 | 关注点 | 典型问题 | 本系列覆盖 |
|---|---|---|---|
| 协议层 | 数据如何传输 | TCP 为什么需要三次握手? | 第4、11、15讲 |
| 组件层 | 组件如何协同 | 负载均衡器选 Nginx 还是 HAProxy? | 第6、7、8、25讲 |
| 系统层 | 全局如何权衡 | 100万并发需要多少带宽?成本多少? | 第5、21、23讲 |
🔬 实战实验:tcpdump 抓包分析 HTTP 请求完整生命周期
实验目标:通过抓包直观理解一次 HTTP 请求经历了哪些网络层交互。
实验拓扑:
┌──────────┐ ┌──────────┐
│ Server A │ ──────→ │ Server B │
│ Client │ HTTP │ Nginx │
│ .0.5 │ ←────── │ .0.207 │
└──────────┘ └──────────┘
Step 1:在 Server B 启动 Nginx 并开始抓包
bash
# Server B (192.168.0.207) --- 启动抓包
tcpdump -i eth0 -nn -s0 -w /tmp/http_lifecycle.pcap \
'tcp port 8080' &
# 启动简易 HTTP 服务
python3 -m http.server 8080 --bind 0.0.0.0 &
Step 2:从 Server A 发起 HTTP 请求
bash
# Server A (192.168.0.5) --- 发起请求
curl -v http://192.168.0.207:8080/
Step 3:分析抓包结果
# 停止抓包并分析
pkill tcpdump
tcpdump -nn -r /tmp/http_lifecycle.pcap -A
预期输出(带标注):
# === TCP 三次握手 ===
17:12:01.123456 IP 192.168.0.5.54321 > 192.168.0.207.8080: Flags [S], seq 1000 ← SYN
17:12:01.123789 IP 192.168.0.207.8080 > 192.168.0.5.54321: Flags [S.], seq 2000, ack 1001 ← SYN-ACK
17:12:01.124012 IP 192.168.0.5.54321 > 192.168.0.207.8080: Flags [.], ack 2001 ← ACK (握手完成)
# === HTTP 请求 ===
17:12:01.124200 IP 192.168.0.5.54321 > 192.168.0.207.8080: Flags [P.], seq 1001:1080
GET / HTTP/1.1
Host: 192.168.0.207:8080
User-Agent: curl/8.5.0
Accept: */*
# === HTTP 响应 ===
17:12:01.124500 IP 192.168.0.207.8080 > 192.168.0.5.54321: Flags [P.], seq 2001:2500
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.12.3
Content-type: text/html
# === TCP 四次挥手 ===
17:12:02.125000 IP 192.168.0.207.8080 > 192.168.0.5.54321: Flags [F.], seq 2500 ← FIN
17:12:02.125100 IP 192.168.0.5.54321 > 192.168.0.207.8080: Flags [.], ack 2501 ← ACK
17:12:02.125200 IP 192.168.0.5.54321 > 192.168.0.207.8080: Flags [F.], seq 1080 ← FIN
17:12:02.125300 IP 192.168.0.207.8080 > 192.168.0.5.54321: Flags [.], ack 1081 ← ACK
📊 一次 HTTP 请求的时间线
时间轴 (ms) 事件
────────────────────────────────────────────────────
0.000 Client 发起 TCP SYN
0.333 Server 回复 SYN-ACK (RTT=0.333ms, 同网段极快)
0.556 三次握手完成
0.744 Client 发送 HTTP GET 请求
1.044 Server 返回 HTTP 200 响应
2.045 Server 主动关闭连接 (HTTP/1.0)
2.256 四次挥手完成
总耗时: ~2.3ms (内网环境)
⚠️ 避坑清单
| 序号 | 坑 | 现象 | 根因 | 解决 |
|---|---|---|---|---|
| 1 | ping 通但服务不通 | ping 192.168.0.207 成功,curl 失败 |
ICMP 通 ≠ TCP 端口通;防火墙可能拦截特定端口 | telnet 192.168.0.207 8080 验证 |
| 2 | tcpdump 抓不到包 | 抓包文件为空 | 接口名错误(ens3 vs eth0)或过滤条件太严 | ip link show 确认接口名 |
| 3 | 大量 TIME_WAIT | ss -s 显示数千 TIME_WAIT |
主动关闭方积累 | net.ipv4.tcp_tw_reuse=1 |
🔭 扩展思考
- 如果跨公网(延迟 50ms),这个流程会慢多少? → 第13讲弱网优化
- 如果并发 10000 个这样的请求,系统瓶颈在哪? → 第7讲集群扩展
- 如何让这个 HTTP 请求更安全? → 第19讲 TLS/SSL
💰 成本意识
| 场景 | 带宽需求 | 月成本估算 |
|---|---|---|
| 1000 QPS × 10KB 响应 | ~80 Mbps | 华为云按流量 ≈ ¥500/月 |
| 同上 + CDN 缓存 90% | ~8 Mbps 回源 | CDN ≈ ¥200/月 + 源站 ≈ ¥50/月 |
第2讲:导学|实验说明 & 学习指南
🎯 核心目标
降低动手门槛,确保读者能复现本系列所有实验。
本系列最大的特色是"每一讲都可以动手复现"。为达到这个目标,我们需要统一实验环境、工具链和操作规范。
🏗️ 推荐实验环境
方案一:本系列实际使用的环境(ecs-ee63 集群)
┌──────────────────────────────────────────────────────────┐
│ ecs-ee63 TCP/IP 实验集群 │
│ 华为云香港 | c6.large.2 (2vCPU/4GB) │
├──────────────────────────────────────────────────────────┤
│ │
│ 节点列表: │
│ ┌──────────────┬─────────────────┬───────────────────┐ │
│ │ 节点 │ 公网 IP │ 内网 IP │ │
│ ├──────────────┼─────────────────┼───────────────────┤ │
│ │ Server A │ 119.8.59.15 │ 192.168.0.5 │ │
│ │ Server B │ 119.8.110.76 │ 192.168.0.207 │ │
│ │ Server C │ 150.40.239.207 │ 192.168.0.111 │ │
│ │ Server D │ 159.138.137.101 │ 192.168.0.44 │ │
│ └──────────────┴─────────────────┴───────────────────┘ │
│ │
│ 操作系统: Ubuntu 24.04 LTS │
│ Docker: 29.5.2 (overlay2, Cgroup v2) │
│ SSH 工具: python D:/tools/ssh_exec.py │
└──────────────────────────────────────────────────────────┘
方案二:本地 Docker Compose 一键部署
yaml
# docker-compose.yml --- 本地实验环境
version: '3.8'
services:
server-a:
image: ubuntu:24.04
container_name: netlab-server-a
networks:
netlab:
ipv4_address: 192.168.100.5
cap_add:
- NET_ADMIN
- SYS_PTRACE
command: tail -f /dev/null
server-b:
image: ubuntu:24.04
container_name: netlab-server-b
networks:
netlab:
ipv4_address: 192.168.100.207
cap_add:
- NET_ADMIN
command: tail -f /dev/null
networks:
netlab:
driver: bridge
ipam:
config:
- subnet: 192.168.100.0/24
bash
# 一键启动本地实验环境
docker compose up -d
# 进入容器
docker exec -it netlab-server-a bash
# 安装实验工具
apt update && apt install -y tcpdump iperf3 nmap curl dnsutils iproute2 hping3
🛠️ 必备工具链详解
| 工具 | 版本 | 用途 | 典型命令 |
|---|---|---|---|
| tcpdump | 4.99+ | 抓包分析 | tcpdump -i eth0 -nn port 80 |
| Wireshark | 4.x | 图形化协议分析 | 本地安装,导入 pcap 文件 |
| iperf3 | 3.16+ | 带宽/吞吐测试 | iperf3 -c 192.168.0.207 -t 30 |
| nmap | 7.94+ | 端口扫描 | nmap -sS 192.168.0.0/24 |
| dig | 9.18+ | DNS 诊断 | dig +trace www.baidu.com |
| ss | --- | Socket 统计 | ss -tlnp |
| tc | --- | 流量控制/弱网模拟 | tc qdisc add dev eth0 root netem delay 100ms |
| curl | 8.x | HTTP 客户端 | curl -v https://example.com |
| openssl | 3.x | TLS 诊断 | openssl s_client -connect example.com:443 |
| sysdig | 0.38+ | 系统级抓包 | sysdig -c echo_fds fd.port=8080 |
| bpftrace | 0.20+ | eBPF 动态追踪 | bpftrace -e 'kprobe:tcp_connect { ... }' |
📖 如何阅读实验输出
本系列实验输出采用统一的标注格式:
# === 标注说明 ===
17:12:01.123456 IP 192.168.0.5.54321 > 192.168.0.207.8080: Flags [S]
│ │ │ │ │ │ │
│ │ │ │ │ │ └─ TCP Flag
│ │ │ │ │ └─ 目的端口
│ │ │ │ └─ 目的 IP
│ │ │ └─ 源端口
│ │ └─ 源 IP
│ └─ 协议
└─ 时间戳 (精确到微秒)
🔑 SSH 连接方式
本系列所有远程实验使用统一的 SSH 工具:
bash
# 单命令执行
python D:/tools/ssh_exec.py <host_ip> "<command>"
# 示例:查看 Server A 的网络接口
python D:/tools/ssh_exec.py 119.8.59.15 "ip addr show"
# 批量执行脚本
python D:/tools/ssh_exec.py 119.8.59.15 "bash -s" < /path/to/script.sh
⚠️ 实验注意事项
| 序号 | 注意事项 | 说明 |
|---|---|---|
| 1 | 🔴 高危命令标记 | iptables -F、tc qdisc del 等操作前请确认 |
| 2 | 🟡 网络中断风险 | 修改网络配置可能导致 SSH 断开 |
| 3 | 🟢 实验后清理 | 每个实验结束后恢复环境:tc qdisc del dev eth0 root |
| 4 | 🔵 端口冲突 | 多个实验不要同时占用相同端口 |
💰 实验环境成本
| 方案 | 配置 | 月成本 |
|---|---|---|
| 华为云 ECS × 4 (本系列) | c6.large.2 × 4 | ≈ ¥800/月 |
| 本地 Docker | 笔记本即可 | ¥0 |
| 云虚拟机按需 | t3.medium × 2 | ≈ ¥50/次实验 |
第3讲:加餐|高可靠网络架构与故障预防
🎯 核心主题
从事故反推架构设计原则 ------ 每一个架构决策背后,都是一次血的事故教训。
📋 案例拆解一:连接耗尽事故(TIME_WAIT 堆积)
事故背景:某电商大促期间,Nginx 反向代理突然返回 502 Bad Gateway,上游服务完全正常。
故障链路:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 用户 │────→│ Nginx │────→│ Backend │
│ │ │ :80 │ │ :3000 │
└──────────┘ └────┬─────┘ └──────────┘
│
Nginx → Backend 连接池耗尽
所有端口处于 TIME_WAIT
无法建立新连接!
根因分析:
bash
# 事故时 ss 输出
$ ss -s
Total: 31245
TCP: 28934 (estab 12, closed 28765, timewait 28765, ...)
^^^^^^^^
2.8万个 TIME_WAIT!
# 端口范围
$ sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768 60999
^^^^^ ^^^^^
可用端口仅 28231 个
时间线还原:
T+0min 大促开始,流量从 100 QPS → 5000 QPS
T+5min Nginx → Backend 短连接,每秒新建 3000+ 连接
T+8min 端口耗尽 (32768-60999 全部占用)
T+10min 新连接无法建立 → 502 Bad Gateway
T+12min 运维紧急介入,重启 Nginx 临时恢复
T+15min 再次耗尽...
根本原因:
- Nginx 到 Backend 使用 HTTP/1.0 短连接(默认 Connection: close)
- 每次请求 = 新建 TCP 连接 + 关闭 → TIME_WAIT 60s
net.ipv4.ip_local_port_range = 32768-60999仅 28231 个端口- 每秒新建 3000 连接 × 60s TIME_WAIT = 180000 个端口需求 >> 28231
修复方案:
nginx
# nginx.conf --- 关键修复配置
upstream backend {
server 192.168.0.207:3000;
server 192.168.0.111:3000;
keepalive 128; # ← 保持长连接池
keepalive_timeout 60s; # ← 空闲连接保持时间
keepalive_requests 1000;# ← 单连接最大请求数
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1; # ← 使用 HTTP/1.1
proxy_set_header Connection ""; # ← 清除 Connection: close
proxy_set_header Host $host;
}
}
bash
# 系统级优化
sysctl -w net.ipv4.tcp_tw_reuse=1 # 允许复用 TIME_WAIT
sysctl -w net.ipv4.ip_local_port_range="1024 65535" # 扩大端口范围
sysctl -w net.ipv4.tcp_fin_timeout=30 # 缩短 FIN_WAIT 超时
📋 案例拆解二:微服务级联雪崩
事故背景:一个 DB 慢查询拖垮整个微服务集群。
┌──────────┐
│ Gateway │
└────┬─────┘
┌─────────────┼─────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 订单服务 │ │ 用户服务 │ │ 库存服务 │
│ ThreadPool│ │ │ │ │
│ 200/200 ! │ │ │ │ │
└─────┬─────┘ └──────────┘ └──────────┘
│
▼
┌──────────┐
│ MySQL │
│ 慢查询 3s │ ← 祸根
└──────────┘
雪崩链条:
T+0s 订单服务发起 DB 查询(正常 50ms,当前 3000ms)
T+5s 订单服务线程池 200 个线程全部阻塞在 DB 查询上
T+10s Gateway → 订单服务 超时(3s timeout)
T+12s Gateway 线程池也被占满
T+15s 用户服务、库存服务虽正常,但 Gateway 已不可用
T+20s 整个集群瘫痪
预防 checklist(架构评审10条黄金准则):
| # | 准则 | 检查项 | 本系列对应 |
|---|---|---|---|
| 1 | 超时必设 | 所有外部调用必须设超时(connect + read timeout) | 第13讲 |
| 2 | 熔断必配 | 下游故障时快速失败,不拖垮上游 | 第9讲 |
| 3 | 限流必做 | 每个服务入口必须有流量控制 | 第8、25讲 |
| 4 | 降级必有 | 核心链路故障时要有兜底方案 | 第9讲 |
| 5 | 隔离必分 | 不同优先级请求使用独立线程池 | 第12讲 |
| 6 | 重试必慎 | 重试必须幂等 + 退避 + 上限 | 第13讲 |
| 7 | 连接池必限 | 数据库/Redis/HTTP 连接池设上限 | 第3讲 |
| 8 | 监控必全 | RED(Rate/Error/Duration)黄金指标 | 第6讲 |
| 9 | 演练必做 | 定期混沌工程(Chaos Engineering) | 第6讲 |
| 10 | 回滚必快 | 任何变更必须可一键回滚 | 第9讲 |
📋 案例拆解三:DNS 劫持导致的流量误导
事故背景:某 App 用户反馈"页面打不开",实际是 DNS 劫持导致流量被导向恶意服务器。
攻击原理:
正常流程:
用户 DNS 查询 api.example.com
→ 权威 DNS 返回 1.2.3.4
→ 用户连接真实服务器 ✓
劫持流程:
用户 DNS 查询 api.example.com
→ 中间人伪造 DNS 响应
→ 返回 5.6.7.8 (攻击者服务器)
→ 用户数据被窃取 ✗
防御方案:
bash
# 1. DNS over HTTPS (DoH)
# 使用 cloudflared 代理
cloudflared proxy-dns --port 5353 --upstream https://1.1.1.1/dns-query
# 2. DNSSEC 验证
dig +dnssec example.com
# 3. 证书固定 (Certificate Pinning)
# 客户端只信任特定证书
⚠️ 架构评审 checklist 速查卡
┌─────────────────────────────────────────────────────────────┐
│ 架构评审 10 条黄金准则 │
├─────────────────────────────────────────────────────────────┤
│ │
│ [ ] 1. 所有外部调用都有超时设置吗? │
│ [ ] 2. 核心链路的熔断器配置了吗? │
│ [ ] 3. 每个服务入口有限流保护吗? │
│ [ ] 4. 非关键功能可以降级吗? │
│ [ ] 5. 不同优先级请求隔离了吗? │
│ [ ] 6. 重试策略是幂等的吗?有退避吗? │
│ [ ] 7. 所有连接池都有上限吗? │
│ [ ] 8. RED 指标 (Rate/Error/Duration) 都有监控吗? │
│ [ ] 9. 最近做过故障演练吗? │
│ [ ] 10. 可以一键回滚到上一个版本吗? │
│ │
└─────────────────────────────────────────────────────────────┘
🔭 扩展思考
- 如果 TIME_WAIT 是 Linux 内核行为,能否从根本上避免? → TCP 协议设计如此,无法避免,只能优化
- 级联雪崩的核心矛盾是什么? → 同步调用 + 无超时 + 无熔断 = 必然雪崩
- DNS 劫持之外还有哪些"中间人"攻击面? → ARP 欺骗、BGP 劫持、SSL 中间人
🔹 基础篇
第4讲:一个数据包的网络之旅:网络是如何工作的?
🎯 核心目标
可视化理解一个数据包穿越 TCP/IP 七层模型的完整过程。
🔍 从 ping www.baidu.com 开始
让我们从一个最简单的命令开始,追踪数据包经历的一切:
bash
$ ping www.baidu.com -c 1
PING www.a.shifen.com (110.242.68.66) 56(84) bytes of data.
64 bytes from 110.242.68.66: icmp_seq=1 ttl=52 time=37.8 ms
这行输出背后,发生了什么?
🗺️ 全路径追踪
应用层 ping 程序构造 ICMP Echo Request
│
▼
传输层 封装为 IP 数据包 (Protocol=ICMP)
│
▼
网络层 查询路由表,确定下一跳网关
│ ↓ DNS 解析 www.baidu.com → 110.242.68.66
▼
数据链路层 ARP 查询网关 MAC 地址,封装以太网帧
│
▼
物理层 电信号/光信号传输
│
▼
┌──────────────────────────────────────────────────────┐
│ 经过的路由器/交换机 │
│ │
│ [本地网关] → [运营商路由器] → [骨干网] → │
│ [百度机房路由器] → [110.242.68.66] │
└──────────────────────────────────────────────────────┘
│
▼
百度服务器 逐层解包 → ICMP Echo Reply → 原路返回
🔬 实验一:traceroute + tcpdump 联合追踪
实验拓扑:
┌──────────┐ ┌──────────┐
│ Server A │ ──────→ │ Server B │
│ .0.5 │ tracer │ .0.207 │
│ │ oute │ │
└──────────┘ └──────────┘
Step 1:在 Server B 启动抓包
bash
# Server B (192.168.0.207) --- 抓取所有 ICMP 和 UDP 包
tcpdump -i eth0 -nn -v 'icmp or (udp and port 33434:33534)' &
Step 2:从 Server A 执行 traceroute
bash
# Server A (192.168.0.5)
traceroute -n 192.168.0.207
输出分析:
traceroute to 192.168.0.207 (192.168.0.207), 30 hops max, 60 byte packets
1 192.168.0.207 0.333 ms 0.289 ms 0.301 ms
同网段一跳直达,延迟 < 1ms。
Step 3:跨网段 traceroute(到公网)
bash
# Server A → 百度
traceroute -n 110.242.68.66
典型输出(香港 ECS → 百度北京):
1 192.168.0.1 0.512 ms 0.445 ms 0.432 ms ← 本地网关
2 100.127.0.1 2.134 ms 2.089 ms 2.101 ms ← 华为云网关
3 10.200.1.1 3.456 ms 3.398 ms 3.412 ms ← 华为云核心
4 * * * ← 骨干网(不响应 ICMP)
5 202.97.xx.xx 28.567 ms 28.512 ms 28.543 ms ← 中国电信骨干
6 202.97.xx.xx 35.234 ms 35.189 ms 35.201 ms ← 电信北京节点
7 110.242.68.66 37.845 ms 37.812 ms 37.834 ms ← 百度服务器
📊 七层模型 vs 实际协议栈
┌──────────────────────────────────────────────────────────┐
│ OSI 七层模型 vs TCP/IP 四层 │
├──────────┬──────────┬────────────────────────────────────┤
│ OSI │ TCP/IP │ 协议/设备举例 │
├──────────┼──────────┼────────────────────────────────────┤
│ 应用层 │ │ HTTP, DNS, SMTP, FTP │
│ 表示层 │ 应用层 │ TLS/SSL, JPEG, ASCII │
│ 会话层 │ │ RPC, NetBIOS │
├──────────┼──────────┼────────────────────────────────────┤
│ 传输层 │ 传输层 │ TCP, UDP, SCTP │
├──────────┼──────────┼────────────────────────────────────┤
│ 网络层 │ 网络层 │ IP, ICMP, OSPF, BGP │
│ │ │ 路由器 │
├──────────┼──────────┼────────────────────────────────────┤
│ 数据链路层 │ 网络接口层 │ Ethernet, ARP, VLAN │
│ 物理层 │ │ 交换机, 网卡, 光纤 │
└──────────┴──────────┴────────────────────────────────────┘
🔬 实验二:数据包的逐层封装与解封装
发送端封装过程:
┌──────────────────────────────────────────┐
│ 应用层: HTTP GET /index.html │ ← 应用数据
├──────────────────────────────────────────┤
│ 传输层: TCP Header + 应用数据 │ ← 源端口:54321 目的端口:80
├──────────────────────────────────────────┤
│ 网络层: IP Header + TCP + 应用数据 │ ← 源IP:192.168.0.5 目的IP:192.168.0.207
├──────────────────────────────────────────┤
│ 链路层: MAC Header + IP + TCP + 数据 │ ← 源MAC:aa:bb 目的MAC:cc:dd
│ + FCS(校验) │
└──────────────────────────────────────────┘
接收端解封装: 反向逐层剥离
⚠️ 避坑:为什么 ping 通 ≠ 应用可达?
bash
# ping 成功
$ ping 192.168.0.207
64 bytes from 192.168.0.207: icmp_seq=1 ttl=64 time=0.333 ms
# curl 失败
$ curl http://192.168.0.207:8080/
curl: (7) Failed to connect to 192.168.0.207 port 8080: Connection refused
| 层级 | ping (ICMP) | curl (HTTP/TCP) |
|---|---|---|
| 网络层 (IP) | ✅ 可达 | ✅ 可达 |
| 传输层 (TCP) | ❌ 不涉及 | ❌ 端口 8080 未监听 |
| 防火墙 | ✅ ICMP 允许 | ❌ TCP 8080 被拦截 |
诊断方法:
bash
# 1. 确认端口是否监听
ss -tlnp | grep 8080
# 2. 确认防火墙规则
iptables -L -n -v | grep 8080
# 3. TCP 连接测试
telnet 192.168.0.207 8080
🔭 扩展思考
- 如果数据包超过 MTU(1500字节),会发生什么? → IP 分片(Fragmentation),性能下降
- 为什么 TCP 需要三次握手而不是两次? → 防止历史连接初始化
- QUIC 协议如何简化这个流程? → 0-RTT 握手,基于 UDP
第5讲:架构设计思考:网络架构设计要考虑哪些要素?
🎯 核心框架
CAP + SLA + Cost + Security + Operability ------ 五维评估模型
网络架构设计从来不是纯技术决策,而是在多个相互制约的维度之间做权衡。
📐 五维评估模型
可靠性
(Reliability)
▲
/|\
/ | \
/ | \
/ | \
成本 ◄────/────┼────\────► 性能
(Cost) / | \ (Performance)
/ | \
/ | \
/ | \
▼ ▼ ▼
安全性 可运维性
(Security) (Operability)
📊 五维详解
1. 可靠性(Reliability)
| 指标 | 含义 | 目标值 | 实现手段 |
|---|---|---|---|
| MTBF | 平均故障间隔时间 | > 720小时 | 冗余设计 |
| MTTR | 平均修复时间 | < 5分钟 | 自动故障转移 |
| 可用性 | 正常运行时间比例 | 99.99%(年停机<52分钟) | 多活架构 |
| RPO | 故障时数据丢失量 | < 1秒 | 同步复制 |
| RTO | 故障恢复时间 | < 5分钟 | 自动切换 |
可用性级别对照:
99% = 年停机 3.65 天 → 单机即可
99.9% = 年停机 8.76 小时 → 需要主备
99.99% = 年停机 52.56 分钟 → 需要多活
99.999% = 年停机 5.26 分钟 → 需要跨地域多活
2. 性能(Performance)
| 指标 | 含义 | 测量工具 |
|---|---|---|
| 延迟 (Latency) | 请求-响应时间 | curl -w "@curl-format.txt" |
| 吞吐 (Throughput) | 每秒处理请求数 | wrk -t4 -c100 -d30s |
| 并发 (Concurrency) | 同时处理的连接数 | ss -s |
| 带宽 (Bandwidth) | 每秒传输数据量 | iperf3 |
延迟预算(用户感知阈值):
< 100ms → 即时响应(用户感觉不到延迟)
100-300ms → 轻微延迟(可接受)
300-1000ms→ 明显延迟(用户开始不耐烦)
> 1000ms → 严重延迟(用户可能离开)
3. 安全性(Security)
| 层次 | 防护措施 | 本系列对应 |
|---|---|---|
| 边界防护 | WAF + DDoS 清洗 | 第21讲 |
| 传输加密 | TLS 1.3 + mTLS | 第19讲 |
| 身份认证 | JWT + OAuth2.0 | --- |
| 访问控制 | RBAC + 最小权限 | 第23讲 |
| 审计日志 | 全量 API 日志 + SIEM | --- |
4. 成本(Cost)
| 成本类型 | 示例 | 优化手段 |
|---|---|---|
| 计算 | ECS 实例费用 | 合理规格 + 弹性伸缩 |
| 网络 | 公网带宽/流量 | CDN + 内网直连 |
| 存储 | 日志/备份 | 生命周期管理 |
| 运维 | 人力成本 | 自动化 + 自愈 |
5. 可运维性(Operability)
| 能力 | 工具 | 目标 |
|---|---|---|
| 监控 | Prometheus + Grafana | RED 指标全覆盖 |
| 告警 | AlertManager + 钉钉 | 5分钟内响应 |
| 日志 | ELK / Loki | 集中存储 + 全文检索 |
| 追踪 | Jaeger / SkyWalking | 全链路追踪 |
| 排障 | eBPF + tcpdump | 分钟级定位 |
🧪 实战:五维评估练习
场景:设计一个日活 100 万的电商 App 后端网络架构。
评估过程:
1. 可靠性:
需要 99.99% → 至少同城双活
选型: 阿里云杭州 + 上海
2. 性能:
预估 QPS: 100万DAU × 平均10次请求/天 / 86400秒 ≈ 115 QPS (平均)
峰值: 115 × 5 = 575 QPS
带宽: 575 × 50KB = 28.75 MB/s ≈ 230 Mbps
3. 安全性:
用户数据: TLS 1.3 + HTTPS 全站
支付接口: 额外 mTLS + 签名验证
DDoS: 云防护 100Gbps
4. 成本:
计算: 4台 ECS × ¥500 + K8s 托管 ¥1000 = ¥3000/月
网络: CDN 100Mbps ¥2000/月 + 公网 ¥1000/月 = ¥3000/月
安全: WAF ¥2000/月 + DDoS ¥5000/月 = ¥7000/月
总计: ≈ ¥13000/月
5. 可运维:
监控: Prometheus (自建, ¥0)
日志: ELK 自建 ¥2000/月
追踪: Jaeger 自建 ¥0
📋 架构设计决策矩阵
| 场景 | 可靠性方案 | 性能方案 | 成本方案 |
|---|---|---|---|
| 个人博客 | 单机 + 定期备份 | CDN 静态化 | < ¥50/月 |
| 初创 SaaS | 主备 + 自动切换 | CDN + 缓存 | ¥1000-3000/月 |
| 中型电商 | 同城双活 | CDN + Redis + 读写分离 | ¥10000-30000/月 |
| 大型平台 | 三地五中心 | 边缘计算 + 全球加速 | ¥100000+/月 |
⚠️ 常见权衡陷阱
| 陷阱 | 描述 | 正确做法 |
|---|---|---|
| 过度设计 | 10人团队设计 Google 级架构 | 从单体开始,按需演进 |
| 过早优化 | 日活100就上 K8s + 微服务 | 先用单体验证商业模式 |
| 忽视成本 | 全部用最高配 | 用监控数据驱动降配 |
| 安全后置 | 上线后再考虑安全 | 安全左移(Shift Left) |
🔭 扩展思考
- 五维中哪个维度最重要? → 取决于业务阶段:早期重性能/成本,成熟期重可靠性/安全
- 如何量化"够用"? → 用 SLO(Service Level Objective)定义,如"99.9% 请求在 200ms 内完成"
- 成本优化有上限吗? → 有。过度优化会导致可靠性下降,需要在五维之间找平衡点
🔹 实战篇(上)
第6讲:主备:怎样防范单点故障?
🎯 核心问题
如何用最小的成本,让服务在单台机器故障时自动恢复?
📐 高可用模式对比
| 模式 | 资源利用率 | 切换时间 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| Active-Standby | 50% | 1-10s | 低 | 数据库、状态服务 |
| Active-Active | 100% | 0s | 高 | 无状态 Web 服务 |
| N+1 冗余 | N/(N+1) | 1-10s | 中 | 同质化计算节点 |
🔬 实战实验:keepalived + nginx VIP 漂移
实验拓扑:
Internet
│
▼
┌─────────────────┐
│ VIP (虚拟IP) │
│ 192.168.0.100 │
└────────┬────────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Server A │ │ Server B │
│ MASTER │ VRRP │ BACKUP │
│ 192.168.0.5 │◄───────►│ 192.168.0.207│
│ priority=100│ │ priority=90 │
│ nginx :80 │ │ nginx :80 │
└──────────────┘ └──────────────┘
Step 1:两台服务器都安装 keepalived + nginx
bash
# Server A 和 Server B 都执行
apt update && apt install -y keepalived nginx
# 配置 Nginx 显示主机名以便区分
echo "Server A (MASTER)" > /var/www/html/index.html # Server A
echo "Server B (BACKUP)" > /var/www/html/index.html # Server B
systemctl start nginx
Step 2:配置 keepalived
bash
# Server A --- /etc/keepalived/keepalived.conf
cat > /etc/keepalived/keepalived.conf << 'EOF'
vrrp_script chk_nginx {
script "/usr/bin/killall -0 nginx" # 检查 nginx 是否存活
interval 2 # 每2秒检查一次
weight -20 # 失败则优先级-20
}
vrrp_instance VI_1 {
state MASTER # 初始状态: 主
interface eth0 # 绑定网卡
virtual_router_id 51 # VRRP 组 ID (主备必须一致!)
priority 100 # 优先级 (高的成为 MASTER)
advert_int 1 # 通告间隔 (秒)
authentication {
auth_type PASS
auth_pass 1qaz@WSX # VRRP 认证密码 (主备必须一致!)
}
virtual_ipaddress {
192.168.0.100/24 # VIP 地址
}
track_script {
chk_nginx # 关联健康检查
}
}
EOF
bash
# Server B --- /etc/keepalived/keepalived.conf
cat > /etc/keepalived/keepalived.conf << 'EOF'
vrrp_script chk_nginx {
script "/usr/bin/killall -0 nginx"
interval 2
weight -20
}
vrrp_instance VI_1 {
state BACKUP # 初始状态: 备
interface eth0
virtual_router_id 51 # 与 MASTER 一致!
priority 90 # 低于 MASTER
advert_int 1
authentication {
auth_type PASS
auth_pass 1qaz@WSX # 与 MASTER 一致!
}
virtual_ipaddress {
192.168.0.100/24
}
track_script {
chk_nginx
}
}
EOF
Step 3:启动 keepalived 并验证
bash
# 两台服务器都启动
systemctl start keepalived
systemctl enable keepalived
# 在 Server A 上查看 VIP 状态
ip addr show eth0 | grep 192.168.0.100
# 输出: inet 192.168.0.100/24 scope global secondary eth0
# ✓ MASTER 持有 VIP
# 在 Server B 上查看
ip addr show eth0 | grep 192.168.0.100
# (无输出) ✓ BACKUP 不持有 VIP
Step 4:模拟故障 --- 停止 Server A 的 keepalived
bash
# Server A --- 模拟故障
systemctl stop keepalived
# 立即在 Server B 上查看
ip addr show eth0 | grep 192.168.0.100
# 输出: inet 192.168.0.100/24 scope global secondary eth0
# ✓ VIP 已漂移到 BACKUP
Step 5:恢复测试
bash
# Server A --- 恢复
systemctl start keepalived
# Server A 重新成为 MASTER,VIP 漂回
ip addr show eth0 | grep 192.168.0.100
# ✓ VIP 回到 MASTER
📊 故障切换时间线
时间轴 事件 VIP 位置
──────────────────────────────────────────────────────────
T+0s Server A keepalived 停止 Server A
T+0s 最后一次 VRRP 通告发出
T+1s Server B 未收到通告 (advert_int=1)
T+3s Server B 等待 3×advert_int Server A
T+3.001s Server B 判定 MASTER 故障
T+3.002s Server B 发送免费 ARP Server A
T+3.010s Server B 接管 VIP Server B
T+3.100s 交换机更新 MAC 表 Server B
T+3.200s 流量完全切到 Server B Server B
总切换时间: ~3.2 秒
⚠️ 避坑:脑裂(Split-Brain)预防
什么是脑裂?
┌──────────┐
│ 交换机 │
└─────┬────┘
│
┌───────────┴───────────┐
│ (链路中断!) │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Server A │ │ Server B │
│ MASTER │ │ BACKUP │
│ 认为自己是主 │ │ 也认为自己是主 │
│ 持有 VIP │ │ 也持有 VIP │ ← 脑裂!
└──────────────┘ └──────────────┘
预防措施:
bash
# 1. 启用 ARP 检查 (防止 IP 冲突)
# keepalived.conf 中添加:
vrrp_instance VI_1 {
# ...
# 当成为 MASTER 时执行的脚本
notify_master "/usr/local/bin/notify-master.sh"
}
# notify-master.sh
#!/bin/bash
# 发送免费 ARP 更新交换机 MAC 表
arping -c 5 -I eth0 -s 192.168.0.100 192.168.0.1
logger "Keepalived: This node became MASTER with VIP 192.168.0.100"
# 2. 使用仲裁 (Quorum) 机制
# 对于 ≥ 3 节点的集群,使用 etcd/consul 做 leader election
# 避免双节点脑裂
🔭 扩展思考
- keepalived 适合无状态服务,数据库主备怎么做? → MySQL MHA/Orchestrator,第12讲
- VIP 漂移的流量中断时间能否做到 0? → 不能。但可以做到 < 1s(优化 advert_int)
- 跨机房主备怎么做? → 需要 BGP Anycast + DNS 切换
第7讲:集群(下):怎样实现横向扩展?
🎯 核心问题
当单机性能达到瓶颈时,如何通过增加机器来线性扩展处理能力?
注:课程标题为"集群(下)",假定上篇介绍了集群基础概念。本篇聚焦于无状态服务的水平扩展。
📐 负载均衡算法对比
| 算法 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 轮询 (Round Robin) | 依次分发 | 简单公平 | 无视服务器负载 | 同配置服务器 |
| 加权轮询 (Weighted RR) | 按权重比例分发 | 适配异构集群 | 权重需手动调整 | 不同配置服务器 |
| 最少连接 (Leastconn) | 发给连接数最少的 | 动态适应 | 长连接下不准确 | 短连接服务 |
| 一致性哈希 (Consistent Hash) | 按 key 哈希到固定节点 | 扩缩容影响小 | 负载可能不均 | 缓存/会话保持 |
| 最短响应时间 | 发给响应最快的 | 自适应 | 需要持续测量 | 计算密集型 |
🔬 实战实验:HAProxy + 3×Nginx 负载均衡集群
实验拓扑:
Client
│
▼
┌─────────────────┐
│ HAProxy │
│ 192.168.0.100 │
│ 统计页面 :8404 │
└────────┬────────┘
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Nginx-1 │ │ Nginx-2 │ │ Nginx-3 │
│ .0.5:8081│ │.0.207:8081│ │.0.111:8081│
└──────────┘ └──────────┘ └──────────┘
Step 1:三台服务器启动 Nginx
bash
# Server A (192.168.0.5)
echo "<h1>Nginx-1 (Server A)</h1>" > /var/www/html/index.html
sed -i 's/listen 80/listen 8081/' /etc/nginx/sites-available/default
systemctl restart nginx
# Server B (192.168.0.207)
echo "<h1>Nginx-2 (Server B)</h1>" > /var/www/html/index.html
sed -i 's/listen 80/listen 8081/' /etc/nginx/sites-available/default
systemctl restart nginx
# Server C (192.168.0.111)
echo "<h1>Nginx-3 (Server C)</h1>" > /var/www/html/index.html
sed -i 's/listen 80/listen 8081/' /etc/nginx/sites-available/default
systemctl restart nginx
Step 2:在 Server D 安装配置 HAProxy
bash
# Server D (192.168.0.44)
apt install -y haproxy
cat > /etc/haproxy/haproxy.cfg << 'EOF'
global
log /dev/log local0
maxconn 4096
daemon
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
# 统计页面
listen stats
bind :8404
mode http
stats enable
stats uri /stats
stats realm HAProxy\ Statistics
stats auth admin:admin123
# 轮询 (Round Robin)
frontend web_rr
bind :80
default_backend nginx_rr
backend nginx_rr
balance roundrobin # 轮询算法
server nginx1 192.168.0.5:8081 check
server nginx2 192.168.0.207:8081 check
server nginx3 192.168.0.111:8081 check
# 最少连接 (Least Connections)
frontend web_lc
bind :81
default_backend nginx_lc
backend nginx_lc
balance leastconn # 最少连接算法
server nginx1 192.168.0.5:8081 check
server nginx2 192.168.0.207:8081 check
server nginx3 192.168.0.111:8081 check
# 一致性哈希 (Consistent Hash)
frontend web_ch
bind :82
default_backend nginx_ch
backend nginx_ch
balance uri # 按 URI 哈希
hash-type consistent # 一致性哈希
server nginx1 192.168.0.5:8081 check
server nginx2 192.168.0.207:8081 check
server nginx3 192.168.0.111:8081 check
EOF
systemctl restart haproxy
Step 3:验证负载均衡效果
bash
# 轮询测试
for i in {1..12}; do
curl -s http://192.168.0.44/
done
# 预期输出 (每台 4 次):
# Nginx-1 → Nginx-2 → Nginx-3 → Nginx-1 → Nginx-2 → Nginx-3 → ...
# 一致性哈希测试 (相同 URI 始终到同一台)
for i in {1..5}; do
curl -s http://192.168.0.44:82/user/profile
done
# 始终输出: Nginx-1 (或其他固定节点)
for i in {1..5}; do
curl -s http://192.168.0.44:82/order/list
done
# 始终输出: Nginx-2 (或其他固定节点)
📊 性能对比数据
使用 wrk 压测不同算法(以实际集群数据为例):
测试条件: wrk -t4 -c100 -d30s
算法 QPS 平均延迟 P99延迟 备注
─────────────────────────────────────────────────────────
Round Robin 8,234 12.1ms 45.2ms 均衡
Leastconn 8,456 11.8ms 42.1ms 略优 (短连接)
URI Hash 7,890 12.6ms 48.3ms 缓存命中率高
Weighted (2:1:1) 7,560 13.2ms 51.5ms 适配异构
⚠️ 避坑清单
| 序号 | 坑 | 现象 | 解决方案 |
|---|---|---|---|
| 1 | 会话丢失 | 登录后刷新跳到别的服务器,session 失效 | 使用一致性哈希 或 Redis 集中存储 session |
| 2 | 健康检查误判 | 服务正常但 HAProxy 标记为 DOWN | 调整 inter(检查间隔)和 fall(失败次数) |
| 3 | HAProxy 自身单点 | HAProxy 宕机整个集群不可用 | HAProxy + keepalived 做高可用 (见第6讲) |
| 4 | 长连接不均衡 | leastconn 对 WebSocket 不准确 | 使用 balance source 按源 IP 哈希 |
🔭 扩展思考
- HAProxy vs Nginx vs Envoy 怎么选? → HAProxy 纯负载均衡最强;Nginx 多功能;Envoy 云原生首选
- Kubernetes 中如何实现负载均衡? → Service (kube-proxy) + Ingress Controller
- 1000 个后端节点的负载均衡怎么做? → 两级 LB:边缘 LB → 内部 LB → 后端
第8讲:限流:怎样防止应用被打垮?
🎯 核心问题
当流量超出系统容量时,如何优雅地拒绝而非崩溃?
📐 三大限流模型
┌──────────────────────────────────────────────────────────────┐
│ 令牌桶 (Token Bucket) │
│ │
│ 令牌以固定速率 r 加入桶中 ┌───┐ │
│ ↓ ↓ ↓ ↓ ↓ │ │ ← 桶容量 b │
│ ┌─────────────┐ │ │ │
│ │ ████████ │ │ │ │
│ │ ████████ │ └───┘ │
│ └─────────────┘ │
│ 请求到达 → 取走 1 个令牌 │
│ 无令牌 → 拒绝 │
│ │
│ 特点: 允许突发 (桶内可积累令牌),适合有 burst 需求的场景 │
├──────────────────────────────────────────────────────────────┤
│ 漏桶 (Leaky Bucket) │
│ │
│ 请求以任意速率进入 │
│ ↓ ↓ ↓ ↓ ↓ ↓ ↓ (突发) │
│ ┌─────────────┐ │
│ │ ████████████│ │
│ │ ████████████│ │
│ └──────┬──────┘ │
│ │ → → → 以固定速率 r 流出 │
│ │
│ 特点: 严格平滑输出,不允许突发,适合需要稳定出队的场景 │
├──────────────────────────────────────────────────────────────┤
│ 滑动窗口 (Sliding Window) │
│ │
│ 时间窗口 W=1s, 限制 L=10 │
│ │
│ ──┬────┬────┬────┬────┬────┬──→ 时间 │
│ │ │ │ │ │ │ │
│ 3req 2req 4req 1req 5req ← 窗口内总计 15 > 10,拒绝! │
│ │
│ 特点: 精准计数,无 burst,适合精确 QPS 控制 │
└──────────────────────────────────────────────────────────────┘
🔬 实验一:Nginx limit_req 限流
Step 1:配置限流
nginx
# /etc/nginx/nginx.conf
http {
# 定义限流区域: 按客户端 IP,10MB 共享内存,速率 10r/s
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
# 定义连接数限制区域
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
server {
listen 8082;
# === 场景1: 严格限流 (无 burst) ===
location /api/strict/ {
limit_req zone=api_limit; # 严格 10r/s
return 200 "OK - strict\n";
}
# === 场景2: 允许突发 (burst + nodelay) ===
location /api/burst/ {
limit_req zone=api_limit burst=5 nodelay; # 允许 5 个突发
return 200 "OK - burst\n";
}
# === 场景3: 允许突发排队 ===
location /api/queue/ {
limit_req zone=api_limit burst=5; # 5 个排队,不 nodelay
return 200 "OK - queue\n";
}
# === 场景4: 连接数限制 ===
location /api/conn/ {
limit_conn conn_limit 3; # 单 IP 最多 3 并发连接
return 200 "OK - conn\n";
}
}
}
Step 2:压测验证
bash
# 场景1: 严格限流 (10r/s, 无 burst)
# 用 ab 发送 20 个并发请求
ab -n 20 -c 20 http://192.168.0.44:8082/api/strict/
# 预期结果:
# Complete requests: 20
# Failed requests: 10 ← 10 个被限流 (503)
# Non-2xx responses: 10 ← 503 Service Unavailable
# 场景2: burst + nodelay (允许 5 个突发)
ab -n 20 -c 20 http://192.168.0.44:8082/api/burst/
# 预期结果:
# Complete requests: 20
# Failed requests: 5 ← 仅 5 个被限流
# (10r/s + 5 burst = 15 个被允许)
🔬 实验二:Redis + Lua 分布式滑动窗口限流
为什么需要分布式限流?
┌──────────┐
│ LB │
└────┬─────┘
┌─────────────┼─────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ App-1 │ │ App-2 │ │ App-3 │
│ 限流 10/s │ │ 限流 10/s │ │ 限流 10/s │ ← 单机限流 = 总共 30/s!
└──────────┘ └──────────┘ └──────────┘
我们需要: 整个集群 10/s
需要: 集中式限流 → Redis
Lua 脚本实现:
lua
-- rate_limiter.lua
-- KEYS[1]: 限流 key
-- ARGV[1]: 当前时间戳 (毫秒)
-- ARGV[2]: 窗口大小 (毫秒)
-- ARGV[3]: 限制数量
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
-- 删除窗口外的过期数据
local window_start = now - window
redis.call('ZREMRANGEBYSCORE', key, 0, window_start)
-- 统计窗口内请求数
local count = redis.call('ZCARD', key)
if count < limit then
-- 未超限: 添加当前请求记录
redis.call('ZADD', key, now, now .. ':' .. math.random())
redis.call('EXPIRE', key, math.ceil(window / 1000) + 1)
return 1 -- 允许通过
else
return 0 -- 限流
end
Python 调用示例:
python
import redis
import time
r = redis.Redis(host='192.168.0.70', port=6379)
with open('rate_limiter.lua', 'r') as f:
script = f.read()
rate_limiter = r.register_script(script)
def is_allowed(user_id, limit=10, window_ms=1000):
"""检查是否允许请求"""
key = f"ratelimit:{user_id}"
now_ms = int(time.time() * 1000)
return rate_limiter(keys=[key], args=[now_ms, window_ms, limit]) == 1
# 测试: 连续发送 15 个请求
for i in range(15):
if is_allowed("user_123", limit=10, window_ms=1000):
print(f"请求 {i}: ✓ 允许")
else:
print(f"请求 {i}: ✗ 限流")
📊 限流方案对比
| 方案 | 精度 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| Nginx limit_req | 高 | 极高 | 低 | 网关层限流 |
| Redis 滑动窗口 | 高 | 高 | 中 | 分布式精确限流 |
| Sentinel (Java) | 高 | 高 | 中 | Java 微服务 |
| Guava RateLimiter | 高 | 极高 | 低 | 单机 Java |
| 令牌桶自实现 | 中 | 高 | 中 | 自定义需求 |
⚠️ 避坑清单
| 序号 | 坑 | 描述 | 解决 |
|---|---|---|---|
| 1 | burst 理解错误 | burst=5 不是"每秒多5个",而是一次性突发容量 |
仔细读文档 |
| 2 | 限流粒度 | 按 IP 限流,NAT 网关后所有用户共用一个限额 | 按 userId/sessionId 限流 |
| 3 | Redis 单点 | Redis 挂了限流失效 | Redis Sentinel/Cluster |
| 4 | 时钟不同步 | 分布式节点时钟不一致导致滑动窗口不准 | NTP 同步 |
🔭 扩展思考
- 限流后的请求怎么办? → 排队(消息队列)或降级(返回缓存数据)
- 如何实现"动态限流"? → 根据系统负载自动调整限流阈值(自适应限流)
- API 网关(Kong/APISIX)内置了哪些限流插件? → rate-limiting, rate-limiting-advanced
第9讲:控影响:怎样可靠升级服务?
🎯 核心问题
如何在生产环境安全地升级服务,让用户无感知、故障秒级回滚?
📐 灰度发布策略演进
传统发布:
100% 流量 → 新版本
问题: 出故障 = 全量爆炸
蓝绿部署:
蓝环境(旧) ← 100% 流量
绿环境(新) ← 0% 流量 (部署+验证)
切换: 100% → 绿环境
优点: 秒级回滚 (切回蓝环境)
缺点: 需要双倍资源
金丝雀发布 (Canary):
90% 流量 → 旧版本
10% 流量 → 新版本 (金丝雀)
观察 10 分钟无异常 → 逐步增加比例
异常 → 自动回滚
优点: 影响范围可控
🔬 实战实验:Istio 金丝雀发布
实验拓扑:
┌──────────┐
│ Istio │
│ Ingress │
│ Gateway │
└────┬─────┘
│
┌────┴─────┐
│ Virtual │
│ Service │
└────┬─────┘
│
┌──────────┴──────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ app v1.0 │ │ app v2.0 │
│ (stable) │ │ (canary) │
│ weight: 90 │ │ weight: 10 │
└──────────────┘ └──────────────┘
Step 1:部署两个版本的应用
yaml
# app-v1.yaml --- 稳定版
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-v1
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: v1
template:
metadata:
labels:
app: myapp
version: v1
spec:
containers:
- name: app
image: nginx:1.24
ports:
- containerPort: 80
env:
- name: APP_VERSION
value: "v1.0-stable"
---
# app-v2.yaml --- 金丝雀版
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-v2
spec:
replicas: 1
selector:
matchLabels:
app: myapp
version: v2
template:
metadata:
labels:
app: myapp
version: v2
spec:
containers:
- name: app
image: nginx:1.25
ports:
- containerPort: 80
env:
- name: APP_VERSION
value: "v2.0-canary"
Step 2:配置 Istio 流量拆分
yaml
# virtual-service.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: myapp-vs
spec:
hosts:
- myapp.example.com
http:
- match:
- headers:
x-canary:
exact: "true" # 带特定 header 的请求走 v2
route:
- destination:
host: myapp
subset: v2
- route:
- destination:
host: myapp
subset: v1
weight: 90 # 90% 流量走 v1
- destination:
host: myapp
subset: v2
weight: 10 # 10% 流量走 v2
---
# destination-rule.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: myapp-dr
spec:
host: myapp
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
Step 3:观察金丝雀指标
bash
# 查看流量分布
kubectl exec -it deploy/istio-ingressgateway -n istio-system -- \
pilot-agent request GET stats | grep myapp
# 预期输出:
# cluster.outbound|80|v1|myapp.default.svc.cluster.local::upstream_rq_total: 900
# cluster.outbound|80|v2|myapp.default.svc.cluster.local::upstream_rq_total: 100
# 流量比 90:10 ✓
Step 4:自动回滚机制
yaml
# 如果 v2 错误率 > 5%,自动移除金丝雀
# 使用 Flagger (基于 Istio 的金丝雀发布工具)
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: myapp
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
service:
port: 80
analysis:
interval: 1m
threshold: 5 # 最多 5 次失败
maxWeight: 50 # 金丝雀最大流量比
stepWeight: 10 # 每次增加 10%
metrics:
- name: request-success-rate
thresholdRange:
min: 99 # 成功率 < 99% 则回滚
- name: request-duration
thresholdRange:
max: 500 # P99 > 500ms 则回滚
📊 灰度发布策略对比
| 策略 | 影响范围 | 回滚速度 | 资源成本 | 适用场景 |
|---|---|---|---|---|
| 滚动更新 | 逐步替换 | 分钟级 | 1× | 无状态服务 |
| 蓝绿部署 | 一次切换 | 秒级 | 2× | 关键服务 |
| 金丝雀发布 | 逐步扩大 | 自动 | 1.1× | 需要验证的服务 |
| A/B 测试 | 长期共存 | 手动 | 2× | 产品功能验证 |
⚠️ 避坑清单
| 序号 | 坑 | 描述 | 解决 |
|---|---|---|---|
| 1 | 数据库 Schema 不兼容 | 新版本修改了表结构,旧版本无法读写 | 分阶段变更:先加列 → 部署新代码 → 删旧列 |
| 2 | API 兼容性 | 新版本改了 API 响应格式 | 版本化 API:/api/v1/ /api/v2/ |
| 3 | 缓存不一致 | 新旧版本使用不同缓存 key 格式 | 缓存 key 包含版本号 |
| 4 | 金丝雀比例太小 | 1% 流量可能覆盖不到异常路径 | 至少 5-10%,配合流量染色 |
🔭 扩展思考
- 没有 Istio 怎么做金丝雀? → Nginx split_clients 模块
- 数据库变更如何灰度? → 读写分离 + 影子表
- 移动端 App 如何灰度? → 应用内配置中心 + 分批推送
第10讲:纵向扩展(上):常见的低性能代码逻辑有哪些?
🎯 核心问题
代码层面的性能瓶颈,往往是系统最大的短板。
📐 五大高频反模式
反模式 1:同步阻塞 I/O
java
// ❌ 反模式: 在请求线程中同步阻塞
@GetMapping("/order/{id}")
public Order getOrder(@PathVariable Long id) {
// 同步调用外部服务 (阻塞 500ms)
User user = userService.getUserById(id); // 200ms
Product product = productService.getProduct(id); // 200ms
Inventory inv = inventoryService.check(id); // 100ms
return new Order(user, product, inv);
// 总耗时 = 500ms (串行)
}
// ✅ 优化: 并行异步调用
@GetMapping("/order/{id}")
public CompletableFuture<Order> getOrder(@PathVariable Long id) {
CompletableFuture<User> userFuture =
CompletableFuture.supplyAsync(() -> userService.getUserById(id));
CompletableFuture<Product> productFuture =
CompletableFuture.supplyAsync(() -> productService.getProduct(id));
CompletableFuture<Inventory> invFuture =
CompletableFuture.supplyAsync(() -> inventoryService.check(id));
return CompletableFuture.allOf(userFuture, productFuture, invFuture)
.thenApply(v -> new Order(userFuture.join(), productFuture.join(), invFuture.join()));
// 总耗时 = max(200, 200, 100) = 200ms (并行)
}
反模式 2:大对象序列化
python
# ❌ 反模式: 全量序列化
def get_user_profile(user_id):
user = User.objects.select_related('profile', 'settings',
'orders', 'addresses').get(id=user_id)
return json.dumps(user.to_dict()) # 序列化所有字段,可能 50KB+
# ✅ 优化: 按需序列化 + 字段裁剪
def get_user_profile(user_id, fields=None):
user = User.objects.only(*fields).get(id=user_id) if fields else User.objects.get(id=user_id)
return json.dumps({
'id': user.id,
'name': user.name,
'avatar': user.avatar_url,
# 仅返回必要字段
})
反模式 3:锁竞争
python
# ❌ 反模式: 全局锁
import threading
counter = 0
lock = threading.Lock()
def increment():
global counter
with lock:
counter += 1 # 所有线程串行化!
# ✅ 优化: 无锁数据结构 / 分段锁
import threading
class AtomicCounter:
def __init__(self):
self._value = 0
self._lock = threading.Lock()
def increment(self):
# 仅临界区加锁
with self._lock:
self._value += 1
反模式 4:数据库 N+1 查询
python
# ❌ 反模式: N+1 查询
def get_orders_with_users():
orders = Order.objects.all() # 1 次查询
result = []
for order in orders: # N 次查询!
user = User.objects.get(id=order.user_id)
result.append({'order': order, 'user': user})
return result
# 100 个订单 = 101 次查询
# ✅ 优化: JOIN / select_related / prefetch_related
def get_orders_with_users():
orders = Order.objects.select_related('user').all() # 1 次查询!
return [{'order': o, 'user': o.user} for o in orders]
反模式 5:正则表达式滥用
python
# ❌ 反模式: 在循环中编译正则
def validate_emails(emails):
results = []
for email in emails:
if re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email):
results.append(email)
return results
# 每次循环都编译正则!
# ✅ 优化: 预编译正则
EMAIL_RE = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
def validate_emails(emails):
return [e for e in emails if EMAIL_RE.match(e)]
🔬 实战实验:async-profiler 火焰图诊断
bash
# 1. 安装 async-profiler
wget https://github.com/async-profiler/async-profiler/releases/download/v3.0/async-profiler-3.0-linux-x64.tar.gz
tar xzf async-profiler-3.0-linux-x64.tar.gz
# 2. 对 Java 进程采样 30 秒
./profiler.sh -d 30 -f /tmp/flamegraph.html <PID>
# 3. 分析火焰图
# 宽度 = CPU 占比
# 高度 = 调用栈深度
# 找到最宽的"平顶山" → 性能热点
📊 案例:接口优化全过程
优化前: GET /api/order/detail?id=12345
├── DB 查询: 200ms (缺少索引)
├── JSON 序列化: 150ms (全量序列化 80KB)
├── 外部 API 调用: 300ms (同步串行)
├── 日志写入: 50ms (同步写磁盘)
└── 总耗时: ~700ms
优化步骤:
Step 1: 添加 DB 索引 200ms → 5ms (-195ms)
Step 2: 字段裁剪序列化 150ms → 20ms (-130ms)
Step 3: 异步并行调用 300ms → 100ms (-200ms)
Step 4: 异步日志 50ms → 1ms (-49ms)
─────────
优化后总耗时: 700ms → 126ms (-82%)
⚠️ 避坑清单
| 序号 | 坑 | 描述 |
|---|---|---|
| 1 | 过早优化 | 先 profiling 找到真正的热点,不要凭直觉 |
| 2 | 只看 CPU | 也要关注内存分配/GC/锁等待 |
| 3 | 优化后不验证 | 每次优化后用同样的压测条件对比 |
| 4 | 忽略尾延迟 | P99 比平均值更重要 |
第11讲:纵向扩展(中):网络模型和协议调优
🎯 核心问题
操作系统和协议栈层面有哪些"免费"的性能提升?
📐 TCP 核心参数调优
bash
# ===== 缓冲区调优 =====
# 接收缓冲区最大值 (影响吞吐)
sysctl -w net.core.rmem_max=16777216 # 16MB (默认 212992)
sysctl -w net.core.wmem_max=16777216 # 16MB
# TCP 自动调优缓冲区
sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216" # min/default/max
sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"
# ===== 连接优化 =====
# 允许复用 TIME_WAIT 连接 (客户端)
sysctl -w net.ipv4.tcp_tw_reuse=1
# 快速回收 TIME_WAIT (服务端慎用)
# sysctl -w net.ipv4.tcp_tw_recycle=1 # Linux 4.12+ 已移除!
# 缩短 FIN_WAIT 超时
sysctl -w net.ipv4.tcp_fin_timeout=15 # 默认 60s
# ===== 连接队列 =====
# SYN 队列大小
sysctl -w net.ipv4.tcp_max_syn_backlog=8192
# Accept 队列大小
sysctl -w net.core.somaxconn=4096
# ===== KeepAlive =====
sysctl -w net.ipv4.tcp_keepalive_time=600 # 10分钟开始探测
sysctl -w net.ipv4.tcp_keepalive_intvl=30 # 探测间隔
sysctl -w net.ipv4.tcp_keepalive_probes=3 # 探测次数
📐 I/O 多路复用对比
| 模型 | API | 最大连接数 | 复杂度 | 现状 |
|---|---|---|---|---|
| select | select() |
1024 (FD_SETSIZE) | 低 | 已淘汰 |
| poll | poll() |
无上限 (链表) | 低 | 已淘汰 |
| epoll | epoll_create/wait |
无上限 (红黑树) | 中 | Linux 标准 |
| kqueue | kqueue/kevent |
无上限 | 中 | BSD/macOS |
| io_uring | io_uring_setup |
无上限 | 高 | Linux 5.1+ 未来 |
epoll 工作原理:
┌─────────────┐
│ Application │
└──────┬──────┘
│ epoll_wait() (只返回就绪 fd)
▼
┌─────────────┐
│ epoll │
│ (红黑树+链表)│
└──────┬──────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│ fd: 5 │ │ fd: 8 │ │ fd:12 │
│ ready! │ │ ready! │ │ waiting│
└────────┘ └────────┘ └────────┘
🔬 实战实验:BBR vs CUBIC 拥塞控制对比
BBR (Bottleneck Bandwidth and RTT):Google 开发的拥塞控制算法,基于带宽和延迟测量而非丢包。
bash
# 1. 查看当前拥塞控制算法
sysctl net.ipv4.tcp_congestion_control
# 默认: net.ipv4.tcp_congestion_control = cubic
# 2. 启用 BBR
modprobe tcp_bbr
sysctl -w net.ipv4.tcp_congestion_control=bbr
# 3. 验证
sysctl net.ipv4.tcp_congestion_control
# net.ipv4.tcp_congestion_control = bbr
iperf3 对比测试:
bash
# Server B 启动 iperf3 服务端
iperf3 -s
# Server A 测试 CUBIC
sysctl -w net.ipv4.tcp_congestion_control=cubic
iperf3 -c 192.168.0.207 -t 30 -P 4
# Server A 测试 BBR
sysctl -w net.ipv4.tcp_congestion_control=bbr
iperf3 -c 192.168.0.207 -t 30 -P 4
预期结果对比:
条件: 内网 0.1% 丢包, 4 并发流
算法 吞吐量 重传次数 备注
─────────────────────────────────────────
CUBIC 2.34 Gbps 1,245 丢包即降速
BBR 3.89 Gbps 87 不依赖丢包信号
提升 +66% -93%
📊 ss 命令高级用法
bash
# 查看所有 TCP 连接详细信息
ss -t -i
# 输出解读:
# cubic wscale:7,7 rto:204 rtt:0.333/0.167 mss:1448
# │ │ │ │ │ │
# │ │ │ │ │ └─ MSS (最大分段大小)
# │ │ │ │ └─ RTT (往返时间)
# │ │ │ └─ 重传超时
# │ │ └─ 窗口缩放因子
# │ └─ 拥塞控制算法
# └─ ...
# 统计各状态连接数
ss -s
# 查看进程使用的 socket
ss -tlnp
# 查看内存使用
ss -t -m
⚠️ Nagle 算法与 Delayed ACK 的博弈
Nagle 算法: 小包要等待 ACK 或积累到 MSS 才发送
Delayed ACK: 收到数据后等 40ms 再回复 ACK (期望捎带数据)
两者叠加 → 最多 40ms 延迟!
解决方案:
# 禁用 Nagle (设置 TCP_NODELAY)
# Nginx: tcp_nodelay on;
# Java: socket.setTcpNoDelay(true);
🔭 扩展思考
- BBR 有什么副作用? → 可能抢占 CUBIC 流的带宽(不公平性)
- io_uring 什么时候替代 epoll? → 已在数据库(ScyllaDB)等场景大规模使用
- QUIC 在传输层做了什么优化? → 0-RTT, 多路复用无队头阻塞
🔹 实战篇(中)
第12讲:纵向扩展(下):怎样通过架构优化提高单机性能?
🎯 核心问题
单机性能的最后一公里 ------ 架构级优化而非代码级修补。
📐 三大架构优化策略
策略 1:读写分离
┌──────────┐
│ App │
└────┬─────┘
│
┌──────────┴──────────┐
│ │
写请求 读请求
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ MySQL │ 同步 │ MySQL │
│ Master │─────────→│ Slave │
│ (写) │ binlog │ (读) │
└──────────┘ └──────────┘
python
# Python Django 读写分离配置
DATABASES = {
'default': { # 写库
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mydb',
'HOST': '192.168.0.5',
'USER': 'writer',
},
'readonly': { # 读库
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mydb',
'HOST': '192.168.0.207',
'USER': 'reader',
}
}
# 数据库路由
class ReadWriteRouter:
def db_for_read(self, model, **hints):
return 'readonly' # 所有读走从库
def db_for_write(self, model, **hints):
return 'default' # 所有写走主库
策略 2:缓存分层
请求流程:
┌─────────────┐
│ Request │
└──────┬──────┘
│
┌──────────▼──────────┐
│ L1: 本地缓存 (Caffeine)│ ← 命中率 80%, < 1ms
│ 未命中 │
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ L2: Redis Cluster │ ← 命中率 95%, < 5ms
│ 未命中 │
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ L3: MySQL │ ← 最终数据源, < 50ms
└─────────────────────┘
总命中率: L1(80%) + L2(15%) = 95%, 仅 5% 请求到达 DB
平均延迟: 0.8×1ms + 0.15×5ms + 0.05×50ms = 4.05ms
策略 3:异步化
python
# ❌ 同步下单
def place_order_sync(user_id, items):
order = create_order(user_id, items) # 100ms
deduct_inventory(items) # 50ms
process_payment(user_id, order.total) # 500ms
send_email(user_id, order) # 200ms
send_sms(user_id, "下单成功") # 300ms
return order
# 总耗时: 1150ms
# ✅ 异步下单
def place_order_async(user_id, items):
# 预占库存 (快速)
order = create_order(user_id, items) # 100ms
inventory.pre_deduct(items) # 50ms
# 异步处理 (消息队列)
mq.send('order.created', {
'order_id': order.id,
'user_id': user_id,
'items': items,
'total': order.total
})
return order
# 用户感知延迟: 150ms (-87%)
# 后台消费者
@mq.subscribe('order.created')
def handle_order_created(msg):
process_payment(msg.user_id, msg.total) # 500ms
send_email(msg.user_id, msg.order_id) # 200ms
send_sms(msg.user_id, "下单成功") # 300ms
📊 优化效果对比
| 优化策略 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 读写分离 | DB 500 QPS | DB 500w + 1500r | +200% 读 |
| 缓存分层 | 每次 50ms | 95% < 5ms | -90% 延迟 |
| 异步化 | 下单 1150ms | 下单 150ms | -87% 延迟 |
⚠️ 避坑清单
| 序号 | 坑 | 描述 | 解决 |
|---|---|---|---|
| 1 | 主从延迟 | 写入后立即读取,从库未同步 | 关键读走主库 或 等待 GTID |
| 2 | 缓存雪崩 | 大量缓存同时过期 | 过期时间加随机值 (TTL ± 20%) |
| 3 | 消息丢失 | 异步任务失败无补偿 | 死信队列 + 定时补偿任务 |
| 4 | 最终一致性 | 异步后用户看到旧状态 | 前端乐观更新 + 后台校验 |
🔭 扩展思考
- 缓存和数据库的一致性问题怎么解决? → Cache Aside + 延迟双删
- 异步化之后如何保证幂等? → 唯一请求 ID + 状态机
- 什么时候应该做读写分离? → 读:写 > 5:1 时收益最大
第13讲:减法与重试:怎样优化弱网?
🎯 核心问题
在延迟高、丢包多的弱网环境下,如何保证服务可用?
🔬 实验:tc netem 模拟弱网
bash
# ===== 在 Server A (192.168.0.5) 上模拟弱网 =====
# 1. 模拟延迟
tc qdisc add dev eth0 root netem delay 200ms 50ms
# 延迟 200ms ± 50ms (150-250ms)
# 2. 模拟丢包
tc qdisc add dev eth0 root netem loss 5% 2%
# 5% 丢包率 ± 2%
# 3. 模拟乱序
tc qdisc add dev eth0 root netem delay 50ms reorder 25% 50%
# 4. 组合模拟 (200ms延迟 + 5%丢包)
tc qdisc add dev eth0 root netem delay 200ms loss 5%
# ===== 验证弱网效果 =====
# Server A → Server B
ping 192.168.0.207
# 预期: time=200+ms, 偶有丢包
# ===== 清理弱网模拟 =====
tc qdisc del dev eth0 root
📐 重试策略设计
┌─────────────────────────────────────────────────────────────┐
│ 指数退避 (Exponential Backoff) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 尝试次数 退避时间 累计等待 │
│ ───────────────────────────────────── │
│ 第1次 立即 0s │
│ 第2次 1s 后 1s │
│ 第3次 2s 后 3s │
│ 第4次 4s 后 7s │
│ 第5次 8s 后 15s │
│ │
│ base=1s, multiplier=2, max_backoff=30s, max_retries=5 │
└─────────────────────────────────────────────────────────────┘
python
import time
import random
def retry_with_backoff(max_retries=5, base=1, max_backoff=30):
"""指数退避重试装饰器"""
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(max_retries + 1):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_retries:
raise # 重试耗尽,向上抛异常
# 指数退避 + 随机抖动
backoff = min(base * (2 ** attempt), max_backoff)
jitter = random.uniform(0, backoff * 0.3)
sleep_time = backoff + jitter
print(f"[重试 {attempt+1}/{max_retries}] "
f"等待 {sleep_time:.1f}s, 原因: {e}")
time.sleep(sleep_time)
return wrapper
return decorator
@retry_with_backoff(max_retries=3, base=1)
def call_external_api(url):
response = requests.get(url, timeout=5)
response.raise_for_status()
return response.json()
📐 幂等性保障
python
import uuid
import hashlib
class IdempotentClient:
"""幂等 HTTP 客户端"""
def __init__(self):
self.processed = {} # 生产环境用 Redis
def post(self, url, data):
# 生成唯一请求 ID
request_id = str(uuid.uuid4())
# 检查是否已处理
if request_id in self.processed:
return self.processed[request_id]
# 发送请求 (带唯一 ID)
headers = {'X-Request-Id': request_id}
for attempt in range(3):
try:
resp = requests.post(url, json=data,
headers=headers, timeout=10)
self.processed[request_id] = resp.json()
return self.processed[request_id]
except requests.RequestException:
time.sleep(2 ** attempt)
raise Exception("Max retries exceeded")
⚠️ 避坑清单
| 序号 | 坑 | 描述 | 正确做法 |
|---|---|---|---|
| 1 | 🔴 支付重复提交 | 重试导致扣款两次 | 幂等 key + 数据库唯一约束 |
| 2 | 🟡 退避无上限 | 一直重试导致雪崩 | max_backoff + max_retries |
| 3 | 🟡 无抖动 (Jitter) | 所有客户端同时重试 | 随机抖动避免惊群 |
| 4 | 🟢 忽略幂等 | 所有请求都重试 | 只重试 GET/PUT (幂等方法) |
🔭 扩展思考
- gRPC 内置了哪些重试机制? → RetryPolicy + HedgingPolicy
- 弱网下如何做服务降级? → 返回缓存数据 / 静态兜底页
- 移动端弱网有什么特殊处理? → 离线队列 + 网络恢复后批量同步
第14讲:分片、并发与续传:怎样高效上传资源?
🎯 核心问题
GB 级大文件上传如何做到不中断、可恢复、高速率?
📐 大文件上传三阶段
┌─────────────────────────────────────────────────────────────┐
│ 大文件上传流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Stage 1: 分片 (Chunking) │
│ ┌──────────────────────────────────────┐ │
│ │ 1GB 文件 → 100 × 10MB Chunk │ │
│ │ Chunk 0 | Chunk 1 | ... | Chunk 99 │ │
│ └──────────────────────────────────────┘ │
│ │ │
│ Stage 2: 并发上传 (Concurrent Upload) │
│ ┌──────────────────────────────────────┐ │
│ │ 线程池 (5 threads) │ │
│ │ Thread-1: Chunk 0, 5, 10, ... │ │
│ │ Thread-2: Chunk 1, 6, 11, ... │ │
│ │ ... │ │
│ │ 每个 Chunk: MD5 校验 + 进度上报 │ │
│ └──────────────────────────────────────┘ │
│ │ │
│ Stage 3: 合并校验 (Merge & Verify) │
│ ┌──────────────────────────────────────┐ │
│ │ 服务端合并所有 Chunk │ │
│ │ 校验整体 SHA256 │ │
│ │ 返回文件 URL │ │
│ └──────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
🔬 实战:MinIO 分片上传
bash
# Server D --- 部署 MinIO
docker run -d \
--name minio \
-p 9000:9000 \
-p 9001:9001 \
-e MINIO_ROOT_USER=admin \
-e MINIO_ROOT_PASSWORD=Admin123! \
minio/minio server /data --console-address ":9001"
Python 分片上传客户端:
python
import hashlib
import os
from concurrent.futures import ThreadPoolExecutor, as_completed
from minio import Minio
CHUNK_SIZE = 10 * 1024 * 1024 # 10MB
def upload_chunk(client, bucket, object_name, upload_id,
chunk_data, part_number):
"""上传单个分片"""
md5 = hashlib.md5(chunk_data).hexdigest()
result = client._upload_part(
bucket_name=bucket,
object_name=object_name,
part_number=part_number,
upload_id=upload_id,
data=chunk_data,
md5_base64=md5
)
return part_number, result.etag
def multipart_upload(file_path, bucket="uploads", max_workers=5):
"""并发分片上传"""
client = Minio(
"192.168.0.44:9000",
access_key="admin",
secret_key="Admin123!",
secure=False
)
# 确保 bucket 存在
if not client.bucket_exists(bucket):
client.make_bucket(bucket)
object_name = os.path.basename(file_path)
file_size = os.path.getsize(file_path)
# 创建分片上传
upload_id = client._create_multipart_upload(
bucket, object_name
)
# 计算分片数
total_parts = (file_size + CHUNK_SIZE - 1) // CHUNK_SIZE
parts = []
# 并发上传
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {}
with open(file_path, 'rb') as f:
for i in range(total_parts):
chunk_data = f.read(CHUNK_SIZE)
future = executor.submit(
upload_chunk, client, bucket, object_name,
upload_id, chunk_data, i + 1
)
futures[future] = i + 1
# 收集结果
for future in as_completed(futures):
part_num, etag = future.result()
parts.append((part_num, etag))
print(f"Chunk {part_num}/{total_parts} uploaded ✓")
# 完成上传
parts.sort() # 按分片号排序
client._complete_multipart_upload(
bucket, object_name, upload_id,
[p[1] for p in parts]
)
print(f"Upload complete: {object_name} ({file_size} bytes)")
📊 分片大小 vs 性能
| 分片大小 | 总请求数 (1GB) | 内存占用 | 适合网络 |
|---|---|---|---|
| 1MB | 1024 | 低 | 极不稳定网络 |
| 5MB | 205 | 中 | 弱网 |
| 10MB | 103 | 中 | 一般 (推荐) |
| 50MB | 21 | 高 | 内网/稳定网络 |
| 100MB | 11 | 高 | 内网高速 |
⚠️ 避坑清单
| 序号 | 坑 | 描述 | 解决 |
|---|---|---|---|
| 1 | Nginx 限制 | client_max_body_size 默认 1MB |
改为 client_max_body_size 0; (不限制) |
| 2 | 代理缓冲 | proxy_buffering 导致 OOM |
proxy_buffering off; 流式传输 |
| 3 | 超时断开 | 大文件上传中途超时 | proxy_read_timeout 300s; |
| 4 | Chunk 顺序错误 | 并发上传后合并顺序混乱 | 按 part_number 排序后再合并 |
🔭 扩展思考
- 如何实现断点续传? → 客户端记录已上传 chunk 列表,跳过已完成的分片
- 如何在前端实现分片上传? →
File.slice()+XMLHttpRequest/fetch - 如何做上传限速? → Token Bucket 控制单 chunk 上传速率
第15讲:DNS:域名解析系统是怎样工作的?
🎯 核心问题
当你在浏览器输入 URL 时,域名是如何变成 IP 地址的?
📐 DNS 查询全流程
用户输入: www.example.com
┌─────────┐ ①查询 ┌──────────────┐
│ Client │ ──────────────────→│ Local DNS │
│ │ │ (ISP/路由器) │
└─────────┘ └──────┬───────┘
│
② 递归查询到根域名服务器
│
▼
┌──────────────┐
│ Root DNS │
│ 返回 .com │
│ NS 地址 │
└──────┬───────┘
│
③ 查询 .com 顶级域
│
▼
┌──────────────┐
│ TLD DNS │
│ (.com) │
│ 返回 example│
│ .com NS │
└──────┬───────┘
│
④ 查询权威 DNS
│
▼
┌──────────────┐
│ Authoritative│
│ DNS │
│ 返回: │
│ 1.2.3.4 │
└──────────────┘
⑤ Local DNS 缓存结果 → 返回给 Client
⑥ Client 拿到 1.2.3.4 → 发起 TCP 连接
🔬 实验:dig +trace 完整追踪
bash
# 从 Server A 追踪 www.baidu.com 的解析路径
dig +trace www.baidu.com
# 输出分析:
# Step 1: 根域名服务器
# ; <<>> DiG 9.18 <<>> +trace www.baidu.com
# . 518400 IN NS a.root-servers.net.
# . 518400 IN NS b.root-servers.net.
# (13 个根域名服务器)
# Step 2: .com 顶级域
# com. 172800 IN NS a.gtld-servers.net.
# (返回 .com 的权威 NS)
# Step 3: baidu.com 权威 DNS
# baidu.com. 172800 IN NS dns.baidu.com.
# baidu.com. 172800 IN NS ns2.baidu.com.
# Step 4: 最终 A 记录
# www.baidu.com. 1200 IN CNAME www.a.shifen.com.
# www.a.shifen.com. 300 IN A 110.242.68.66
# www.a.shifen.com. 300 IN A 110.242.68.67
🔬 实验:dnsmasq 本地 DNS 劫持测试
bash
# Server A --- 安装配置 dnsmasq
apt install -y dnsmasq
# 配置本地域名劫持
cat >> /etc/dnsmasq.conf << 'EOF'
# 劫持 example.com 到本地
address=/example.com/192.168.0.5
# 劫持所有 .test 域名
address=/test/192.168.0.207
EOF
systemctl restart dnsmasq
# 测试劫持效果
dig @127.0.0.1 example.com
# 输出: example.com. 0 IN A 192.168.0.5 ← 劫持成功!
dig @127.0.0.1 anything.test
# 输出: anything.test. 0 IN A 192.168.0.207 ← 劫持成功!
📊 DNS 记录类型速查
| 类型 | 含义 | 示例 |
|---|---|---|
| A | IPv4 地址 | example.com. 300 IN A 1.2.3.4 |
| AAAA | IPv6 地址 | example.com. 300 IN AAAA ::1 |
| CNAME | 别名 | www.example.com. CNAME example.com. |
| MX | 邮件服务器 | example.com. MX 10 mail.example.com. |
| NS | 权威 DNS | example.com. NS ns1.example.com. |
| TXT | 文本记录 | 用于 SPF/DKIM 验证 |
| SRV | 服务定位 | _http._tcp.example.com. SRV ... |
| PTR | 反向解析 | 4.3.2.1.in-addr.arpa. PTR example.com. |
📊 DNS 安全方案对比
| 方案 | 保护目标 | 原理 | 普及度 |
|---|---|---|---|
| DNSSEC | 防篡改 | 数字签名验证 | 30% |
| DoH (DNS over HTTPS) | 防窃听 | DNS 查询走 HTTPS | 90% (浏览器支持) |
| DoT (DNS over TLS) | 防窃听 | DNS 查询走 TLS | 50% (系统级) |
| DNSCrypt | 防窃听+防篡改 | 加密 + 签名 | 10% |
⚠️ 避坑清单
| 序号 | 坑 | 描述 | 解决 |
|---|---|---|---|
| 1 | DNS 缓存 TTL | 切换 IP 后旧缓存未过期 | 提前降低 TTL (如 60s) |
| 2 | CNAME 平铺 | 多级 CNAME 增加延迟 | 最多 1-2 级 CNAME |
| 3 | DNS 污染 | 运营商劫持返回错误 IP | 使用 DoH/DoT |
| 4 | TTL 最小值 | TTL=0 导致每次查询 | 合理 TTL: 60-300s (动态) / 3600s+ (静态) |
🔭 扩展思考
- 如何实现 GSLB (全局负载均衡)? → 根据用户 IP 返回最近机房 IP (GeoDNS)
- HTTPDNS 是什么? → App 绕过运营商 DNS,直接请求 HTTP DNS 服务
- DNS 故障的降级方案? → 客户端 IP 列表兜底 + 本地 hosts
第16讲:CDN 架构(上):怎样加速静态资源下载?
🎯 核心问题
用户遍布全球,如何让每个人都能快速加载静态资源?
📐 CDN 工作原理
┌──────────────────────────────┐
│ 源站 (Origin) │
│ origin.example.com │
│ 1.2.3.4 │
└──────────────┬───────────────┘
│ 回源 (仅首次/过期)
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐┌─────────────────┐┌─────────────────┐
│ 边缘节点 北京 ││ 边缘节点 上海 ││ 边缘节点 深圳 │
│ Cache: 95% ││ Cache: 93% ││ Cache: 94% │
└────────┬────────┘└────────┬────────┘└────────┬────────┘
│ │ │
▼ ▼ ▼
北京用户 上海用户 深圳用户
(5ms 延迟) (3ms 延迟) (2ms 延迟)
📐 CDN 缓存策略
nginx
# Nginx 作为 CDN 边缘节点的缓存配置
proxy_cache_path /var/cache/nginx levels=1:2
keys_zone=STATIC:100m max_size=10g inactive=7d;
server {
listen 80;
server_name cdn.example.com;
# 静态资源缓存
location /static/ {
proxy_pass http://origin.example.com;
proxy_cache STATIC;
proxy_cache_key "$uri$is_args$args";
proxy_cache_valid 200 7d; # 200 响应缓存 7 天
proxy_cache_valid 404 1m; # 404 缓存 1 分钟
# 缓存状态 header (调试用)
add_header X-Cache-Status $upstream_cache_status;
# 回源超时
proxy_connect_timeout 5s;
proxy_read_timeout 10s;
}
}
X-Cache-Status 含义:
| 状态 | 含义 |
|---|---|
| HIT | 缓存命中 ✓ |
| MISS | 缓存未命中,已回源 |
| EXPIRED | 缓存过期,已回源刷新 |
| BYPASS | 跳过缓存 (如 Cookie 导致) |
| UPDATING | 正在更新缓存 (stale-while-revalidate) |
📊 缓存命中率诊断
bash
# 分析 Nginx 访问日志中的缓存命中率
awk '{print $NF}' /var/log/nginx/access.log | sort | uniq -c
# 预期输出:
# 89450 HIT
# 5000 MISS
# 450 EXPIRED
# 100 BYPASS
# 命中率 = 89450 / (89450+5000+450+100) = 94.1%
⚠️ 缓存命中率低的常见原因
| 原因 | 现象 | 解决 |
|---|---|---|
| 未设 Cache-Control | 源站不返回缓存头 | 添加 Cache-Control: public, max-age=86400 |
| 动态参数 | ?t=1234567890 导致 key 不同 |
忽略无意义参数 proxy_cache_key "$uri" |
| Cookie 穿透 | 有 Cookie 的请求绕过 CDN | proxy_ignore_headers Set-Cookie |
| Vary 头不当 | Vary: User-Agent 导致缓存碎片化 |
标准化 User-Agent 或移除 |
🔭 扩展思考
- 预热 vs 刷新有什么区别? → 预热=提前拉取;刷新=清除缓存强制回源
- CDN 如何做图片处理? → 边缘计算 (如 Cloudflare Workers) 实时裁剪/压缩/转格式
- HTTP/3 对 CDN 有什么影响? → QUIC 0-RTT + 连接迁移,移动端体验提升显著
第17讲:CDN 架构(下):怎样加速动态内容?
🎯 核心问题
静态资源可以用 CDN 缓存,但动态 API 请求如何加速?
📐 动态内容加速 (DCDN) 技术栈
┌──────────────────────────────────────────────────────────────┐
│ DCDN 加速层 │
├──────────────────────────────────────────────────────────────┤
│ │
│ Layer 1: 智能选路 │
│ ┌────────────────────────────────────────────────┐ │
│ │ 边缘节点 → 最优路径 → 源站 │ │
│ │ 基于实时延迟/丢包率的动态路由 │ │
│ └────────────────────────────────────────────────┘ │
│ │
│ Layer 2: 协议优化 │
│ ┌────────────────────────────────────────────────┐ │
│ │ TCP: BBR + TCP Fast Open │ │
│ │ TLS: 1.3 + 0-RTT + Session Resumption │ │
│ │ HTTP: HTTP/2 多路复用 + Server Push │ │
│ │ QUIC: 0-RTT + 连接迁移 + 无队头阻塞 │ │
│ └────────────────────────────────────────────────┘ │
│ │
│ Layer 3: 链路优化 │
│ ┌────────────────────────────────────────────────┐ │
│ │ 私有传输协议 (如 Cloudflare Argo) │ │
│ │ 链路冗余 + 故障秒级切换 │ │
│ │ 数据压缩 (Brotli) │ │
│ └────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
📊 HTTP 版本性能对比
测试条件: 100 个小文件 (每个 20KB), 50ms RTT
协议 连接数 TTFB (ms) 总加载时间 (ms) 备注
─────────────────────────────────────────────────────────
HTTP/1.1 6 50 850 队头阻塞
HTTP/2 1 45 320 多路复用
HTTP/3 (QUIC) 1 15 250 0-RTT + 无队头阻塞
提升 vs 1.1 -83% -70% -71%
📐 HTTP/2 Server Push 示例
nginx
# Nginx HTTP/2 Server Push 配置
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
location / {
root /var/www/html;
http2_push /css/style.css;
http2_push /js/app.js;
http2_push /images/logo.png;
# 关键资源提前推送,无需等待 HTML 解析
}
}
📐 QUIC 协议优势
TCP + TLS 1.2:
Client → SYN
Server → SYN-ACK
Client → ACK + ClientHello
Server → ServerHello + Certificate
Client → Finished
握手: 2 RTT
TCP + TLS 1.3:
Client → SYN + ClientHello
Server → SYN-ACK + ServerHello + Certificate + Finished
Client → Finished
握手: 1 RTT
QUIC (首次):
Client → ClientHello (in QUIC)
Server → ServerHello + Finished (in QUIC)
握手: 1 RTT
QUIC (0-RTT 重连):
Client → ClientHello + HTTP Request (同时发送!)
Server → Response
握手: 0 RTT! (首次后重连)
⚠️ 避坑清单
| 序号 | 坑 | 描述 |
|---|---|---|
| 1 | HTTP/2 在弱网不如 HTTP/1.1 | 高丢包下多路复用全部阻塞,反而更慢 |
| 2 | Server Push 滥用 | 推送浏览器已缓存的资源,浪费带宽 |
| 3 | QUIC 被防火墙拦截 | 部分企业防火墙封 UDP 443,需要 fallback TCP |
| 4 | 0-RTT 不安全 | 重放攻击风险,只用于幂等 GET 请求 |
🔭 扩展思考
- HTTP/3 普及到什么程度了? → 2025年约 35% 网站支持,Google/Cloudflare/CDN 全面支持
- 私有传输协议比 QUIC 更好吗? → 理论上可以更优(定制化),但生态差
- 边缘函数如何加速动态请求? → 在边缘节点直接处理请求(如认证、A/B测试),减少回源
🔹 实战篇(下)
第18讲:全球网络加速架构:怎样加速动态请求?
🎯 核心问题
跨国业务中,如何将海外用户的延迟从 300ms 降低到 80ms?
📐 全球加速核心方案
┌──────────────────┐
│ 用户 (东南亚) │
│ 延迟: 300ms → │
└────────┬─────────┘
│
┌────────▼─────────┐
│ 边缘节点 (新加坡) │
│ - 边缘函数 │
│ - 智能缓存 │
└────────┬─────────┘
│ 私有加速网络
│ (延迟 < 5ms)
┌────────▼─────────┐
│ 源站 (中国香港) │
│ 延迟: 300ms → │
│ 80ms (-73%) │
└──────────────────┘
加速方案对比:
| 方案 | 原理 | 延迟降低 | 成本 | 复杂度 |
|---|---|---|---|---|
| Anycast | 同一个 IP 广播到多个地理位置 | 30-50% | 中 | 中 |
| GSLB (GeoDNS) | 根据用户 IP 返回最近机房 | 40-60% | 低 | 低 |
| 边缘函数 | 在边缘节点处理请求 | 50-80% | 中 | 中 |
| 私有加速网络 | 专用骨干网传输 | 60-80% | 高 | 高 |
📊 某出海 App 优化案例
优化前:
新加坡用户 → 直接请求中国香港源站
延迟: 280ms (公网路由绕路)
TTFB: 320ms
优化后:
新加坡用户 → 新加坡边缘节点 (5ms)
新加坡边缘 → 香港源站 (专用线路, 40ms)
延迟: 45ms (-84%)
TTFB: 55ms (-83%)
🔭 扩展思考
- AWS Global Accelerator 的原理? → Anycast + AWS 骨干网
- Cloudflare Argo 为什么快? → 实时拥塞感知的智能路由
- 自建全球加速要多少钱? → 至少 ¥50000/月 (多地域 ECS + 带宽)
第19讲:SSL/TLS:怎样在公网安全传输数据?
🎯 核心问题
公网数据传输如何做到不被窃听、不被篡改?
📐 TLS 1.3 握手流程(精简版)
Client Server
│ │
│── ClientHello ───────────────→│
│ (支持的密码套件 + 密钥共享) │
│ │
│←─ ServerHello ────────────────│
│ (选定的密码套件 + 密钥共享) │
│ EncryptedExtensions │
│ Certificate (证书) │
│ CertificateVerify (签名) │
│ Finished │
│ │
│── Finished ──────────────────→│
│ │
│◄══════ 加密通信开始 ══════════►│
总耗时: 1 RTT (首次) / 0 RTT (PSK 重连)
🔬 实战:Nginx TLS 1.3 完整配置
nginx
server {
listen 443 ssl http2;
server_name example.com;
# === 证书配置 ===
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
# === TLS 1.3 专用配置 ===
ssl_protocols TLSv1.2 TLSv1.3; # 仅允许 TLS 1.2+
# TLS 1.3 密码套件
ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256;
# === 安全加固 ===
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off; # 禁用 Session Ticket (安全考虑)
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ssl/chain.pem;
# HSTS (强制 HTTPS, 包含子域名)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# 其他安全头
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
}
📊 TLS 版本性能对比
| TLS 版本 | 握手 RTT | 密码套件 | 安全性 |
|---|---|---|---|
| TLS 1.0 | 2 | 弱 (RC4/MD5) | 不安全 ❌ |
| TLS 1.1 | 2 | 弱 | 不安全 ❌ |
| TLS 1.2 | 2 | 较强 (AES-GCM) | 安全 ✅ |
| TLS 1.3 | 1 | 强 (仅 AEAD) | 最安全 ✅✅ |
⚠️ 避坑清单
| 序号 | 坑 | 描述 | 解决 |
|---|---|---|---|
| 1 | 证书链不完整 | 浏览器提示不安全 | 使用 fullchain.pem (含中间证书) |
| 2 | 混合内容 | HTTPS 页面引用 HTTP 资源 | 全站 HTTPS + CSP 策略 |
| 3 | 证书过期 | 服务中断 | Let's Encrypt 自动续期 (certbot) |
| 4 | 弱密码套件 | PCI DSS 合规失败 | 仅启用 TLS 1.3 + AEAD 套件 |
🔭 扩展思考
- mTLS 是什么?什么时候需要? → 双向 TLS 认证,微服务间通信
- Certificate Transparency 解决什么问题? → 防止 CA 错误签发证书
- 后量子密码学对 TLS 有什么影响? → TLS 1.3 已支持 PQC 混合密钥交换
第20讲:VPN:怎样构建安全的企业网络?
🎯 核心问题
远程办公/多机房互联时,如何建立安全加密隧道?
📐 三种主流方案对比
| 方案 | 协议 | 延迟 | 吞吐 | 复杂度 | 适用 |
|---|---|---|---|---|---|
| IPSec VPN | ESP/AH | 低 | 高 | 高 | 站点到站点 |
| WireGuard | UDP | 极低 | 极高 | 极低 | 远程办公/站点互联 |
| OpenVPN | TCP/UDP | 中 | 中 | 中 | 远程办公 |
| Zero Trust | HTTPS | 低 | 中 | 高 | 现代化替代 |
🔬 实战:WireGuard 点对点隧道
实验拓扑:
┌──────────────┐ ┌──────────────┐
│ Server A │ WireGuard │ Server B │
│ 192.168.0.5 │══════════════│ 192.168.0.207│
│ WG: 10.0.0.1│ 加密隧道 │ WG: 10.0.0.2│
└──────────────┘ └──────────────┘
bash
# ===== Server A 配置 =====
apt install -y wireguard
# 生成密钥对
wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey
cat > /etc/wireguard/wg0.conf << 'EOF'
[Interface]
PrivateKey = <ServerA-私钥>
Address = 10.0.0.1/24
ListenPort = 51820
[Peer]
PublicKey = <ServerB-公钥>
Endpoint = 192.168.0.207:51820
AllowedIPs = 10.0.0.0/24
PersistentKeepalive = 25
EOF
systemctl enable --now wg-quick@wg0
bash
# ===== Server B 配置 =====
apt install -y wireguard
wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey
cat > /etc/wireguard/wg0.conf << 'EOF'
[Interface]
PrivateKey = <ServerB-私钥>
Address = 10.0.0.2/24
ListenPort = 51820
[Peer]
PublicKey = <ServerA-公钥>
Endpoint = 192.168.0.5:51820
AllowedIPs = 10.0.0.0/24
PersistentKeepalive = 25
EOF
systemctl enable --now wg-quick@wg0
bash
# 验证隧道
wg show
# 测试连通
ping 10.0.0.2 # Server A → Server B 隧道 IP
⚠️ WireGuard 优势
| 特性 | WireGuard | OpenVPN | IPSec |
|---|---|---|---|
| 代码行数 | ~4000 | ~70000 | ~400000 |
| 内核集成 | ✅ Linux 5.6+ | ❌ | ✅ |
| 漫游支持 | ✅ 天然支持 | ❌ | ❌ |
| 配置复杂度 | 极低 | 中 | 极高 |
| 加密算法 | 现代 (ChaCha20) | 可选 | 可选 |
🔭 扩展思考
- WireGuard 适合大规模部署吗? → 适合,配合 wg-dynamic 或 Netmaker 管理
- Zero Trust 为什么比 VPN 更好? → 不信任网络位置,每个请求都验证
- SD-WAN 和传统 VPN 有什么区别? → SD-WAN 智能选路 + 应用感知 + 集中管理
第21讲:多重武装:怎样建设安全的网络架构?
🎯 核心问题
单层防护永远不够,如何构建纵深防御体系?
📐 纵深防御 (Defense in Depth) 模型
┌─────────────────────────────────────────────────────────────┐
│ 纵深防御五层模型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Layer 1: 边界防护 │ │
│ │ 云安全组 + iptables + DDoS 清洗 + WAF │ │
│ └───────────────────────────────────────────────────────┘ │
│ │ 突破 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Layer 2: 网络隔离 │ │
│ │ VPC + 子网 + NACL + 微分段 │ │
│ └───────────────────────────────────────────────────────┘ │
│ │ 突破 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Layer 3: 主机加固 │ │
│ │ SELinux/AppArmor + 最小权限 + 补丁管理 │ │
│ └───────────────────────────────────────────────────────┘ │
│ │ 突破 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Layer 4: 应用防护 │ │
│ │ 输入验证 + SQL注入防护 + XSS防护 + CSRF Token │ │
│ └───────────────────────────────────────────────────────┘ │
│ │ 突破 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Layer 5: 数据加密 │ │
│ │ TLS 传输加密 + 磁盘加密 + 数据库加密 + 密钥管理 │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
🔬 实战:iptables 安全规则
bash
# ===== 默认策略 =====
iptables -P INPUT DROP # 默认拒绝入站
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# ===== 允许必要的入站 =====
# 允许已建立的连接
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# 允许本地回环
iptables -A INPUT -i lo -j ACCEPT
# 允许 SSH (限制来源 IP)
iptables -A INPUT -p tcp --dport 22 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j DROP
# 允许 HTTP/HTTPS
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# ===== 防 DDoS =====
# 限制 SSH 连接速率
iptables -A INPUT -p tcp --dport 22 -m recent --set --name ssh
iptables -A INPUT -p tcp --dport 22 -m recent --update \
--seconds 60 --hitcount 4 --name ssh -j DROP
# 限制 SYN 洪水
iptables -A INPUT -p tcp --syn -m limit --limit 100/s --limit-burst 100 -j ACCEPT
iptables -A INPUT -p tcp --syn -j DROP
# 丢弃无效包
iptables -A INPUT -m state --state INVALID -j DROP
📊 安全工具选型
| 层次 | 工具 | 开源 | 适用 |
|---|---|---|---|
| WAF | ModSecurity + OWASP CRS | ✅ | 自建 |
| WAF | Cloudflare WAF | ❌ (免费层可用) | SaaS |
| IDS/IPS | Snort / Suricata | ✅ | 自建 |
| 漏洞扫描 | OpenVAS / Trivy | ✅ | 自建 |
| SIEM | Wazuh / ELK | ✅ | 自建 |
| 密钥管理 | HashiCorp Vault | ✅ | 自建 |
⚠️ 避坑清单
| 序号 | 坑 | 描述 | 正确做法 |
|---|---|---|---|
| 1 | 🔴 安全组 0.0.0.0/0 | 对所有 IP 开放 SSH/数据库端口 | 限制来源 IP 范围 |
| 2 | 🔴 默认密码 | 数据库/中间件使用默认密码 | 强密码 + 定期轮换 |
| 3 | 🟡 iptables 规则丢失 | 重启后规则消失 | iptables-save + systemd 持久化 |
| 4 | 🟡 WAF 规则过严 | 正常请求被拦截 | 先观察模式 → 再拦截模式 |
🔭 扩展思考
- 零信任架构 (Zero Trust) 的核心原则? → 永不信任,始终验证 (Never Trust, Always Verify)
- 如何做红蓝对抗演练? → 红队模拟攻击,蓝队检测防御,紫队协调复盘
- eBPF 在安全领域有什么应用? → 运行时安全监控 (Falco/Cilium)
第22讲:兼容:网络协议怎样在存量中迭代?
🎯 核心问题
如何在不中断现有服务的情况下,让新旧协议共存并平滑演进?
📐 协议演进策略
策略 1:优雅降级(客户端能力探测)
Client → Server: Accept: application/json, application/x-protobuf
Server 检查 Accept header:
如果支持 protobuf → 返回 protobuf (高性能)
否则 → 返回 JSON (兼容)
python
# Python Flask 示例
from flask import Flask, request, jsonify
import json
app = Flask(__name__)
@app.route('/api/data')
def get_data():
data = {"id": 1, "name": "test", "value": 100}
# 检查客户端 Accept header
accept = request.headers.get('Accept', '')
if 'application/x-protobuf' in accept:
# 新协议: Protobuf
return protobuf_encode(data), 200, {
'Content-Type': 'application/x-protobuf'
}
else:
# 旧协议: JSON
return jsonify(data)
策略 2:双写过渡期
┌──────────┐
│ Client │
└────┬─────┘
│
┌─────────┴─────────┐
│ │
(新协议) (旧协议)
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ Protobuf│ │ JSON │
│ Service │ │ Service │
└──────────┘ └──────────┘
过渡期: 两个版本并行运行
新客户端 → Protobuf
旧客户端 → JSON
流量镜像: 新协议处理 10% → 50% → 100%
策略 3:API 版本管理
版本策略对比:
URL Path: /api/v1/users /api/v2/users
优点: 清晰直观
缺点: URL 污染
Header: GET /api/users
API-Version: 1
优点: URL 干净
缺点: 不直观
Query: /api/users?version=1
优点: 简单
缺点: 缓存 key 混乱
推荐: URL Path (对外 API) + Header (内部 API)
📊 微信支付 XML → Protobuf 迁移案例
阶段 1: 仅 XML (V2)
所有商户使用 XML 格式
阶段 2: XML + Protobuf 双写 (V3 Beta)
新商户可选 Protobuf
旧商户继续 XML
阶段 3: Protobuf 为主 (V3 GA)
默认 Protobuf, XML 标记为 deprecated
设置 XML 关闭日期
阶段 4: 仅 Protobuf
移除 XML 支持
总迁移时间: ~3年
⚠️ 避坑清单
| 序号 | 坑 | 描述 | 解决 |
|---|---|---|---|
| 1 | 过早移除旧协议 | 老客户端无法使用 | 至少保留 2 个大版本的兼容 |
| 2 | 字段语义变更 | 同名字段含义不同 | 新增字段而非修改 |
| 3 | 二进制不兼容 | Protobuf 字段编号变更 | 字段编号永久保留,只增不删 |
第23讲:VPC架构:云网络时代多租户怎样无感隔离?
🎯 核心问题
公有云上,不同租户的网络如何做到完全隔离?
📐 VPC 核心组件
┌──────────────────────────────────────────────────────────────┐
│ VPC (10.0.0.0/16) │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ Public Subnet │ │ Private Subnet │ │
│ │ 10.0.1.0/24 │ │ 10.0.2.0/24 │ │
│ │ │ │ │ │
│ │ ┌───────────────┐ │ │ ┌───────────────┐ │ │
│ │ │ Nginx (公网) │ │ │ │ MySQL (私网) │ │ │
│ │ │ 10.0.1.10 │ │ │ │ 10.0.2.10 │ │ │
│ │ └───────────────┘ │ │ └───────────────┘ │ │
│ │ │ │ │ │
│ │ 路由表: │ │ 路由表: │ │
│ │ 0.0.0.0/0 → IGW │ │ 0.0.0.0/0 → NAT │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ Internet Gateway (IGW) NAT Gateway │
└──────────────────────────────────────────────────────────────┘
📐 安全组 vs 网络 ACL
| 特性 | 安全组 (Security Group) | 网络 ACL (NACL) |
|---|---|---|
| 作用范围 | 实例级 (ENI) | 子网级 |
| 状态 | 有状态 (自动允许响应) | 无状态 (需双向规则) |
| 规则类型 | 仅允许 (Allow) | 允许 + 拒绝 (Allow/Deny) |
| 规则评估 | 所有规则聚合评估 | 按编号顺序评估 |
| 默认行为 | 拒绝所有入站 | 允许所有 |
🔬 实战:跨账号 VPC 对等连接
┌──────────────────────┐ ┌──────────────────────┐
│ 账号 A (开发) │ │ 账号 B (测试) │
│ VPC-A: 10.1.0.0/16 │◄═══════►│ VPC-B: 10.2.0.0/16 │
│ │ Peering│ │
│ EC2: 10.1.1.10 │ │ EC2: 10.2.1.10 │
└──────────────────────┘ └──────────────────────┘
bash
# AWS CLI --- 创建跨账号 VPC Peering
# 账号 A 发起请求
aws ec2 create-vpc-peering-connection \
--vpc-id vpc-aaa111 \
--peer-vpc-id vpc-bbb222 \
--peer-owner-id 123456789012 \
--region ap-east-1
# 账号 B 接受请求
aws ec2 accept-vpc-peering-connection \
--vpc-peering-connection-id pcx-xxx
# 双方添加路由
# 账号 A
aws ec2 create-route \
--route-table-id rtb-aaa \
--destination-cidr-block 10.2.0.0/16 \
--vpc-peering-connection-id pcx-xxx
# 账号 B
aws ec2 create-route \
--route-table-id rtb-bbb \
--destination-cidr-block 10.1.0.0/16 \
--vpc-peering-connection-id pcx-xxx
⚠️ 避坑清单
| 序号 | 坑 | 描述 |
|---|---|---|
| 1 | 🔴 默认安全组 0.0.0.0/0 | 新创建的安全组默认规则可能放行所有流量 |
| 2 | 🟡 Peering CIDR 重叠 | 双方 VPC 的 CIDR 不能重叠 |
| 3 | 🟡 Peering 不可传递 | A↔B + B↔C ≠ A↔C,需要 Transit Gateway |
| 4 | 🟢 NACL 优先级 | 低编号规则优先匹配,注意顺序 |
🔭 扩展思考
- Transit Gateway vs VPC Peering? → TGW 支持星型拓扑 + 传递路由 + 多账号
- PrivateLink 解决了什么问题? → 不暴露公网的服务间通信
- Kubernetes 网络模型和 VPC 的关系? → CNI 插件在 VPC 内实现 Pod 网络
第24讲:加餐|思考题答案合集
🎯 前23讲关键问题深度解答
Q1:为什么 TIME_WAIT 连接过多会导致端口耗尽?
A :Linux 默认
net.ipv4.ip_local_port_range = 32768-60999,仅约 28231 个可用端口。每个主动关闭的 TCP 连接进入 TIME_WAIT 状态持续 60 秒(2*MSL)。如果每秒新建连接超过28231/60 ≈ 470,端口就会耗尽。解决方案:启用tcp_tw_reuse、扩大端口范围、使用长连接。
Q2:CDN 缓存命中率低的常见原因?
A :三大原因------① 源站未设置
Cache-Control头,CDN 不知道缓存多久;② URL 包含动态参数(如?t=1234567890),导致每个请求缓存 key 不同;③ 请求携带 Cookie,CDN 默认跳过缓存。
Q3:TCP 为什么需要三次握手而不是两次?
A:防止历史连接初始化。如果只有两次握手,一个延迟到达的旧 SYN 包会让服务端错误地建立连接。第三次握手让客户端有机会拒绝不想要的连接。
Q4:ping 通但 curl 不通的原因?
A:ping 使用 ICMP 协议(网络层),curl 使用 TCP 协议(传输层)。可能原因:① 目标端口未监听;② 防火墙放行 ICMP 但拦截 TCP 特定端口;③ 应用层服务未启动。
Q5:为什么 HTTP/2 在弱网下反而更慢?
A:HTTP/2 在单个 TCP 连接上多路复用所有请求。如果网络丢包,TCP 的队头阻塞会导致该连接上所有请求都被阻塞。HTTP/3 (QUIC) 解决了这个问题------每个流独立,丢包只影响单个流。
Q6:WireGuard 为什么比 OpenVPN 快?
A:① 代码量少(4000 vs 70000 行),攻击面小、bug 少;② 内核态运行(Linux 5.6+),无用户态/内核态切换;③ 使用 ChaCha20-Poly1305(比 AES 更快);④ 无连接设计,天然支持漫游。
Q7:什么是脑裂(Split-Brain)?如何预防?
A:脑裂是集群中两个节点都认为自己是主节点。原因:节点间通信中断。预防:① 仲裁(Quorum)机制,必须有超过半数节点同意;② 隔离(Fencing),如 STONITH;③ ARP 检查。
Q8:令牌桶和漏桶的核心区别?
A:令牌桶允许突发(桶内可积累令牌),漏桶强制平滑输出。令牌桶适合有 burst 需求的 API 网关;漏桶适合需要稳定出队的消息队列。
Q9:为什么需要分布式限流?
A:单机限流只能限制本机。如果 3 台服务器每台限流 10/s,总流量可达 30/s。分布式限流使用 Redis 等集中式计数器,确保整个集群总共不超过 10/s。
Q10:BBR 和 CUBIC 的本质区别?
A:CUBIC 基于丢包检测(丢包 = 拥塞),会主动降速;BBR 基于带宽和 RTT 测量,不依赖丢包信号。在有一定丢包的网络中(如 Wi-Fi/4G),BBR 性能远超 CUBIC。
第25讲:加餐|搞懂 Nginx 限流的关键概念
🎯 深度补充第8讲
📐 Nginx 限流三剑客
┌─────────────────────────────────────────────────────────────┐
│ Nginx 限流三剑客 │
├─────────────────────────────────────────────────────────────┤
│ │
│ limit_conn 限制并发连接数 │
│ ───────────────────────────────────── │
│ 同一 IP 同时最多 N 个 TCP 连接 │
│ 超出 → 503 Service Unavailable │
│ │
│ limit_req 限制请求速率 │
│ ───────────────────────────────────── │
│ 每秒最多 N 个请求 │
│ burst: 允许的突发容量 │
│ nodelay: 突发请求是否立即处理 │
│ │
│ limit_rate 限制响应带宽 │
│ ───────────────────────────────────── │
│ 单个连接的最大下载速度 │
│ 如 limit_rate 100k; # 限速 100KB/s │
│ │
└─────────────────────────────────────────────────────────────┘
📐 burst 与 nodelay 的微妙区别
nginx
# === 场景对比 ===
# 限制: rate=10r/s (每 100ms 一个请求)
# 配置 A: burst=5 nodelay
limit_req zone=api burst=5 nodelay;
# 到达 6 个并发请求 → 5 个立即处理 + 1 个 503
# 特点: 突发立即处理,之后恢复 10r/s
# 配置 B: burst=5 (无 nodelay)
limit_req zone=api burst=5;
# 到达 6 个并发请求 → 1 个立即处理 + 5 个排队 (每 100ms 释放一个) + 0 个 503
# 特点: 突发请求排队等待,第 6 个请求等待最多 500ms
可视化对比:
配置 A: burst=5 nodelay
请求: ① ② ③ ④ ⑤ ⑥ (同时到达)
处理: ① ② ③ ④ ⑤ ✓ (立即) ⑥ ✗ (503)
时间: 0ms─────────────────────────→
配置 B: burst=5
请求: ① ② ③ ④ ⑤ ⑥ (同时到达)
处理: ① ✓ (0ms) ② ✓ (100ms) ③ ✓ (200ms) ...
时间: 0ms─────100ms─────200ms─────300ms─────→
📐 完整限流配置模板
nginx
http {
# 定义限流区域
limit_req_zone $binary_remote_addr zone=api_rate:10m rate=10r/s;
limit_req_zone $server_name zone=per_server:10m rate=1000r/s;
limit_conn_zone $binary_remote_addr zone=api_conn:10m;
# 白名单 (不限制的 IP)
geo $limit {
default 1;
10.0.0.0/8 0; # 内网不限流
192.168.0.0/16 0; # 内网不限流
}
map $limit $limit_key {
0 ""; # 空 key → 不限流
1 $binary_remote_addr;
}
server {
listen 80;
# 全局限流
location / {
limit_req zone=api_rate burst=10 nodelay;
limit_req_status 429; # 自定义状态码
limit_conn api_conn 10;
limit_conn_status 429;
proxy_pass http://backend;
}
# 敏感接口更严格
location /api/payment/ {
limit_req zone=api_rate burst=3 nodelay;
limit_req_status 429;
proxy_pass http://payment_backend;
}
# 下载限速
location /downloads/ {
limit_rate 500k; # 500KB/s
limit_rate_after 1m; # 前 1MB 不限速
}
}
}
⚠️ 避坑清单
| 序号 | 坑 | 描述 | 解决 |
|---|---|---|---|
| 1 | binary_remote_addr vs remote_addr | 前者节省内存 4× | 始终使用 $binary_remote_addr |
| 2 | zone 大小不足 | 内存不够导致限流失效 | 64字节/IP → 10MB ≈ 16万IP |
| 3 | burst 理解错误 | burst 是并发容量不是额外速率 | burst=5 不会让速率变成 15r/s |
| 4 | 429 状态码处理 | 客户端不知道如何响应 429 | 配合 Retry-After header |
🔭 扩展思考
- 如何实现"动态限流"(根据后端负载自动调整)? → OpenResty + Lua + 共享内存
- Nginx 限流 vs API 网关限流怎么选? → Nginx 适合简单场景;网关(Kong/APISIX)功能更丰富
- 如何监控限流效果? →
$limit_req_status变量 + Prometheus nginx-exporter
🔹 结束篇
第26讲:结束语|每一次问题,都是成长的契机
🎯 架构师的成长心法
经过 25 讲的系统学习,你已经从协议层到系统层完整走了一遍网络架构的设计之道。但技术的终点不是知识,而是思维方式。
💡 三个"从...到..."的转变
┌─────────────────────────────────────────────────────────────┐
│ │
│ 转变 1: 从"解决问题"到"预防问题" │
│ ───────────────────────────────────── │
│ 初级工程师: 出故障 → 修复 │
│ 架构师: 设计阶段就消除故障可能 │
│ 关键能力: FMEA (故障模式与影响分析) │
│ │
│ 转变 2: 从"技术实现"到"业务权衡" │
│ ───────────────────────────────────── │
│ 初级工程师: 技术选型看谁"最好" │
│ 架构师: 在成本/性能/可靠性之间找平衡 │
│ 关键能力: 五维评估模型 (第5讲) │
│ │
│ 转变 3: 从"单点最优"到"系统全局" │
│ ───────────────────────────────────── │
│ 初级工程师: 优化单个服务 │
│ 架构师: 优化整个系统,可能局部要妥协 │
│ 关键能力: 瓶颈分析 + 全局视角 │
│ │
└─────────────────────────────────────────────────────────────┘
📚 推荐书单
| 书名 | 作者 | 核心价值 |
|---|---|---|
| 《Designing Data-Intensive Applications》 | Martin Kleppmann | 数据系统的设计哲学 |
| 《Site Reliability Engineering》 | Google SRE Team | 生产环境的运维之道 |
| 《TCP/IP Illustrated, Vol.1》 | W. Richard Stevens | 协议细节的权威参考 |
| 《Computer Networking: A Top-Down Approach》 | Kurose & Ross | 网络原理入门经典 |
| 《Systems Performance》 | Brendan Gregg | 性能分析方法论 |
| 《Cloud Native Infrastructure》 | Justin Garrison | 云原生基础设施模式 |
🗺️ 继续学习路线
本系列完成后的推荐路径:
网络架构实战 (本系列)
│
├── 深入方向: eBPF 可观测性 (性能调优进阶)
├── 广度方向: Kubernetes 网络 (CNI/ServiceMesh)
├── 应用方向: API 网关设计与开发
└── 安全方向: 零信任架构实践
推荐认证:
- CKA/CKAD (Kubernetes)
- AWS Solutions Architect
- CCIE (网络最高认证)
🎯 你已经具备的能力
✅ 独立设计中大型系统网络架构的能力
✅ 快速定位与解决线上网络问题的实战经验
✅ 在成本、性能、安全间做出合理权衡的架构思维
✅ 一套可复用的网络诊断与优化工具箱
✅ 从单机到云原生的全栈网络视野
第27讲:结课测试|来赴一场满分之约吧
📝 选择题
1. 某直播平台峰值 10 万并发,用户反馈"卡顿严重"。请选择正确的排查路径:
A. 检查 CDN 缓存命中率
B. 抓包分析 TCP 重传率
C. 查看应用层 GC 日志
D. 以上都是
✅ 正确答案:D --- 需要系统性排查:CDN(静态资源)→ TCP(传输质量)→ 应用(处理能力)
2. 以下哪个场景最适合使用一致性哈希负载均衡?
A. 静态文件下载
B. 用户登录后需要会话保持
C. 计算密集型任务
D. 实时消息推送
✅ 正确答案:B --- 一致性哈希确保同一用户始终路由到同一服务器
3. TLS 1.3 相比 TLS 1.2 的握手需要几次 RTT?
A. 3 RTT
B. 2 RTT
C. 1 RTT
D. 0 RTT
✅ 正确答案:C --- TLS 1.3 首次握手 1 RTT,PSK 重连 0 RTT
4. 以下哪个不是 TIME_WAIT 过多的正确解决方案?
A. 启用 tcp_tw_reuse
B. 使用 HTTP 长连接
C. 减小 MSL 时间
D. 增加服务器内存
✅ 正确答案:D --- 增加内存不能解决端口耗尽问题
5. 以下关于 VPC 对等连接的说法,哪个是错误的?
A. 双方 VPC 的 CIDR 不能重叠
B. 对等连接可以跨账号
C. 对等连接支持传递路由 (A↔B↔C = A↔C)
D. 需要双方都添加路由
✅ 正确答案:C --- VPC 对等连接不可传递,需要 Transit Gateway 实现
🏗️ 场景设计题
挑战:设计一个支持百万设备接入的物联网网关架构
设计要求:
- 100 万设备同时在线
- 每设备每 10 秒上报一次数据 (100KB)
- 需要支持设备认证 + 数据加密
- 99.99% 可用性
┌──────────────────────────────────────────────────────────────┐
│ IoT 网关架构 (百万级) │
├──────────────────────────────────────────────────────────────┤
│ │
│ 设备层 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 100万设备 × MQTT/TLS 1.3 │ │
│ │ 每设备 10s/次, 100KB/次 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ 接入层 (L4 负载均衡) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ HAProxy × 4 (L4, source hash) │ │
│ │ 每台: 25万连接, 2.5 Gbps │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ 消息层 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ EMQX Cluster × 8 节点 │ │
│ │ 每节点: 12.5万连接 │ │
│ │ 消息吞吐: 10万 msg/s × 100KB = 10 GB/s │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ 处理层 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Kafka Cluster × 10 节点 │ │
│ │ 数据缓冲 + 流处理 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ 存储层 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ TDengine × 5 节点 (时序数据) │ │
│ │ MySQL Cluster (设备元数据) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 容量估算: │
│ 带宽: 10万 msg/s × 100KB = 10 GB/s │
│ 存储: 10 GB/s × 86400s = 864 TB/天 │
│ → 保留 30 天 = 25.9 PB │
│ │
│ 成本估算 (粗略): │
│ 接入层: 4×EMQX Enterprise ≈ ¥20000/月 │
│ 消息层: 10×Kafka Broker ≈ ¥30000/月 │
│ 存储层: TDengine + MySQL ≈ ¥15000/月 │
│ 带宽: 10 Gbps 专线 ≈ ¥80000/月 │
│ 总计: ≈ ¥145000/月 │
│ │
└──────────────────────────────────────────────────────────────┘
📌 系列附录
🛠️ 配套实验脚本索引
| 脚本 | 对应讲数 | 用途 |
|---|---|---|
lab-01-tcpdump.sh |
第1讲 | HTTP 完整生命周期抓包 |
lab-02-env-setup.sh |
第2讲 | Docker 实验环境一键部署 |
lab-06-keepalived.sh |
第6讲 | keepalived + nginx 高可用 |
lab-07-haproxy.sh |
第7讲 | HAProxy 负载均衡 |
lab-08-nginx-limit.sh |
第8讲 | Nginx 限流配置 |
lab-11-bbr-test.sh |
第11讲 | BBR vs CUBIC 对比 |
lab-13-netem.sh |
第13讲 | tc netem 弱网模拟 |
lab-14-minio-upload.py |
第14讲 | MinIO 分片上传 |
lab-15-dns-dig.sh |
第15讲 | DNS 追踪分析 |
lab-19-nginx-tls.sh |
第19讲 | Nginx TLS 1.3 配置 |
lab-20-wireguard.sh |
第20讲 | WireGuard 隧道搭建 |
lab-21-iptables.sh |
第21讲 | iptables 安全规则 |
📊 关键命令速查表
bash
# 网络诊断
tcpdump -i eth0 -nn port 80 # 抓包
ss -t -i # Socket 详情
nmap -sS 192.168.0.0/24 # 端口扫描
dig +trace example.com # DNS 追踪
traceroute -n 8.8.8.8 # 路由追踪
# 性能测试
iperf3 -c 192.168.0.207 -t 30 # 带宽测试
wrk -t4 -c100 -d30s http://192.168.0.44/ # HTTP 压测
ab -n 1000 -c 100 http://192.168.0.44/ # Apache Bench
# 弱网模拟
tc qdisc add dev eth0 root netem delay 100ms loss 5%
tc qdisc del dev eth0 root
# TCP 调优
sysctl net.ipv4.tcp_congestion_control=bbr
sysctl net.core.rmem_max=16777216
# 安全
iptables -L -n -v
openssl s_client -connect example.com:443 -tls1_3
📌 文档版本 : v1.0
📌 创建日期 : 2026-06-06
📌 实验集群 : ecs-ee63 (华为云香港, 4×c6.large.2)
📌 总字数: ~12000 行, ~380KB
"网络架构不是背诵协议,而是在无数限制条件下找到最优解的艺术。"