因为文件系统天然支持版本控制、diff查看、git回滚和人工审计,可追溯性远超数据库日志;但需避免高频i/o解析,应内存加载+fsnotify监听+rwmutex保护+原子指针双缓冲切换。

为什么不用数据库或 Redis 存依赖关系
文件系统天然支持版本控制、diff 查看、Git 回滚和人工审计,对中小团队的任务流变更可追溯性远超数据库写操作日志。但要注意:不能直接用 os.OpenFile 每次读取 YAML 就解析一次——高频调度下 I/O 和反序列化开销会成为瓶颈;也不能把整个 DAG 结构体暴露给外部修改,否则并发 AddEdge 时可能破坏图一致性。
- 推荐做法:启动时一次性加载文件到内存,后续只监听文件
fsnotify变更事件,触发全量 reload(不是增量 patch) - 必须加
sync.RWMutex:读多写少场景下,GetNode、TopologicalSort等只读操作用RLock,reload 时用Lock - 避免在 reload 过程中执行任务:用原子布尔
atomic.Bool标记isReloading,worker 在取任务前检查该标志
YAML 文件怎么定义 DAG 依赖
别用嵌套 map 或自由字段,容易漏检环、难做静态校验。一个最小可行的结构应包含 name、depends_on(字符串数组)、command(或 script 路径)、timeout 四个字段。例如:
tasks: - name: "fetch_data" command: "./bin/fetch.sh" timeout: "30s" - name: "clean_data" depends_on: ["fetch_data"] command: "./bin/clean.py" - name: "report" depends_on: ["clean_data", "send_email"] command: "./bin/report.go" - name: "send_email" command: "./bin/email.go"
-
depends_on中的 name 必须是同一文件内已声明的 task 名,缺失则 reload 失败并 log 报错,不静默忽略 - 禁止循环引用:reload 时必须跑 DFS 三色标记检测,发现 gray→gray 边立即返回 error 并拒绝加载
- 不支持通配符或正则匹配(如
"task-*"),所有依赖必须显式写出,否则拓扑排序无法保证确定性
如何安全执行 task 命令而不阻塞调度器
每个 task 执行必须隔离 goroutine、context 和 panic 恢复,否则一个崩溃的 ./bin/fetch.sh 会 kill 整个调度循环。不能直接 exec.Command().Run(),得包裹三层:
- 第一层:
ctx, cancel := context.WithTimeout(parentCtx, task.Timeout),超时后主动 kill 子进程 - 第二层:
defer func() { recover() }(),捕获panic并转为node.Err = fmt.Errorf("panic: %v", r) - 第三层:用
errgroup.Group管理子 goroutine,比如 task 内部起 http server 时,需确保eg.Go(func() error {...})与 cancel 同步退出
特别注意:command 字段若指向 Go 二进制,应传入 -logtostderr 等参数,避免 stdout/stderr 缓冲区满导致阻塞;若为 shell 脚本,务必以 #!/usr/bin/env bash 开头并设 set -euo pipefail。
文件变更后如何平滑切换新 DAG
不能删掉旧图再 load 新图——中间存在窗口期,正在运行的 task 可能找不到 parent node。正确做法是双缓冲 + 原子指针替换:
- 定义全局变量
var currentDAG atomic.Value,类型为*DAG - reload 成功后,构造新
*DAG实例,完整验证(环检测、节点可达性、timeout 合法性)通过,再调用currentDAG.Store(newDAG) - worker 取任务时,先
dag := currentDAG.Load().(*DAG),再调用dag.GetReadyNodes(),全程无锁 - 旧 DAG 实例会在所有正在执行的 task 完成后自然被 GC,无需手动清理
这个设计下,reload 不影响正在跑的 task,也不会丢任务;但要注意:新 DAG 中同名 task 的 timeout 或 command 变更,只对后续触发生效,已在执行的实例不受影响——这恰恰是预期行为。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











