长连接网关不能用std::thread每连接一线程,因10万连接需约800GB栈内存、上下文切换开销爆炸、线程创建销毁成本高;必须采用单线程事件循环+非阻塞I/O(如epoll/kqueue)。

长连接网关为什么不能用 std::thread 每连接一线程
因为扛不住。10 万连接 ≈ 10 万线程,Linux 默认栈 8MB,光栈内存就吃掉 800GB;上下文切换开销爆炸,epoll_wait 返回后调度延迟不可控;std::thread 创建/销毁成本高,无法复用。真实网关必须用单线程事件循环 + 非阻塞 I/O,典型如 libevent、libuv 或直接封装 epoll/kqueue。
实操建议:
- 用
epoll(Linux)或kqueue(macOS/BSD),避免select的 1024 文件描述符硬限制和遍历开销 - 每个 worker 进程绑定 1 个 CPU 核,用
pthread_setaffinity_np固定亲和性,减少跨核缓存失效 - 连接结构体(
struct conn_ctx)必须紧凑,避免 cache line 分割;把频繁访问字段(如读缓冲区指针、状态码)放在前 64 字节内
路由匹配怎么做到 O(1) 而不是遍历所有规则
正则或字符串前缀匹配在百万级规则下必然拖垮性能。真正可行的是分层哈希 + 前缀树组合:Host 和 Path 分离处理,Host 用 std::unordered_map<:string route_table_t> 快速定位虚拟主机表;Path 使用 radix tree(如 nginx 的 ngx_hash_t 变种)支持最长前缀匹配,插入/查询均摊 O(k),k 是路径段数。
注意点:
- 不要在运行时动态编译正则——
std::regex构造开销大,且可能被恶意规则触发回溯爆炸;预编译后固化到内存只读段 - 路由规则需支持权重、健康检查标记、灰度标签,这些元数据存在独立哈希表里,不混入匹配主路径
- 热更新路由表必须无锁:用 RCU(read-copy-update)模式,新表构建完成后再原子交换指针,老表延迟释放
分布式场景下如何同步连接状态和路由变更
连接状态(如用户 ID、鉴权 token、上游节点选择)不能只存在本地内存,否则 failover 或扩缩容时会丢状态。但全量上发 etcd/Redis 又太重——每次读写都走网络,10 万连接每秒几次心跳就是百万 QPS。
折中方案是「本地优先 + 异步广播」:
- 连接元数据存在本地
folly::ConcurrentHashMap或boost::lockfree::spsc_queue配合 per-CPU slab 分配器 - 路由变更通过
gRPC或protobufoverUnix domain socket推送到本机所有 worker,不经过网络中间件 - 节点间心跳用轻量
UDP+ 应用层 ACK,避免 TCP 建连开销;失败时降级为定时拉取配置中心的版本号
sendfile 和 splice 在零拷贝转发中怎么选
网关大量做「接收请求 → 转发上游 → 接收响应 → 返回客户端」,传统 read+write 至少 4 次用户态/内核态拷贝。Linux 提供两个零拷贝路径:
-
sendfile(fd_out, fd_in, offset, count):仅支持文件 → socket,适合静态资源透传,但不支持 socket → socket -
splice(fd_in, &offset_in, fd_out, &offset_out, len, flags):支持 pipe ↔ socket,可构造两段 pipe 实现 socket → pipe → socket,但要求两端都支持splice(如 Linux >=2.6.27,且 socket 不能是 TLS)
实际部署中,HTTP/1.1 纯文本流量用 splice + SPLICE_F_MOVE 效果最好;HTTPS 流量因 TLS 终结在网关,必须解密再加密,零拷贝失效,此时重点优化 OpenSSL 的 SSL_read_ex/SSL_write_ex 缓冲区复用。
容易忽略的一点:splice 对 SOCK_STREAM 的 offset 参数无效,必须配合 pipe 中转,且 pipe buffer 大小(/proc/sys/fs/pipe-max-size)要调到 2MB 以上,否则频繁阻塞。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











