应使用带缓冲通道并设置合理容量(如1000),避免无缓冲chan导致上游阻塞;scanner需调大缓冲限制并及时复制数据;etl需用errgroup控制并发与上下文取消。

为什么 chan 直接传日志行会卡死或丢数据
Go 的管道(chan)不是缓冲队列,无缓冲通道在发送端和接收端未就绪时会阻塞。日志 ETL 流程中,解析、过滤、转换、写入各阶段耗时差异大,比如 json.Unmarshal 快,但写入 elasticsearch 或网络 IO 慢,直接用无缓冲 chan *LogEntry 会导致上游解析协程被挂起,吞吐骤降甚至死锁。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 始终为中间管道设置合理缓冲:
make(chan *LogEntry, 1000),数值根据内存与背压容忍度权衡(1k~10k 常见) - 避免多生产者往同一无缓冲通道发数据;改用带缓冲通道 + 显式错误处理
- 用
select+default防止阻塞:如果通道满,可暂存到本地 slice 或打日志告警,而非 panic - 别用
range遍历通道后不关它——下游 goroutine 退出前必须close(),否则上游可能永远等不到 EOF
bufio.Scanner 读大文件时内存爆掉或漏行
bufio.Scanner 默认每行上限 64KB,超长日志行(如带堆栈、base64 内容)直接触发 scanner.Err() == bufio.ErrTooLong,且默认跳过该行——你根本不知道丢了啥。更隐蔽的是,它内部用 bytes.Buffer 累积数据,若日志行极长或扫描器未及时消费,内存持续上涨。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 初始化时显式调大限制:
scanner.Buffer(make([]byte, 64*1024), 10*1024*1024)(首参数初始 buffer,次参数最大容量) - 每次
scanner.Scan()后立刻用scanner.Bytes()复制数据,别长期持有scanner.Text()返回的字符串引用(底层仍指向 buffer) - 对超长行做兜底处理:检查
scanner.Err(),若为ErrTooLong,改用bufio.Reader.ReadLine()手动读取,自行处理截断或拼接 - 不要让 Scanner 跨 goroutine 共享;每个 goroutine 应独占一个 Scanner 实例
ETL 各阶段并发控制失衡导致 OOM 或上下文取消失效
常见写法是「一股脑启几十个 goroutine 跑解析」+「另一个 goroutine 跑写入」,但没控速。结果解析快、写入慢,中间通道积压百万条 *LogEntry,每条含 JSON 字段,轻松吃光几 GB 内存。更糟的是,用 context.WithTimeout 启动整个流程,但某个 goroutine 因 channel 阻塞无法响应 ctx.Done(),导致超时后程序还不退出。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
errgroup.Group替代裸sync.WaitGroup,天然支持 context 取消传播 - 解析阶段用固定 worker 数(如
runtime.NumCPU()),而非为每行启 goroutine - 写入阶段加限流:用
time.Ticker或golang.org/x/time/rate.Limiter控制 QPS,避免打爆下游 - 所有通道操作都放在
select中监听ctx.Done(),例如:select { case out
JSON 解析与结构体字段映射踩坑:空值、嵌套、时间格式
日志字段动态性强,json.Unmarshal 对缺失字段默认赋零值,但业务常需区分「字段不存在」和「字段为 null」;嵌套 JSON(如 extra 字段是任意 JSON)用 map[string]interface{} 解析后类型断言易 panic;时间字段格式五花八门(RFC3339、Unix、毫秒字符串),time.Time 直接 unmarshal 常失败。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用指针字段接收可选字段:
UserID *int64 `json:"user_id"`,这样能通过nil判断是否缺失 - 对不确定结构的字段,定义为
json.RawMessage,延迟解析或透传,避免提前解成 map 导致性能损耗和类型混乱 - 时间字段统一用自定义类型实现
UnmarshalJSON方法,兼容多种格式(示例中可先尝试time.Parse(time.RFC3339, s),失败再试strconv.ParseInt) - 别依赖
json:",omitempty"控制输出——ETL 中「字段存在但为空」和「字段不存在」语义不同,需按业务逻辑显式判断
真正难的不是写通流程,而是当单机每秒处理 5 万行日志时,哪一环的 GC 峰值、channel 缓冲水位、context 取消延迟,会成为压垮吞吐的最后一根稻草。这些点不会报错,只会让 P99 延迟悄悄爬升——得靠 pprof 和实时 metrics 接进去看,而不是靠日志猜。










