rpoplpush比brpop更可靠,因其原子性地将任务从pending队列移至processing队列,避免worker崩溃导致任务丢失;需配合超时监控与回滚机制,并注意其无阻塞特性需轮询或兜底。

Redis队列选RPOPLPUSH而不是BRPOP的原因
直接用BRPOP拉取任务,节点崩溃时任务就丢了——这是最常踩的坑。而RPOPLPUSH把任务从tasks:pending原子性移到tasks:processing,即使Worker中途挂掉,任务还在processing队列里,可被其他节点扫描重试。
实际使用中要注意三点:
-
RPOPLPUSH不支持超时阻塞,得自己加time.Sleep轮询或配合BLPOP做兜底 - processing队列必须定期清理:超时未确认的任务要回滚到pending,否则堆积
- 别用
LPUSH+BRPOP组合——看似简单,但网络中断时BRPOP返回nil,任务已出队却没执行,彻底丢失
Etcd注册Worker时为什么必须带TTL心跳
没TTL的心跳等于没心跳。Worker往Etcd写/workers/worker-01后,若不设lease,节点宕机后这个key永远存在,Master会误判它还在线,导致任务分发失败。
正确做法是:
- 创建lease时指定TTL(比如30秒),写key时绑定lease ID
- Worker在后台goroutine里每10秒调一次
KeepAlive续期 - Master端监听
/workers/前缀,收到Delete事件立刻触发任务迁移
漏掉KeepAlive或lease过期未捕获,就会出现“僵尸Worker”——界面显示在线,实际已失联。
context.WithTimeout必须包裹整个任务执行链
只给HTTP请求套timeout没用。任务可能卡在数据库查询、外部API调用或死循环里,必须从入口就控制生命周期。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
典型错误写法:http.TimeoutHandler只包handler,不包task.Run();正确结构是:
ctx, cancel := context.WithTimeout(context.Background(), task.Timeout) defer cancel() result, err := task.Execute(ctx) // Execute内部所有IO操作都接收并传递ctx
关键点:
- 所有阻塞操作(
http.Client.Do、db.Query、redis.Conn.Do)必须接收ctx - cancel()要在defer里调,避免goroutine泄漏
- timeout值不能硬编码,得从Task结构体里读取,否则无法按任务粒度定制
任务状态更新为何不能只靠Redis写入
Redis快但不保证最终一致——比如Master写task:123状态为success,Worker同时上报失败,谁赢取决于写入顺序。生产环境必须引入单点状态源。
推荐方案是混合存储:
- Redis存实时状态(用于Dashboard快速展示),但不作为唯一依据
- PostgreSQL存权威状态(带
UPDATE ... WHERE status = 'running'条件更新,防重复提交) - Worker上报时先更新DB,再发Redis Pub/Sub通知Master刷新缓存
纯Redis方案在并发上报场景下,10%以上概率出现状态错乱,尤其重试任务多的时候。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










