data-platform-open 流程引擎 FlowEngine 源码剖析:流程调度、组件生命周期与心跳容错原理

  • 后端
  • 大数据
  • 数据集成
  • ETL
  • 数据工程
  • 任务调度
  • AI 应用

【免费下载链接】data-platform-open

🔥🔥🔥可视化拖拽式大数据集成平台、大数据平台、大数据,包含数据流、数据源、数据对齐、查询模板、完善的监控等。像画流程图一样且无代码方式同步、清洗数据。

项目地址: https://gitcode.com/shaiwz/data-platform-open
点击查看 免费下载

本文带你完整剖析 data-platform-open(可视化拖拽式大数据集成平台)的流程引擎 FlowEngine 源码,看懂它是如何做流程调度、管理组件生命周期、并通过三层心跳机制实现故障容错的。读完你会明白:一个"拖拽出来的数据流",在服务重启、节点宕机、假死等各种异常场景下,为什么依然能自动恢复、接力运行。

一、先认识主角:FlowEngine 在哪里

FlowEngine 是整个数据流子系统的"发动机",核心代码集中在 flow 模块:

整体设计可以概括为一句话:FlowEngine 负责"单机内存里的流程注册表 + 心跳上报",DataFlowDispatchServiceImpl 负责"集群维度的调度决策",两者通过 Redis 和 RabbitMQ 协同。

FlowEngine 内部用一个两级并发 Map 保存所有流程,键是"工作空间 → 流程编码 → Flow 实例"(见 FlowEngine.java#L81),并提供 add / get / remove / start / stop 这一组生命周期操作。

二、流程调度:Leader 选举与三种运行策略

1. 谁来决定"数据流跑在哪台机器上"?

多实例部署时,不能让每台机器都去调度(会重复启动),所以平台采用Redisson 分布式锁做 Leader 选举:每个实例启动后开一个独立的调度线程,循环尝试获取 FLOW_DISPATCH_LEADER_LOCK,谁拿到锁谁就是"调度主",没拿到的每 5 秒重试一次(见 DataFlowDispatchServiceImpl.java#L122-L183)。

这个设计有几个值得学习的细节:

  • 每次调度前都复检锁是否仍被自己持有。如果 Leader 因 Redis 故障续期失败,它会在下一轮循环让出调度权,避免"双主";
  • 调度间隔默认 10 秒(最小 5 秒),通过配置项 dp.flow.dispatch.interval 调整;
  • 资源保护:候选实例的 CPU 或内存使用率超过阈值(默认 90%,dp.flow.dispatch.resource-threshold)时不会被选中,防止把新任务压到快崩溃的节点上,同时会触发 WARNING 告警并在恢复后自动清除。

2. 三种运行策略:决定"要跑几个实例"

调度主拿到锁后,对每一条数据流调用 planInstanceDispatch 做规划(见 DataFlowDispatchServiceImpl.java#L320-L444)。运行策略由枚举 DataFlowRunStrategy.java 定义:

策略含义调度行为
ALL_INSTANCES全部实例健康实例数 < 可用节点数时,把缺的节点全部补上
SPECIFY_INSTANCES指定实例只把指定的实例拉起;指定实例不在线时告警
FIXED_INSTANCE_NUMBER固定实例数按 CPU+内存综合占用率从低到高排序,把最空闲的机器补到位;实例数超规格时剔除最晚注册的节点

规划出"要启动 / 要停止"的实例列表后,通过 RabbitMQ 的 fanout 广播发送调度消息,各节点在 DataFlowDispatchMessageListener.java 中检查"自己是否在名单里",命中才执行 flowEngine.start 或 flowEngine.remove。

另外还有一道防抖锁:同一条数据流 3 分钟内已经调度过就直接跳过(FLOW_DISPATCH_LOCK),避免"启动中"的状态被反复打断。

三、组件生命周期:从拖拽画布到运行中的节点

1. 流程的"骨架":Flow 与 FlowComponent

一个 Flow 持有 Map<String, FlowComponent> flowComponents(画布上的每个节点)和一个 AtomicBoolean running 运行标记。节点之间通过"下一组节点"的双层列表连接:外层是优先级批次,内层是同一批次要并行执行的节点——数据按批次顺序流动,批次内并行执行(见 FlowComponent.java#L62-L101)。

数据在节点间通过 Transmit.java 传递,内部装着一条 Record(普通单条、批量、流式或空记录)和一个 StopWatch 计时器。

2. 启动:按组件类型分而治之

FlowEngine.start 的入口逻辑非常克制(见 FlowEngine.java#L300-L336):先用 running 原子标记做幂等保护(已在运行直接返回),然后只启动两类"主动型"组件:

  • JobFlowComponent(定时任务):底层是 Quartz 的 Cron 触发器,每次触发克隆一个新的 Context。多个节点同时触发时靠 Redisson 锁 FLOW_JOB_LOCK 保证同一时刻只有一个实例真正执行(见 JobFlowComponent.java#L205-L286),还支持"放弃本次 / 中断上次"两种阻塞策略。
  • DebeziumFlowComponent(数据库变更监听):单例执行,多节点同时收到启动消息时会先抢 FLOW_DEBEZIUM_LOCK,抢到的节点再通过 canAssumeDuty 检查 Redis 中的"当前运行实例 + 心跳",只有原节点假死才会发送 STOP 消息强制接管,避免"两个节点都在跑"。

如果启动过程中某个组件失败(比如 MQ 连接超时),FlowEngine 会兜底调用 stop 把已启动的部分全部终止,再抛出异常——保证不会留下"半启动"的脏状态。

3. 停止:有讲究的顺序

FlowEngine.stop 使用并行流关闭组件,但有明确的两阶段顺序:先停 Debezium / Job 这类"生产者"组件(它们停止后不再产出数据),再停其余处理与写入组件,最后把 running 置为 false(见 FlowEngine.java#L402-L456)。

remove 更严格:因为 Debezium 汇聚管道刷最后一批数据可能很慢,停止动作被丢进专用线程池 flowStopExecutor 异步执行,主流程最多等待 6 分钟,并提前把自己从 Redis 心跳表中摘除,防止调度器误判它还在运行。

四、心跳容错:三层心跳如何守住数据流

这是整个引擎最有价值的设计。平台一共维护了三层心跳,层层递进地回答"谁还活着":

第 1 层:服务实例心跳(我还在吗?)

每个节点的 ServerManager.java 每 10 秒向 Redis 的 RMapCache(SERVERS 键)写入一次实例信息:IP、端口、启动时间、CPU/内存使用率,缓存 TTL 为一周。优雅停机时把状态标记为 OFFLINE 并发出"服务下线"告警。调度器正是通过查询这张表拿到"可用实例列表"。

第 2 层:数据流心跳(这条流还健康吗?)

FlowEngine 启动时注册一个单线程定时任务,每 10 秒遍历本机所有运行中的流程,把 FlowHeartbeat(首次注册 + 最近心跳时间)写进 Redis 的 FLOW_HEARTBEAT 键(见 FlowEngine.java#L213-L270)。

关键判定在 FlowHeartbeat.java#L46-L48:最近心跳在 30 秒内才算健康。调度器规划实例时只统计健康心跳——某节点宕机后最多 30 秒就会被判定"死亡",调度器随即把这条数据流派给其他节点,这就是"节点挂了自动接力"的基础。

第 3 层:组件心跳(监听器假死怎么办?)

进程可能还活着,但 Debezium 监听线程已经假死了。为此 DebeziumFlowComponent 在真正跑起来后额外注册一个10 秒一 tick、TTL 120 秒的 Redis 心跳(见 DebeziumFlowComponent.java#L415-L433)。

调度主在每轮调度中还会执行 detectionComponentOnly 守护逻辑:若某条数据流没有新的启停计划,就检查其 Debezium 组件——运行记录存在但心跳消失,说明假死,随即发出 RESTART 组件消息触发重启(3 分钟防抖);运行记录都不存在,则发出 START 消息拉起。至此,"假死检测 → 消息通知 → 新节点接管"的闭环完成。

五、透明化监控与自动重试:CGLib 代理的小巧思

在 Flow.addFlowComponent 中,每个组件被注册时都会套一层 CGLib 动态代理(见 Flow.java#L105-L116)。代理拦截器 FlowComponentProxy.java 只做两件事,对业务代码零侵入:

  1. 埋点:拦截 run 方法,通过 FlowComponentMonitor.java 用 Micrometer 记录每个组件的处理条数(Counter)、执行耗时(Timer)与异常次数,数据最终流入 Prometheus 做看板展示;
  2. 重试:组件类上若标注了 @Retryable(见 Retryable.java),代理会用 Spring Retry 的 RetryTemplate 包裹执行,默认最多 3 次、指数退避(初始 1 秒、倍数 3、上限 60 秒),轻松扛住瞬时网络抖动。

运行期产生的异常则统一由 FlowMonitor.java 收口:写入 Redis 错误列表(只保留最近 5 条)并可选择触发告警场景,恢复后还会自动清理对应类型的历史告警。

六、总结:这套设计好在哪

🔹 职责分层清晰:FlowEngine 只管本机内存与心跳,调度决策全部交给集群 Leader,单机逻辑因此非常薄;

🔹 心跳即状态:三层心跳(服务 10s → 流程 30s 阈值 → 组件 120s TTL)让"谁活着、谁假死"变成 Redis 里的客观事实,任何节点崩溃都能被自动发现与接管;

🔹 锁 + 消息防冲突:调度锁、防抖锁、组件执行锁配合 RabbitMQ 广播,天然规避重复启动、双主调度等分布式经典问题;

🔹 监控与重试无感接入:CGLib 代理 + Micrometer 让每个组件天然可观测,@Retryable 注解即可获得容错重试。

如果你正在自研数据集成平台或流任务调度系统,这套"注册表 + 心跳 + Leader 调度 + 代理埋点"的组合拳非常值得参考。下一篇可以继续深挖 Debezium 保存点与偏移量持久化(MySQLOffsetBackingStore / MySQLSchemaHistory)是如何实现断点续传的,欢迎持续关注。

  • 后端
  • 大数据
  • 数据集成
  • ETL
  • 数据工程
  • 任务调度
  • AI 应用

【免费下载链接】data-platform-open

🔥🔥🔥可视化拖拽式大数据集成平台、大数据平台、大数据,包含数据流、数据源、数据对齐、查询模板、完善的监控等。像画流程图一样且无代码方式同步、清洗数据。

项目地址: https://gitcode.com/shaiwz/data-platform-open
点击查看 免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值