结论:高延迟通常卡在 Redis 延迟、数据库慢查询、上下文超时配置不当这三处,而非任务逻辑本身;需分别排查 Redis 连接池与 p99 延迟、SQL 缺索引/N+1 问题、context 超时未传递或设置不合理。

先说结论:高延迟通常卡在 Redis 延迟、数据库慢查询、上下文超时配置不当这三处,而不是任务逻辑本身。
Redis 延迟是否异常升高
很多团队默认认为“Redis 很快”,但 ZPOPMIN 或 LPUSH/RPOP 在高负载下可能因连接池打满、网络抖动或单点故障导致 RT 突增。别只看平均值——用 redis-cli --latency 实时测,重点观察 p99 是否超过 50ms;若使用 github.com/redis/go-redis/v9,检查 client.Options.PoolSize 是否小于并发 worker 数(比如 10 个 worker 却只配了 5 的 pool size,必然排队);redis-cli info commandstats 中查看 cmdstat_zpopmin:calls= 后的 usec_per_call,若持续 >10000(即 10ms),就得查 Lua 脚本是否阻塞或 zset 规模过大。
数据库查询是否触发 N+1 或缺索引
异步任务常带状态更新(如 HSET delay_processing task_id worker_1),但真正耗时的往往是后续 DB 操作。用 EXPLAIN 对照任务中执行的 SQL:如果出现 type=ALL 或 rows 过万,基本就是缺索引;循环里调 db.QueryRowContext 是典型 N+1,应改用 IN 批量查或预加载;database/sql 的 SetConnMaxLifetime 若设为 0(无限期复用),可能让 stale connection 拖慢整个池——建议设为 30m 左右。
context.WithTimeout 是否被忽略或设得太宽
任务函数里没传 context?或者用了 context.Background()?这是最隐蔽的延迟源。必须确保每个外部调用(HTTP、DB、Redis)都绑定同一 context:比如 db.QueryRowContext(ctx, ...)、client.ZPopMin(ctx, ...);超时值不能拍脑袋定——10 秒看似安全,但若下游服务常态响应 8 秒,那你的任务 p95 就卡在 8~10 秒之间;更合理的是按历史 p90 设(比如 3 秒),再加退避重试;注意 context.WithTimeout 的 cancel 必须 defer 调用,否则泄漏 goroutine。
立即学习“go语言免费学习笔记(深入)”;
Worker Pool 是否过载或 channel 阻塞
用 chan Task 实现队列时,缓冲区太小(如 make(chan Task, 1))会导致生产者阻塞;worker 数固定但任务突发增长,会积压在 channel 里——此时 len(taskChan) 持续接近 cap 就是信号;Go runtime 的 runtime.ReadMemStats 中 NumGoroutine 若长期 >500,大概率是 worker 卡住没 return;别在 worker 里直接 time.Sleep 模拟重试,该用 select { case 配合 context Done。


















