3. 架构原理:Dataset Trigger 在调度系统中的位置
讲 Dataset Trigger 之前,咱们得先搞清楚它在整个调度系统里到底站在哪。我见过不少团队,上来就写触发逻辑,结果跟现有调度框架打架,最后不得不重构。说白了,位置没摆对,后面全是坑。
3.1 Dataset Trigger 在调度系统中的层级
一个典型的大数据调度系统,从上到下大概分三层:
- 应用层:用户看到的 DAG 定义、任务配置、告警规则
- 调度引擎层:负责任务排队、资源分配、依赖解析、触发执行
- 数据层:元数据服务、事件总线、状态存储
Dataset Trigger 就横跨在调度引擎层和数据层之间。它不是调度引擎本身,而是调度引擎的“眼睛”和“耳朵”。
核心观点:Dataset Trigger 是一个数据驱动的事件监听器,它监听数据层的变化,然后通知调度引擎去触发相应的任务。
我习惯把它比作一个“哨兵”。哨兵不负责打仗,但负责发现敌人(数据就绪),然后吹号(触发任务)。
3.2 事件驱动架构基础
为什么 Dataset Trigger 要用事件驱动?你想想看,传统调度是轮询——每隔几分钟扫一遍数据库,看看有没有新数据。数据量小还行,数据量大了,轮询就是灾难。
我曾经维护过一个每天跑 10 万+ 任务的集群,轮询间隔设到 30 秒,数据库 CPU 直接飙到 80%。后来改成事件驱动,CPU 降到 15%。
事件驱动架构的核心就四个字:有变才动。
| 对比项 | 轮询模式 | 事件驱动模式 |
|---|---|---|
| 触发方式 | 定时扫描 | 数据变化时主动推送 |
| 资源消耗 | 高(空转浪费) | 低(按需触发) |
| 实时性 | 取决于轮询间隔 | 毫秒级响应 |
| 扩展性 | 数据库压力随任务量线性增长 | 事件总线可水平扩展 |
事件驱动架构里,有三个角色你必须记住:
- 事件生产者:谁产生数据变化?比如 Hive 表写入完成、HDFS 文件落盘、Kafka 分区更新。
- 事件总线:消息的通道,负责把事件从生产者传给消费者。常见实现有 Kafka、Pulsar、RabbitMQ。
- 事件消费者:谁处理事件?Dataset Trigger 就是消费者之一,它收到事件后判断是否满足触发条件。
个人经验:事件总线选型时,别只看吞吐量。我踩过一个坑——选了吞吐最高的消息队列,但它的消息顺序保证不够,导致 Dataset Trigger 收到乱序的事件,触发逻辑全乱套。后来换了支持分区有序的 Kafka,才稳住。
3.3 元数据服务与事件总线
这两个东西是 Dataset Trigger 的左右手。元数据服务告诉 Trigger“数据长什么样”,事件总线告诉 Trigger“数据什么时候变了”。
3.3.1 元数据服务
元数据服务存的是数据的“身份信息”。比如:
- 这个 Dataset 叫什么名字?
- 它存在哪个路径下?
- 它的分区结构是怎样的?
- 它依赖哪些上游 Dataset?
- 它当前的状态是什么(就绪/未就绪/过期)?
我建议把元数据服务设计成无状态的,方便水平扩展。存储层可以用 MySQL 或者 TiDB,但查询层一定要加缓存,不然高并发下元数据查询会成为瓶颈。
避坑指南:我曾经见过一个团队把元数据直接存在调度系统的业务库里,结果调度系统一升级,元数据也跟着迁移,数据丢了两次。元数据服务应该独立部署,跟调度引擎解耦。
3.3.2 事件总线
事件总线是 Dataset Trigger 的“神经”。它传递的事件消息,一般包含这些字段:
{
"event_id": "evt_20250321143001",
"event_type": "DATASET_AVAILABLE",
"dataset_name": "ods.order_daily",
"partition": "dt=2025-03-21",
"timestamp": 1742553001000,
"producer": "hive_etl_worker_01"
}
事件类型不止一种。我归纳过常见的几种:
| 事件类型 | 含义 | 触发动作 |
|---|---|---|
| DATASET_AVAILABLE | 数据集已就绪 | 触发下游任务 |
| DATASET_EXPIRED | 数据集过期 | 触发重跑或告警 |
| DATASET_UPDATED | 数据集被覆盖 | 触发下游增量更新 |
| DATASET_DELETED | 数据集被删除 | 暂停下游依赖 |
事件总线的设计要点,我总结三条:
- 至少一次投递:宁可重复,不能丢失。重复的事件可以在消费端做幂等处理。
- 分区有序:同一个 Dataset 的事件必须按顺序消费,否则状态会乱。
- 消息 TTL:设置合理的过期时间。我习惯设 7 天,太短了历史回溯困难,太长了浪费存储。
3.4 整体架构图
下面这张图,是我画的一个简化版架构。它展示了 Dataset Trigger 在系统中的位置,以及元数据服务和事件总线怎么配合。
3.5 数据流与事件流
搞清楚了位置,咱们再看看数据是怎么流的。我习惯把流程拆成两条线:
数据流(实线):数据从 Hive/HDFS 写入,元数据服务记录状态变化,事件总线广播事件,Dataset Trigger 消费事件并判断,最后通知调度引擎触发任务。
控制流(虚线):调度引擎向 Dataset Trigger 注册监听规则,Trigger 向元数据服务查询 Dataset 的依赖关系,元数据服务返回 Dataset 的上下游拓扑。
这两条线是并行的。数据流负责“快”,控制流负责“准”。
一个小技巧:我习惯在 Dataset Trigger 内部维护一个本地状态缓存。每次从事件总线拿到事件后,先查本地缓存,命中就直接判断,没命中再去元数据服务查。这样能大幅降低元数据服务的压力。缓存失效时间我设 30 秒,够用。
3.6 高可用设计
最后聊两句高可用。Dataset Trigger 本身不能挂,挂了整个数据驱动调度就瘫痪了。
我建议至少做三件事:
- 多副本部署:至少两个实例,用消费者组的方式消费事件总线。一个挂了,另一个自动接管。
- 状态持久化:Trigger 的消费进度(offset)要定期写入外部存储,重启后能续上。
- 降级策略:如果事件总线挂了,回退到轮询模式。虽然慢,但至少不丢任务。
我曾经踩过的坑:有一次事件总线集群磁盘写满,消息积压了 3 个小时。Trigger 的 offset 没及时提交,重启后重复消费了 10 万条消息,下游任务全部重复跑了一遍。从那以后,我强制要求所有事件消费逻辑做幂等——同一个事件处理两次,结果必须一样。
嗯,架构原理这部分就这些。记住三个关键词:位置(调度引擎和数据层之间)、机制(事件驱动)、依赖(元数据服务 + 事件总线)。后面咱们会深入讲 Dataset Trigger 的具体实现,到时候这些原理都会用上。