必须显式配置 http.Server 的 ReadTimeout 和 WriteTimeout 防止 goroutine 堆积;ReadTimeout 控制读请求头和体超时(建议 5s),WriteTimeout 控制写响应总耗时(建议 10s),并推荐设置 IdleTimeout 防长连接滥用。

用 http.Server 的 ReadTimeout 和 WriteTimeout 防止连接拖垮服务
Go 默认不设超时,一个慢客户端或网络抖动就可能让 goroutine 堆积、内存暴涨。必须显式配置读写超时,而不是依赖反向代理(如 Nginx)的超时设置——后者只管转发层,Go 服务内部仍会持续等待。
-
ReadTimeout控制从 TCP 连接读取请求头和请求体的最大时间,建议设为5 * time.Second;超过则直接关闭连接,不进入路由逻辑 -
WriteTimeout控制写响应的最大时间,建议设为10 * time.Second;注意它包含中间件执行、模板渲染、DB 查询等全部耗时 - 避免设成
0或过长(如30s),否则容易触发too many open files或 goroutine 泄漏
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 30 * time.Second, // 推荐同时设 IdleTimeout 防长连接滥用
}禁用默认 http.DefaultServeMux,用 http.ServeMux 或第三方路由器时注意并发安全
直接用 http.HandleFunc 会注册到全局 http.DefaultServeMux,它底层是 map + sync.RWMutex,高并发下锁争用明显。更严重的是,它不支持路径参数、中间件链、HTTP 方法精确匹配,容易写出低效甚至错误的路由逻辑。
- 自己 new 一个
http.ServeMux实例,避免全局竞争;但注意它仍不支持通配和变量提取 - 若需路径参数(如
/user/{id}),用chi或gorilla/mux,别手写正则匹配——每次请求都编译正则开销大且易出错 - 所有自定义中间件函数必须是无状态的,避免在闭包里捕获 request / response —— 它们会被复用,可能导致数据污染
响应体压缩用 gzip.Handler 要谨慎:只对文本类内容启用
盲目套一层 gzip.NewHandler 看似简单,实则可能降低性能:压缩本身吃 CPU,而小响应(
- 优先在反向代理层(Nginx / CDN)做 Gzip,让 Go 专注业务逻辑
- 如果必须在 Go 层压缩,用
alexedwards/scs/v2或手动检查Content-Type头,仅对text/、application/json、application/javascript等类型启用 - 设置
GzipLevel为gzip.BestSpeed(1),而非默认的gzip.DefaultCompression(6),平衡速度与压缩率
数据库查询别用 database/sql 的 QueryRow 直接扫全表
很多新手写 db.QueryRow("SELECT * FROM users WHERE id = ?", id),看似没问题,但 * 会让数据库返回所有字段,网络传输、内存分配、GC 压力都上升;更糟的是没加 WHERE 索引或写成 SELECT * FROM logs,直接拖垮整个服务。
立即学习“go语言免费学习笔记(深入)”;
- 永远明确列出所需字段,例如
SELECT id, name, email FROM users - 确保
WHERE条件字段有索引,用EXPLAIN检查执行计划;Go 层不要依赖“小数据量暂时没事” - 分页用
LIMIT/OFFSET时注意深度分页性能衰减,改用游标分页(WHERE id > ? ORDER BY id LIMIT ?) - 连接池参数必须调优:
SetMaxOpenConns不宜过大(如 100+),避免数据库拒绝连接;SetMaxIdleConns建议设为SetMaxOpenConns的 1/4~1/2
实际压测中,去掉 SELECT * 和补上索引,QPS 常提升 3~5 倍;而一个没设 WriteTimeout 的服务,在慢日志场景下可能 2 分钟内耗尽 65535 个文件描述符。这些点不难改,但上线前常被跳过。











