生产环境推荐用 redis stream 而非 lpush/brpop,因其支持消息持久化、消费者组、ack 确认和消息回溯;lpush/brpop 易丢消息、无重试机制,仅适用于幂等性强、延迟敏感的轻量场景。

直接用 go-redis 手写 LPUSH/BRPOP 是可行的,但生产环境里容易漏掉重试、幂等、超时清理、死信处理这些关键环节——不是框架不能用,而是“裸用 Redis 命令”本身不等于“可靠队列”。
为什么不用纯 channel 而必须走 Redis
Go 的 chan 是内存级、无持久化的通信机制。服务重启、OOM 或 Worker 进程崩溃时,未消费的任务直接消失。文件上传这类场景要求“不丢文件”,就必须把任务元数据(ID、路径、重试次数)落盘。Redis 提供原子操作、TTL、Sorted Set 支持延迟重试,且天然支持多实例 Worker 并发消费。
-
chan适合短生命周期协程协作(如 HTTP 请求上下文传递) - Redis List / Sorted Set 是跨进程、跨机器、可恢复的任务载体
- 哪怕只部署单台 Worker,也建议用 Redis 而非
chan,否则上线后扩容就卡住
选 TaskQ 还是手撸 redis/v8 + lua
TaskQ 封装了重试策略、分布式锁、批量拉取逻辑,适合中大型项目快速落地;但如果你需要精确控制消费节奏、自定义失败归档路径或对接内部监控系统,直接基于 github.com/go-redis/redis/v8 写更灵活。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- TaskQ 的
RetryPolicy依赖consumer_config.go配置,指数退避参数需结合业务失败率调优 - 手写方案推荐用 Lua 脚本做
ZPOPMIN+ZADD原子迁移,避免消息被多个 Worker 重复消费 - TaskQ 默认用 Redis List,不支持原生延迟队列;若需定时触发(如 5 分钟后校验文件完整性),得切到 Sorted Set + 定时轮询
消费者必须实现的三个硬性逻辑
无论用哪个库,Worker 启动后必须显式处理这三件事,否则队列会逐渐堆积或丢失状态:
- 消费前先用
GET检查任务对应 NFS 文件是否存在,不存在则ZADD到死信队列并打告警日志 - 上传 S3 成功后,必须用
DEL清理 NFS 临时文件,且该操作要和 Redis 状态更新放在同一事务(用 Lua) - 每个任务处理必须带
context.WithTimeout,超时未返回 ACK 就触发自动重入;MaxConsumeDuration建议设为预期耗时的 2–3 倍
最常踩的坑:NFS + Redis 的时序错乱
Server 写完 NFS 文件后立即 LPUSH 入队,但 Worker 可能因网络延迟或调度滞后,在文件还没完全刷盘时就读取,导致 os.Open 报 no such file。这不是代码 bug,而是分布式系统固有竞态。
- Server 端写 NFS 后加
syscall.Sync强制刷盘(仅限 ext4/xfs),再入队 - Worker 端首次读取失败时,不要直接 nack,先
time.Sleep(100 * time.Millisecond)后重试 2 次 - 禁止在 Worker 中用
os.Stat判断文件存在性——它不保证内容完整;改用os.Open+io.ReadFull读前 16 字节校验
真正难的不是把任务塞进 Redis,而是让每条消息在失败、重试、并发、网络分区下都保持状态可追溯。所有“可靠”设计,本质都是对边界条件的穷举和防御。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










