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:抓包与命令速查

第38章 Capstone:把玩具代理改造成生产级骨架

学习目标

  • 列清"玩具代理"与"生产级代理"的具体差距,每条对应一个机制
  • 把背压、半关闭、超时、连接池、缓冲池、优雅关闭、减载整合进一个 Go 骨架
  • 诚实面对这个骨架仍未做的部分,理解为什么生产要用 Envoy/pingora
  • 用这一章把全书的机制串成一个可运行的整体

前置知识

  • 第21章 Go 玩具/第24章 C 玩具
  • 第34章 背压、第35章 状态机、第31章 性能、第37章 韧性

原理

玩具 vs 生产:差距具体在哪几行

第21章/第24章 的代理"能跑 demo",但离生产差一大截。把差距摊开,每条对应本书一个机制:

能力玩具生产级章
背压无界缓冲(慢读者 OOM)流控耦合 / watermark34
半关闭一刀 Close 截断CloseWrite 传播 FIN35
超时无connect/read/idle 分级31/35
连接池每次新建复用 + 探活03/31
缓冲每连接分配缓冲池复用31
过载无限接收直到崩减载(503)37
崩溃隔离一处 panic 全挂单连接 recover—
优雅关闭kill 即断连drain 存量再退35
可观测无连接/指标10

本章不是再写一个完整代理(那是 Nginx/Envoy 的体量),而是把最关键的几个机制补进一个 Go 骨架,让你亲眼看到"从玩具到生产"差的就是这些行。选 Go 是因为它的 io.Copy 已经免费给了背压(第34章),让我们能聚焦其它机制。


代码:生产级骨架(Go)

package main

import (
	"context"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"sync"
	"sync/atomic"
	"syscall"
	"time"
)

// 缓冲池:复用转发缓冲,避免每连接分配(第31章)
var bufPool = sync.Pool{New: func() any { b := make([]byte, 32*1024); return &b }}

// 并发上限:满了快速拒绝(减载,第37章)
var sem = make(chan struct{}, 10000)

var active int64 // 可观测:活跃连接数(第10章)

// 到后端的连接池 + 超时分级(第03/31/35章)
var transport = &http.Transport{
	DialContext: (&net.Dialer{
		Timeout:   5 * time.Second,  // connect 超时(第35章 SYN_SENT)
		KeepAlive: 30 * time.Second,
	}).DialContext,
	MaxIdleConns:          1000,
	MaxIdleConnsPerHost:   100,              // 后端连接池大小
	IdleConnTimeout:       90 * time.Second,
	ResponseHeaderTimeout: 30 * time.Second, // read 超时(第35章 → 504)
}

var hopHeaders = []string{"Connection", "Proxy-Connection", "Keep-Alive",
	"Proxy-Authenticate", "Proxy-Authorization", "Te", "Trailer", "Transfer-Encoding", "Upgrade"}

func main() {
	srv := &http.Server{
		Addr:              ":8080",
		Handler:           http.HandlerFunc(handle),
		ReadHeaderTimeout: 10 * time.Second, // 防 Slowloris(第34/35章)
		IdleTimeout:       60 * time.Second, // idle 回收(第35章)
	}

	// 优雅关闭:停止 accept,等存量 drain,超时强制(第35章)
	go func() {
		ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
		defer stop()
		<-ctx.Done()
		log.Println("关闭信号:停止 accept,drain 存量(≤30s)")
		sctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
		defer cancel()
		srv.Shutdown(sctx)
	}()

	log.Println("生产级骨架代理 :8080")
	if err := srv.ListenAndServe(); err != http.ErrServerClosed {
		log.Fatal(err)
	}
}

func handle(w http.ResponseWriter, r *http.Request) {
	// 减载:并发满了直接 503,保住其余请求 SLA(第37章)
	select {
	case sem <- struct{}{}:
		defer func() { <-sem }()
	default:
		http.Error(w, "overloaded", http.StatusServiceUnavailable)
		return
	}
	atomic.AddInt64(&active, 1)
	defer atomic.AddInt64(&active, -1)

	// panic 隔离:单连接崩溃不波及整个代理
	defer func() {
		if v := recover(); v != nil {
			log.Printf("panic recovered: %v", v)
		}
	}()

	if r.Method == http.MethodConnect {
		handleConnect(w, r)
		return
	}

	// 明文 HTTP:经连接池转发(删 hop-by-hop 头,第04章)
	for _, h := range hopHeaders {
		r.Header.Del(h)
	}
	r.RequestURI = ""
	resp, err := transport.RoundTrip(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadGateway) // 502
		return
	}
	defer resp.Body.Close()
	for _, h := range hopHeaders {
		resp.Header.Del(h)
	}
	for k, vv := range resp.Header {
		for _, v := range vv {
			w.Header().Add(k, v)
		}
	}
	w.WriteHeader(resp.StatusCode)
	buf := bufPool.Get().(*[]byte)
	defer bufPool.Put(buf)
	io.CopyBuffer(w, resp.Body, *buf) // 缓冲池复用 + io.Copy 自带背压(第34章)
}

func handleConnect(w http.ResponseWriter, r *http.Request) {
	dst, err := net.DialTimeout("tcp", r.Host, 5*time.Second) // connect 超时
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadGateway)
		return
	}
	hij, ok := w.(http.Hijacker)
	if !ok {
		http.Error(w, "no hijack", http.StatusInternalServerError)
		dst.Close()
		return
	}
	src, _, err := hij.Hijack()
	if err != nil {
		dst.Close()
		return
	}
	src.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n"))

	// 双向转发:半关闭传播(第35章)+ 背压(io.Copy 自带,第34章)
	var wg sync.WaitGroup
	wg.Add(2)
	go func() { defer wg.Done(); pipe(dst, src) }()
	go func() { defer wg.Done(); pipe(src, dst) }()
	wg.Wait()
	dst.Close()
	src.Close()
}

// 单向转发:EOF 时只半关闭【对端写方向】,不 close 整条(第35章)
func pipe(dst, src net.Conn) {
	buf := bufPool.Get().(*[]byte)
	defer bufPool.Put(buf)
	io.CopyBuffer(dst, src, *buf) // Write 阻塞自动停 Read = 背压(第34章)
	if c, ok := dst.(interface{ CloseWrite() error }); ok {
		c.CloseWrite() // 传播 FIN,不截断另一方向(第35章)
	}
}
go run skeleton.go &
curl -x http://127.0.0.1:8080 http://httpbin.org/ip            # 明文 → 连接池 + 502 兜底
curl -x http://127.0.0.1:8080 https://example.com/ -s -o /dev/null -w "%{http_code}\n"  # CONNECT
# 慢读者测试:内存应平稳(背压 + 缓冲池)
curl --limit-rate 10k -x http://127.0.0.1:8080 http://源站/big.bin -o /dev/null

对照玩具: 多出来的每一块都对应前面一章——超时、连接池、缓冲池、半关闭、减载、panic 隔离、优雅关闭。这就是"从玩具到生产"的差距,不是更聪明的算法,而是这些不起眼但致命的机制。


诚实:这个骨架还差什么才算"真生产级"

我不想给你"造好了"的错觉。这个骨架补齐了基础机制,但离 Envoy/Nginx 仍有量级差距,诚实列出未做的:

  • HTTP/2 / HPACK 终止(第36章):骨架只处理 h1 + CONNECT 隧道,没做 h2 帧解析、两级流控、HPACK 重编码
  • 精细的流式 watermark(第34章):Go 的 io.Copy 给了背压,但没有 Envoy 那样可配置的高低水位有界缓冲
  • TLS 终止 / mTLS / 证书管理(第05章):骨架只隧道不终止
  • 动态配置 / xDS(第15章):路由/后端写死,不能热更新
  • 完整韧性(第37章):没有熔断状态机、重试预算、异常剔除、P2C 负载均衡
  • 深度可观测:只有连接计数,没有分布式追踪上下文注入、细粒度指标、访问日志
  • 连接探活:连接池复用前未主动探活坏死连接(第35章)

结论:写一个"能用"的代理是几十行;写一个"扛得住生产"的代理是 Envoy/Nginx 那样数十万行——差距全在本篇这些机制的完整、正确、可配置实现上。理解了它们,你既能读懂这些项目的源码,也能在选型时知道自己写 vs 用现成的真实成本。


排错

现象根因解决
慢读者下骨架内存仍涨用了 io.Copy 但同时缓冲了整 body走流式(本骨架已流式);别 io.ReadAll
优雅关闭时长连接被硬断CONNECT 隧道不受 Shutdown 管控单独追踪隧道连接并 drain
高负载大量 503并发上限设太低调 sem 容量 + 扩容(减载是保护非目的)
后端连接池命中率低MaxIdleConnsPerHost 太小调大池;确认复用(第31章)
panic 日志刷屏某类请求稳定触发 panic修根因,recover 只是兜底

本章小结

  • 玩具到生产的差距,不在算法在机制:背压、半关闭、超时、连接池、缓冲池、减载、隔离、优雅关闭——每条都是本书一章。
  • Go 骨架因 io.Copy 自带背压而能聚焦其它机制;半关闭用 CloseWrite 传播、减载用并发信号量、优雅关闭用 srv.Shutdown。
  • 诚实:它仍缺 h2/HPACK、TLS 终止、xDS、完整韧性、深度可观测——这正是 Envoy/Nginx 数十万行的价值所在。

底层机制篇 · 结语

至此底层机制篇完结,也补上了本书最初承诺却讲浅了的一层"底层原理":

  • 第34章 背压——代理最难的部分,流控耦合与 watermark
  • 第35章 TCP 状态机——半关闭、CLOSE_WAIT/TIME_WAIT、生命周期
  • 第36章 HTTP/2 内部——帧、两级流控、HPACK、h2↔h1 翻译
  • 第37章 算法与韧性——P2C/一致性哈希/Maglev、熔断、亚稳态
  • 第38章 capstone——把机制整合成生产级骨架,并诚实交代差距

如果说前七篇让你"看懂任何代理、能配能用能手写玩具",这一篇让你"理解一个生产级代理在压力下到底怎么活下来"。代理的本质是在两点间多一跳获得控制力——而这一跳要在真实流量的速度差、慢客户端、连接风暴、后端故障下依然稳健,靠的正是本篇这几套机制。

回到 手册首页 查看完整 38 章 + 附录。

Prev
第37章 负载均衡算法推导与韧性状态机