3. 节点定义与属性:Node 结构体详解
在 GoDAG 调度系统中,节点(Node) 是构成有向无环图(DAG)的最小执行单元。每个节点封装了调度引擎所需的所有信息:唯一标识、执行逻辑、依赖关系以及运行时元数据。本章将深入解析 Node 结构体的设计,重点讲解节点 ID、名称、元数据(Metadata)的存储与读取机制。
3.1 Node 结构体核心定义
以下代码展示了 GoDAG 中 Node 结构体的标准定义。该结构体采用“字段 + 方法”的封装模式,确保属性安全访问的同时,提供灵活的扩展能力。
// Node 定义 DAG 中的基本执行单元
type Node struct {
// 核心标识字段(不可变)
ID string // 全局唯一节点 ID,由 DAG 构建时生成
Name string // 节点可读名称,用于日志与监控
// 元数据存储(可变,线程安全)
metadata sync.Map // 存储任意类型的键值对
// 内部状态(不对外暴露)
status NodeStatus // 当前执行状态
dependents []*Node // 下游依赖节点列表
depCount int32 // 尚未就绪的上游依赖计数
runFunc func(ctx context.Context) error // 节点执行函数
}
// NodeStatus 节点生命周期状态枚举
type NodeStatus int32
const (
StatusPending NodeStatus = iota // 等待调度
StatusReady // 依赖已满足,可执行
StatusRunning // 正在执行
StatusSuccess // 执行成功
StatusFailed // 执行失败
StatusSkipped // 被跳过(条件不满足)
)
设计要点: 使用
sync.Map 存储元数据而非普通 map,是为了支持并发场景下多个 goroutine 同时读写节点属性(如监控探针、动态参数注入),避免加锁带来的性能开销。
3.2 节点 ID 与名称:唯一性与可读性
| 属性 | 类型 | 约束 | 用途 |
|---|---|---|---|
| ID | string |
全局唯一,不可变,构建时生成 | DAG 内部寻址、依赖关系解析、状态追踪 |
| Name | string |
同一 DAG 内建议唯一,允许重复 | 日志输出、监控面板、人工排查 |
ID 生成策略: 推荐使用 UUID v4 或 Snowflake 算法,确保分布式环境下不冲突。Name 则通常由用户定义,如 "数据清洗"、"模型训练",便于阅读。
// NewNode 创建节点实例,自动生成 UUID 作为 ID
func NewNode(name string, fn func(ctx context.Context) error) *Node {
return &Node{
ID: uuid.New().String(),
Name: name,
metadata: sync.Map{},
status: StatusPending,
runFunc: fn,
}
}
3.3 元数据(Metadata)的存储与读取
元数据是节点属性的“扩展槽”,用于存储调度引擎或用户自定义的附加信息,例如:超时时间、重试次数、环境变量、标签、输入输出参数等。GoDAG 通过 sync.Map 提供线程安全的读写接口。
3.3.1 核心操作方法
// SetMetadata 存储键值对,key 必须为非空字符串
func (n *Node) SetMetadata(key string, value interface{}) {
if key == "" {
return // 拒绝空键
}
n.metadata.Store(key, value)
}
// GetMetadata 读取指定键的值,若不存在返回 nil
func (n *Node) GetMetadata(key string) interface{} {
val, _ := n.metadata.Load(key)
return val
}
// GetMetadataAs 泛型读取,自动类型断言,避免调用方重复转换
func GetMetadataAs[T any](n *Node, key string) (T, bool) {
val := n.GetMetadata(key)
if val == nil {
var zero T
return zero, false
}
result, ok := val.(T)
return result, ok
}
// DeleteMetadata 删除指定键
func (n *Node) DeleteMetadata(key string) {
n.metadata.Delete(key)
}
// ListMetadataKeys 列出所有元数据键名(用于调试或监控)
func (n *Node) ListMetadataKeys() []string {
var keys []string
n.metadata.Range(func(key, value interface{}) bool {
keys = append(keys, key.(string))
return true
})
return keys
}
最佳实践: 使用
GetMetadataAs[T] 泛型函数可以避免每次读取后手动类型断言,减少代码重复。例如:timeout, ok := GetMetadataAs[time.Duration](node, "timeout")。
3.3.2 常见元数据键约定
GoDAG 调度引擎预定义了一些标准元数据键,用户也可自由扩展:
| 键名 | 值类型 | 说明 |
|---|---|---|
"timeout" |
time.Duration |
节点执行超时时间,超时后自动标记失败 |
"retry" |
int |
失败重试次数,默认 0 表示不重试 |
"tags" |
[]string |
标签列表,用于分组调度或条件路由 |
"env" |
map[string]string |
环境变量,注入到执行上下文中 |
"description" |
string |
节点功能描述,用于文档生成 |
3.4 节点属性结构图
下图展示了 Node 结构体的完整属性布局及其在 DAG 中的角色:
3.5 元数据读取性能对比
为了帮助开发者选择最合适的读取方式,下表对比了三种元数据访问模式的适用场景:
| 读取方式 | 代码示例 | 类型安全 | 性能 | 推荐场景 |
|---|---|---|---|---|
直接 GetMetadata |
val := node.GetMetadata("timeout") |
否(需手动断言) | 高 | 快速原型、类型已知的临时读取 |
泛型 GetMetadataAs[T] |
t, ok := GetMetadataAs[time.Duration](node, "timeout") |
是 | 中(含类型断言开销) | 生产代码,推荐使用 |
Range 遍历 |
node.metadata.Range(func(k, v any) bool { ... }) |
否 | 低(全量遍历) | 调试、监控、导出所有元数据 |
3.6 节点定义完整示例
以下示例展示了如何创建一个带有元数据的节点,并在执行函数中读取配置:
// 创建节点
cleanNode := NewNode("数据清洗", func(ctx context.Context) error {
// 从元数据读取超时配置
timeout, ok := GetMetadataAs[time.Duration](ctx.Value("node").(*Node), "timeout")
if ok {
// 应用超时逻辑
}
// 读取环境变量
env, _ := GetMetadataAs[map[string]string](ctx.Value("node").(*Node), "env")
fmt.Println("运行模式:", env["MODE"])
// 执行实际业务逻辑
return cleanData()
})
// 设置元数据
cleanNode.SetMetadata("timeout", 30*time.Second)
cleanNode.SetMetadata("retry", 3)
cleanNode.SetMetadata("env", map[string]string{"MODE": "prod"})
cleanNode.SetMetadata("tags", []string{"critical", "data-pipeline"})
注意: 元数据的 key 建议使用常量定义,避免字符串硬编码。例如:
const MetaKeyTimeout = "timeout"。同时,避免在元数据中存储大量数据(如超过 1MB),否则会影响 sync.Map 的遍历性能。
3.7 本章小结
- Node 结构体 由不可变字段(ID、Name)和可变字段(status、metadata)组成,兼顾安全性与灵活性。
- ID 保证全局唯一性,Name 提供可读性,两者共同构成节点的身份标识。
- Metadata 基于
sync.Map实现,支持并发安全的键值存储,是节点扩展属性的核心机制。 - 推荐使用 泛型读取方法
GetMetadataAs[T]提升代码的类型安全性和可维护性。