go语言无原生分布式调度能力,必须用asynq(redis轻量)或temporal(postgresql重型)等专用库实现跨节点、防重、一致性;二者均要求任务幂等且依赖存储可靠性配置。

Go 语言本身不提供分布式任务调度能力,time.Ticker 和 time.AfterFunc 只能做单机定时;真要跨节点、防重复、保一致,必须引入外部协调机制或专用库。
为什么不能用 goroutine + time.Sleep 模拟分布式调度
这种写法在本地跑得通,但一上生产就出问题:
-
goroutine是进程内资源,节点宕机后所有未执行任务直接丢失,无持久化、无恢复 - 多个实例同时触发同一任务(比如发两次短信、扣两次库存),因为没全局锁或唯一分发机制
- 各节点
time.Now()存在毫秒级偏差,导致“同一时刻”触发不一致,尤其对秒级精度任务影响明显 - 无法动态扩缩容:加新 worker 节点需手动改配置、重启服务,等于停服
用 Asynq 做轻量级 Redis 后端调度
适合中小规模、任务逻辑较短、要求快速落地的场景。关键三步缺一不可:
- 定义任务:
asynq.NewTask("send_email", map[string]interface{}{"to": "a@b.com"}) - 注册处理器:
mux.HandleFunc("send_email", sendEmailHandler),必须在asynq.NewServer启动前完成 - 分发任务时显式指定延迟或时间:
asynq.ProcessIn(5 * time.Minute)或asynq.ScheduleAt(time.Now().Add(10 * time.Second)),否则默认立即入队 -
asynq.RedisClientOpt的DB参数必须和业务 Redis DB 隔离,避免 key 冲突或误清空
用 Temporal 做重型 PostgreSQL 后端调度
适合长时运行、需子工作流、信号中断、历史追溯、审计合规的场景(如支付对账、审批流):
- Temporal 自带服务端,推荐用 Docker 启动,不要手搭;客户端通过 gRPC 连接,不是直连数据库
- 任务函数必须幂等——网络分区或 worker crash 可能导致同个
taskID被多次投递 - PostgreSQL 方案中,
pg_cron扩展仅用于定时触发 Go 服务的 HTTP 接口,不能直接在数据库里写业务逻辑 - 所有 payload 序列化建议用
json.Marshal,别用gob,否则 Go 版本升级后反序列化失败风险高
自建调度系统时最容易被忽略的可靠性细节
很多人卡在“能跑通”,但上线后掉链子,核心是低估了存储层的脆弱性:
- Redis 必须开启
AOF + fsync everysec,禁用maxmemory-policy volatile-lru,否则内存满时任务被随机丢弃 - 若用 etcd/nats/consul 做注册中心或消息分发,健康检查间隔不能设太短(如
- 任务状态更新必须和执行原子绑定:推荐用“先写 DB 状态为 running,再执行,最后更新为 success/fail”,避免状态滞后于实际执行
- 所有外部依赖(Redis、PostgreSQL、NATS)都要有连接池超时、重试、降级兜底逻辑,不能让一个依赖故障拖垮整个调度链路











