Go标准库log包在高并发下卡住,因其默认使用带锁io.Writer导致日志调用串行阻塞;应改用无锁、结构化、零分配的zap日志库,并正确配置TimeKey、EncodeTime、LevelEnablerFunc及WriteSyncer。
为什么直接用 log 包在高并发下会卡住
Go 标准库的 log 包默认使用带锁的 io.Writer,所有日志调用串行排队。当每秒写入上千条日志(比如 HTTP 请求打点、微服务 trace 日志),log.Println 会成为明显瓶颈,CPU 没跑满,但 goroutine 大量阻塞在 mu.Lock() 上。
常见现象包括:runtime.gopark 占比飙升、pprof 显示大量时间花在 sync.(*Mutex).Lock、日志延迟高达数百毫秒。
- 避免在 hot path(如 HTTP handler 内)直接调用
log.Printf - 不要把
os.Stderr或os.Stdout直接传给多个log.New实例——它们共享底层锁 - 即使加了缓冲(如用
bufio.Writer包裹),仍无法绕过log包自身的 mutex
用 zap 替代标准 log 的关键配置项
zap 是目前 Go 生态中性能最优、生产就绪的日志库,核心优势是无锁设计 + 结构化 + 零分配(对 hot path 友好)。但开箱即用的 zap.NewProduction() 并不适合所有场景。
必须显式控制的几个参数:
-
EncoderConfig.TimeKey:设为""可完全禁用时间字段(若日志系统本身已带时间戳,避免重复) -
EncoderConfig.EncodeTime:用zapcore.ISO8601TimeEncoder而非默认的zapcore.EpochTimeEncoder,减少格式化开销 -
LevelEnablerFunc:用函数替代zap.LevelEnabler接口实现,避免接口动态调用成本 - 输出目标必须用
zapcore.AddSync(&fileWriter),不能直接传*os.File——否则丢失写缓冲和错误重试
示例片段: