HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 代理技术全栈手册

    • 代理技术全栈手册 - HiHuo
    • 原理篇

      • 第01章 代理是什么:正向 / 反向 / 透明 / 隧道的统一模型
      • 第02章 代理与网络层级:L3 / L4 / L5 / L7 在哪里截断流量
      • 第03章 一个请求穿过代理的一生:连接生命周期全景
    • 协议篇

      • 第04章 HTTP 代理协议:绝对 URI、CONNECT 隧道、转发头与连接池
      • 第05章 HTTPS 与 TLS 代理:终止 / 透传 / MITM / SNI / mTLS
      • 第06章 SOCKS 协议:SOCKS4/4a/5 与 UDP ASSOCIATE 报文级解析
      • 第07章 HTTP/2、gRPC 与 HTTP/3(QUIC) 代理的挑战
      • 第08章 代理自动配置:PAC / WPAD / 系统代理 / NO_PROXY
    • 层级与转发篇

      • 第09章 L4 代理:TCP/UDP 转发与连接级负载均衡
      • 第10章 L7 代理:协议感知与基于内容的路由
      • 第11章 透明代理:iptables REDIRECT/DNAT、TPROXY 与 eBPF 劫持
      • 第12章 数据搬运的艺术:splice / sendfile / 零拷贝 / io_uring
    • 组件横评篇

      • 第13章 Nginx / OpenResty:反向代理、upstream 与 Lua 可编程
      • 第14章 HAProxy:L4/L7、ACL、健康检查与 stick table
      • 第15章 Envoy:xDS 动态配置与 filter chain,为何是云原生数据面
      • 第16章 Traefik / Caddy:自动服务发现与自动 HTTPS
      • 第17章 Squid 与正向/缓存代理:企业出网、缓存与审计
      • 第18章 mitmproxy:抓包、改包、脚本化调试
      • 第19章 内网穿透与隧道:frp / gost / SSH 隧道 / ngrok
      • 第20章 科学上网生态的技术原理(技术中立)
    • 多语言手写篇

      • 第21章 Go:100 行手写 HTTP/CONNECT + SOCKS5 代理
      • 第22章 Rust:基于 tokio 的高性能 TCP 代理
      • 第23章 Python:asyncio 实现,适合调试与脚本
      • 第24章 C:epoll 裸写与零拷贝,及语言选型对比
    • 容器与K8s篇

      • 第25章 Docker 里的代理:HTTP_PROXY、build/pull 与 daemon 配置
      • 第26章 Sidecar 与流量劫持:Istio init-container 的 iptables 原理
      • 第27章 Ingress 与南北流量:Ingress-nginx 与 Gateway API
      • 第28章 Egress 与出网治理:出口网关、registry mirror、审计
      • 第29章 Service Mesh 数据面:Envoy Sidecar 全链路
    • 进阶篇

      • 第30章 可编程代理:Lua / Wasm / eBPF / xDS,代理的"软件定义"
      • 第31章 性能调优:并发模型、连接池、超时与重试、压测
      • 第32章 排错决策树:502 / 504 / 握手失败 / 环路 / 泄漏
      • 第33章 代理安全:开放代理、SSRF、凭证泄漏与攻击面
    • 底层机制篇

      • 第34章 代理的背压与流控:一个代理最难的部分
      • 第35章 socket 与 TCP 状态机:半关闭、超时、连接生命周期
      • 第36章 HTTP/2 帧、流控与 HPACK:h2 代理的内部机制
      • 第37章 负载均衡算法推导与韧性状态机
      • 第38章 Capstone:把玩具代理改造成生产级骨架
    • 综合实战篇

      • 第39章 企业多跳转发链:拓扑、协议矩阵与贯穿性难题
      • 第40章 端到端实战:把 6 类流量全代理通
      • 第41章 更刁钻的流量:gRPC、长轮询、WebRTC、大文件、双向流
      • 第42章 可落地完整参考实现:一套能跑的多协议转发栈
    • 附录

      • 附录 A:代理协议报文速查(HTTP / SOCKS / PAC / PROXY protocol)
      • 附录 B:组件选型决策树
      • 附录 C:抓包与命令速查

第31章 性能调优:并发模型、连接池、超时与重试、压测

学习目标

  • 掌握代理性能的四个维度与并发模型选择
  • 用好连接池、超时分级、重试退避,避开"重试放大"
  • 调对关键内核参数(fd、conntrack、端口范围、backlog)
  • 用正确的压测方法测尾延迟,避开"协调遗漏"陷阱

前置知识

  • 第03章 连接池/缓冲、第12章 零拷贝、第09章 LB
  • 网络手册 · 系统级调优

原理

四个性能维度

  吞吐  QPS / 带宽(每秒处理多少)
  延迟  p50 / p99 / p999(尾延迟比均值更重要!)
  并发  同时维持多少连接(fd、内存)
  资源  CPU / 内存 / fd(成本)

代理优化的核心是尾延迟(p99/p999),不是均值。用户体验由最慢的那部分请求决定,而代理是每个请求的必经之路。

并发模型

模型代表特点
多进程 + 事件驱动Nginxworker 各跑 epoll,SO_REUSEPORT 分摊 accept
多线程 + 事件驱动HAProxy(新)、Envoy线程间负载均衡
协程Go(Traefik/frp)goroutine-per-conn(第21章)
异步Rust/tokio(pingora)无栈状态机(第22章)

调优要点:worker 数 = CPU 核数、绑核(CPU affinity)减少缓存抖动、SO_REUSEPORT 让多 worker 并行 accept 避免惊群。

连接池:最高杠杆的优化(呼应第03章)

第03章 讲过,代理→后端每次新建 TCP+TLS 太贵。连接池复用是性价比最高的优化:

  无池:每请求 1 次 TCP 握手 + 1~2 次 TLS 握手(~100ms+)
  有池:复用空闲连接,省掉全部握手

关键参数:池大小(keepalive/max_idle_conns)、空闲超时、单连接最大请求数(防止连接老化打不散负载,第09章 的长连接陷阱)。Nginx 别忘 proxy_http_version 1.1 + 清 Connection(第13章)。

超时分级:少一个就出诡异问题

  connect timeout  连后端的超时(快速失败)
  read/server timeout 等后端响应(防慢后端拖死)
  client/idle timeout 客户端空闲(回收僵尸连接)
  整体 timeout      请求总时长上限

太长 → 连接/内存堆积;太短 → 误杀正常慢请求。HAProxy 三超时(第14章)是必修课。

重试与退避:小心放大

重试能提可用性,但用不好会雪崩:

  • 幂等性(第10章):POST 别盲目重试
  • 重试放大:每层都重试 3 次,3 层就是 27 倍流量 → 把摇摇欲坠的后端彻底打死
  • 缓解:指数退避 + 抖动、重试预算(限制重试占比,如 ≤10%)、配合熔断(错误率高就停止打后端)

关键内核参数

参数作用症状
ulimit -n / fs.file-max最大打开 fd连接数撞顶 → Too many open files
net.core.somaxconn + listen backlogaccept 队列高并发握手丢失、连接被拒
net.ipv4.ip_local_port_range出向源端口范围代理→后端连接多时端口耗尽
net.ipv4.tcp_tw_reuse复用 TIME_WAIT短连接下 TIME_WAIT 堆积
nf_conntrack_maxconntrack 表大小透明代理/NAT 下表满丢包(第11章)

TLS 性能

TLS 握手是大头,优化手段:会话复用(session resumption / tickets)、TLS 1.3(1-RTT,0-RTT)、OCSP stapling、硬件/指令集加速(AES-NI)、把 TLS 卸载到代理(第05章)。

压测:避开"协调遗漏"陷阱

最容易被坑的一点:ab、wrk 会严重低估尾延迟。原因是"协调遗漏(coordinated omission)"——当被测系统卡住,压测工具也跟着等,于是漏报了本该发生的慢请求。

正确做法:用 wrk2(或 vegeta)以恒定速率压测(-R),它在固定速率下补偿被遗漏的延迟,p99/p999 才真实。先用低速率找基线,逐步加压找拐点(延迟开始陡升处)。


️ 实现 / 命令

实验一:内核参数调优

# 提高 fd 上限
ulimit -n 1048576
# sysctl 关键项
sudo sysctl -w net.core.somaxconn=65535
sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535"
sudo sysctl -w net.ipv4.tcp_tw_reuse=1
sudo sysctl -w net.netfilter.nf_conntrack_max=1048576   # 透明代理/NAT 场景

实验二:Nginx 连接池 + worker 调优

worker_processes auto;                  # = CPU 核数
worker_rlimit_nofile 1048576;
events { worker_connections 65535; use epoll; }

upstream backend {
    server 10.0.0.11:8080;
    keepalive 64;                       # 到后端的连接池
    keepalive_requests 10000;
    keepalive_timeout 60s;
}
server {
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";  # 复用必备(第13章)
    }
}

实验三:用 wrk2 正确测尾延迟

# 恒定 10000 QPS 压 30 秒,看真实 p99/p999(-R 是关键,避开协调遗漏)
wrk2 -t8 -c200 -d30s -R10000 --latency http://proxy/api
#   Latency Distribution:
#    50%  1.20ms
#    99%  18.5ms      ← 真实尾延迟(ab/wrk 会报得更乐观)
#    99.9% 120ms
# 逐步加 -R,找到 p99 陡升的拐点 = 系统容量上限

实验四:排查连接耗尽

ss -s                                   # 连接总览,看 TIME_WAIT/ESTAB 数量
ss -tan state time-wait | wc -l         # TIME_WAIT 堆积?
cat /proc/sys/net/netfilter/nf_conntrack_count   # conntrack 用量 vs max
lsof -p $(pgrep nginx) | wc -l          # fd 用量 vs ulimit

排错

现象根因解决
Too many open filesfd 撞 ulimit提高 ulimit -n/worker_rlimit_nofile
高并发握手被拒/丢backlog/somaxconn 太小调 somaxconn + listen backlog
代理→后端端口耗尽短连接 + 端口范围小连接池复用 + 扩端口范围 + tw_reuse
透明代理/NAT 下偶发丢包conntrack 表满调 nf_conntrack_max
尾延迟实测远高于压测报告协调遗漏用 wrk2 -R 恒定速率
偶发 502,后端没满连接池太小/连接老化调 keepalive、keepalive_requests
重试把后端打挂重试放大退避 + 重试预算 + 熔断

本章小结

  • 优化核心是尾延迟(p99/p999);并发模型选事件驱动/协程,worker=核数 + 绑核 + SO_REUSEPORT。
  • 连接池是最高杠杆(省握手);超时分级必配齐;重试要防放大(退避+预算+熔断)。
  • 调对内核参数:fd、somaxconn、端口范围、conntrack。
  • 压测必用 wrk2 恒定速率避开协调遗漏,逐步加压找拐点。

下一章 第32章 排错决策树,把全书的故障场景收成一棵决策树——从症状到根因,分段二分快速定位。

Prev
第30章 可编程代理:Lua / Wasm / eBPF / xDS,代理的"软件定义"
Next
第32章 排错决策树:502 / 504 / 握手失败 / 环路 / 泄漏