应避免在 HTTP handler 中执行同步阻塞操作,需设超时、用异步日志、sync.Pool 复用对象、优化 JSON 序列化、谨慎处理 request body 读取与复用。

避免在 HTTP handler 中做同步阻塞操作
Go 的 goroutine 调度很轻量,但如果你在 http.HandlerFunc 里直接调用耗时的同步函数(比如未加超时的 http.Get、大文件读取、无缓冲 channel 发送),会卡住当前 goroutine,拖慢整个服务吞吐。这不是并发瓶颈,而是人为制造串行。
- 数据库查询务必设
context.WithTimeout,例如db.QueryContext(ctx, query) - 下游 HTTP 调用统一走
http.Client{Timeout: 3 * time.Second},不要依赖默认零值 - 避免在 handler 里做
time.Sleep或for { ... }等无条件循环 - 日志写入若用同步文件写(如标准
log包直写磁盘),建议换为带缓冲的异步 logger(如zerolog.New(os.Stdout).With().Timestamp().Logger())
用 sync.Pool 复用高频小对象
API 常见模式是每次请求构造临时结构体(如响应 DTO、JSON 解析中间 map)、bytes.Buffer、strings.Builder。频繁分配会推高 GC 压力,尤其在 QPS 上千时明显。
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func handler(w http.ResponseWriter, r http.Request) {
buf := bufferPool.Get().(bytes.Buffer)
buf.Reset()
defer bufferPool.Put(buf)
json.NewEncoder(buf).Encode(responseData)
w.Header().Set("Content-Type", "application/json")
w.Write(buf.Bytes())}
注意:只对生命周期明确、大小稳定、创建开销显著的对象用 sync.Pool;别往池里扔含指针或未清理状态的对象,否则可能引发数据污染。
立即学习“go语言免费学习笔记(深入)”;
减少 JSON 序列化/反序列化开销
json.Marshal 和 json.Unmarshal 是 API 中最常被 profiler 抓出的热点。反射+动态类型检查成本高,且默认生成带空格缩进的格式(纯浪费)。
- 用
jsoniter.ConfigCompatibleWithStandardLibrary替代原生encoding/json,通常快 2–5 倍 - 结构体字段加
json:"field_name,omitempty"减少无效字段编码 - 避免用
map[string]interface{}接收未知结构,优先定义具体 struct 并用json.RawMessage延迟解析嵌套部分 - 输出固定结构响应时,可预计算 JSON 字节切片(如用
const resp = `{"code":0,"msg":"ok"}`),直接w.Write([]byte(resp))
小心中间件链中的隐式内存拷贝
很多 Go Web 框架(如 Gin、Echo)允许你在中间件中读取 r.Body,但 io.ReadCloser 只能读一次。常见错误是中间件里调用 io.ReadAll(r.Body) 后,handler 再读就得到空内容——框架底层会尝试重放 body,但多数实现是深拷贝整块字节,QPS 高时内存和 CPU 双飙升。
- 用
r.Body = ioutil.NopCloser(bytes.NewReader(data))手动重置 body,前提是 data 已缓存 - 更推荐:只在必须校验或审计时读 body,并用
http.MaxBytesReader限流防攻击 - Gin 用户慎用
c.ShouldBindJSON()在多个中间件里重复调用,它内部会反复解析 - 自定义中间件中避免
fmt.Sprintf拼接日志,改用fmt.Fprintf直接写入bytes.Buffer
真正影响 API 性能的往往不是算法复杂度,而是这些看似“无关紧要”的小动作在每请求路径上叠加后的放大效应。尤其是 sync.Pool 的误用、JSON 的泛型解包、body 的重复读取,线上压测时才暴露,但修复成本很低。











