不能靠go+chan实现分布式任务分发,因channel仅限单进程内存、goroutine不跨网络;必须引入redis/etcd等外部协调机制,否则是伪分布式。

不能靠 go + chan 实现分布式任务分发,这是最常踩的坑。本地 channel 只在单进程内存里有效,跨机器完全不可见;goroutine 不跨网络,更不会自动注册到其他节点。所谓“分布式”,必须引入外部协调机制——哪怕只是轻量级的 Redis 或 etcd,否则就是伪分布式。
用 Redis 实现 FIFO 任务队列(最简可行路径)
Redis 是多数团队已有、无需额外部署服务发现组件的最低门槛选择。关键不是“存任务”,而是保证原子领取和失败回退。
-
LPUSH写入待处理任务,RPOPLPUSH原子性地把任务从pending移到processing队列——这是防止重复消费的核心操作 - worker 启动后先
BRPOPLPUSH pending processing 0,阻塞等待任务;成功拿到即视为“已领取”,无需再加锁 - 执行失败时,用
LREM processing 0 <task_json></task_json>清理异常残留,并LPUSH pending <task_json></task_json>回退;别直接删,否则重试逻辑无法触发 - 超时未完成的任务需单独巡检:定期
LRANGE processing 0 -1扫描,对超过预期耗时(如 3×平均执行时间)的条目手动挪回pending
用 etcd 实现选主 + 任务广播(适合调度器高可用)
如果你只有一个调度节点发任务,但希望它挂了能自动切走,etcd 的 lease + watch 就比 Redis 更可靠——它天然支持事件驱动和版本控制。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
- 所有调度实例竞争写
/scheduler/leader,带 10 秒 lease:client.Put(ctx, "/scheduler/leader", "node-123", clientv3.WithLease(leaseID)) - 写入成功者才真正开始分发任务;其余节点监听该 key 变更:
client.Watch(ctx, "/scheduler/leader", clientv3.WithPrevKV()) - watch 到
prevKV为空,说明是首次创建;prevKV非空且Value不等于自己 ID,说明被别人抢走,立刻退出当前调度循环 - lease 续期必须独立 goroutine 运行,并监听
lease.KeepAlive()返回的 channel 关闭信号,避免续租失败后还继续发任务
文件系统方案只适用于离线或降级场景
别把它当正经分布式方案用。NFS 或本地共享盘上用文件分发,本质是妥协——仅当你连 Redis 都跑不了(比如边缘设备、Air-Gapped 环境),且能接受一定重复或丢失时才考虑。
- 唯一安全的“领取”动作是
os.Rename("pending/task_123.json", "claimed/task_123.json"),必须保证源和目标在同一文件系统 - 领取后立即生成
claimed/task_123.json.lock文件,执行完再删 lock、再os.Rename到done/;否则崩溃会导致任务卡死 - 启动时必须扫描
claimed/目录下所有.lock文件,检查对应done/是否已有结果;若无且 lock 文件修改时间 >5 分钟,才挪回pending/ - 没有原子删除、没有跨节点通知、没有状态反馈——你得自己补全所有容错逻辑,成本远高于接入一个 Redis 实例
真正的难点不在代码怎么写,而在于谁来担保任务不丢、不重、不卡死。Redis 提供原子队列,etcd 提供强一致选主,文件系统什么也不担保——你得用一堆临时文件、锁标记、定时巡检去模拟,最后发现维护成本比搭个 Redis 还高。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










