Go语言不直接扛住千万级并发,关键在架构设计是否让goroutine、连接复用、无状态分发和异步解耦生效;盲目堆机器或滥用go关键字反而加速系统崩溃。

Go 语言本身不直接“扛住千万级并发”,关键在架构设计是否让 goroutine、连接复用、无状态分发和异步解耦真正生效。盲目堆机器或写满 go 关键字反而会更快打垮系统。
为什么 net/http 默认配置在世界杯场景下必崩
开赛瞬间 QPS 突破 50 万时,net/http 的默认 Server 配置会迅速暴露瓶颈:
- 默认
MaxConnsPerHost和MaxIdleConns过低,大量连接卡在等待复用阶段,触发超时重试,形成请求雪球 - 每个 HTTP 请求默认启动新 goroutine,但未设
context.WithTimeout或限流,goroutine 泄漏风险极高 - 日志、中间件若含同步 I/O(如直接写磁盘日志),会阻塞整个 M:N 调度器,拖慢所有连接
- 未关闭
KeepAlive或设置过长空闲时间,导致连接池被“僵尸连接”占满,新用户连不上
WebSocket 推送比分时,conn.WriteMessage 阻塞怎么办
实时比分依赖 websocket.Conn.WriteMessage,但它不是线程安全的,且底层 write 可能阻塞——尤其当客户端网络差或断连未及时检测时。
- 永远不要在业务 goroutine 中直接调用
WriteMessage;改用带缓冲的 channel + 单独 writer goroutine - 为每个连接配独立
sync.Pool缓冲区,避免频繁分配[]byte,减少 GC 压力 - 启用
SetWriteDeadline,超时即关闭连接,防止一个慢连接拖垮整个推送队列 - 用
redis.PubSub订阅比分变更,但消费端必须做批量合并:同一秒内多个进球事件合并为单次推送,避免高频小包
用 sync.Pool 复用连接,但忘了清理 net.Conn 的底层资源
sync.Pool 是常用优化手段,但 Go 的 net.Conn(包括 websocket.Conn)持有系统文件描述符(fd),Pool 不会自动关闭它。
- 从 Pool 获取连接后,必须检查
conn.RemoteAddr() == nil或conn.IsClosed()(需自行封装),无效连接立即丢弃并重拨 - 归还前务必调用
conn.Close(),否则 fd 持续泄漏,ulimit -n达到上限后新连接全部失败,错误信息是"too many open files" - 更稳妥做法:Pool 存储的是连接工厂函数或初始化后的结构体,而非裸
*websocket.Conn;连接生命周期由上层控制 - 配合
pprof监控runtime.OpenFDs,上线前压测中必须观察 fd 增长曲线是否收敛
真正卡住系统的,往往不是 goroutine 数量,而是跨层资源未释放——比如 Redis 连接没 Close、HTTP body 没 io.Copy(ioutil.Discard, resp.Body)、WebSocket 连接归还 Pool 前没清理 ping/pong timer。这些细节在流量洪峰下会被指数级放大。


















