很多初次部署 Hysteria 2 的运维人员都会被其内存占用吓一跳:明明只是一个代理协议,怎么跑起来像个内存怪兽?在一台 2GB 内存的标准 VPS 上,如果不做针对性优化,Hysteria 2 的进程配合系统开销,很容易就把内存红线踩爆。这并非代码缺陷,而是其底层架构设计所决定的必然结果。
暴力提速的代价:UDP 缓冲区膨胀
Hysteria 2 的核心卖点是基于 QUIC 协议的 ” 暴力 ” 提速。不同于传统 TCP 代理那种 ” 挤牙膏 ” 式的拥塞控制,Hysteria 2 采用了激进的带宽抢占策略。这就要求系统必须开辟巨大的 接收与发送缓冲区 来容纳瞬间涌入的海量 UDP 数据报。
打个比方,如果传统代理是根细水管,那 Hysteria 2 就是个大口径消防栓。水流(数据)还没来得及分发出去,蓄水池(内存缓冲区)就得先存着。默认配置下,Linux 内核为每个 UDP socket 分配的缓冲区可能并不大,但 Hysteria 2 为了跑满带宽,往往会自行申请更大的内存空间来应对丢包重传和乱序到达的数据包。当并发连接数一上来,成百上千个 ” 蓄水池 ” 同时蓄水,内存消耗自然呈线性甚至指数级上升。
Go 语言的内存管理特性
Hysteria 2 使用 Go 语言编写,这也是内存高企的幕后推手之一。Go 语言的运行时包含了垃圾回收机制(GC),为了减少 GC 触发的频率,Go 往往会向操作系统申请比实际使用量更多的内存,并在内部进行管理。
在监控面板里看到的 1.93 GB 占用,并不一定全是 Hysteria 2 实际正在 ” 使用 ” 的数据,很大一部分可能是 Go 运行时预留的 ” 闲置土地 ”,随时准备分配给新的连接请求。这种机制虽然提升了程序响应速度,但在视觉上给人的冲击力极强——看起来像是内存泄漏,实则是 Go 的内存池化策略在作祟。
如何给内存瘦身?
既然知道了病因,对症下药就不难。最直接的手段是调整系统层面的 UDP 缓冲区限制。不需要让每个连接都无限扩张,可以通过 sysctl 参数如 net.core.rmem_max 和 net.core.wmem_max 设定一个合理的上限,强制内核和应用程序 ” 省着点用 ”。
另外,在 Hysteria 2 的配置文件中,通过限制 recvWindowConn 和 recvWindowClient 等参数,能够从应用层直接锁死每个连接的内存胃口。与其让它在系统里野蛮生长,不如给它套上个紧箍咒。毕竟,在 2G 内存的机器上跑这种吞吐量极大的协议,精细化控制才是生存之道。
