干过 ETL 的兄弟应该都有这种体验:Kettle(也就是 PDI)跑批挂了,往往不是在挂的那一刻被人发现的,而是第二天早上业务方拿着缺失报表来问怎么回事,你才一脸懵地打开日志去找原因。夜里 2 点到 5 点的批次任务,出了错没人通知,修复窗口早就过了,非常被动。我后来花了不少时间研究怎么给 Kettle 做监控、做自定义推送,把任务状态推到企业微信上,实现“挂了立刻知道、失败原因一眼看清”,这套方案在多个项目里落地之后,确实省了很多救火时间。
标题里说的“简单两步”,不是标题党。拆开来看确实就两步:第一步让 Kettle 每次执行完把状态落成可查询的数据;第二步写个脚本把异常数据推送到企业微信机器人。难的是每一步里的细节,比如状态表怎么设计、Job 里怎么埋点、脚本怎么避免重复告警、消息格式怎么处理。这篇文章把这些坑都填上,适合正在用 Kettle 做数据同步、批处理但又没有完善监控体系的团队参考;个人开发者用 Spoon 跑本地任务,也可以按同样思路做个轻量版的自定义推送。
1. 先把需求聊透:你要的其实是“状态可视”和“异常触达”
1.1 调度工具并不等于监控工具
很多人觉得 Kettle 任务已经在 Linux 上用 cron 定时跑了,或者用 Windows 计划任务在跑,每天日志也在生成,就觉得任务“有人管了”。但真的出问题时你会发现,日志文件是死的,不会主动告诉你任务失败了;哪怕你知道任务失败了,还要自己去服务器上翻 .log 文件,从几千行输出里找异常原因,效率极低。
说白了,调度解决的是“什么时候跑”,监控解决的是“跑成什么样、我怎样才能知道”。这两个问题经常被混在一起,导致很多人以为加了调度就有了监控。实际上,Kettle 自身的日志表虽然有 JOB_LOG、STEP_LOG 这些,但它们更偏向于执行过程的审计,真正拿来当监控源,还缺一层“业务化”的加工。
我在项目里最常用的做法,是单独建一张任务状态表,由 Job 自己把每次执行的批次、开始时间、结束时间、状态、错误信息写进去;监控脚本只需要盯这一张表,不需要去解析 Kettle 的多张日志表。这样设计的好处非常明显:查监控的人在数据库里一条 SQL 就能看出所有问题,不用懂 Kettle 内部表结构;后续无论你是推送到企业微信、钉钉还是接入 Prometheus,都只需要对着这一张表操作。
1.2 “两步法”的全局架构是什么
整套方案可以理解成下面这个链路:
- Kettle Job 内部埋点:在 Job 开始、成功、失败三个关键节点,调用一个写状态表的转换或作业。
- 状态表作为核心:记录 job_name、batch_date、status、start_time、end_time、error_msg、host 等信息。
- 监控脚本轮询这张表:判断是否有“新增失败”或“超时未完成”的记录。
- 推送服务发消息:通过企业微信群机器人 Webhook,把格式化后的消息推给相关人员。
后面所有内容都是围绕这条链路展开的。你可能会问:为什么不直接用 Kettle 自带的日志表,或者干脆解析 Spoon 里的日志文件?主要有两个原因:一是 Kettle 日志表字段很多且分散,实时查状态不直观;二是日志文件分散在不同服务器上,脚本访问麻烦,而且 Kettle 的日志级别一调,内容格式也会变。自己维护一张精简状态表,等于把监控源收敛到一个可控点,省心得多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 第一步落地:在 Kettle Job 里做好状态埋点
2.1 状态表怎么设计才能支撑监控查询
先说我个人偏好的一套表结构,做过几个项目以后调整出来的版本:
| 字段名 | 类型(以 MySQL 为例) | 说明 |
|---|---|---|
| id | bigint auto_increment | 主键 |
| job_name | varchar(128) | 任务名称,尽量全局唯一 |
| batch_date | varchar(32) | 业务批次日期,比如 2025-01-15 |
| status | varchar(16) | running / success / failed / timeout |
| start_time | datetime | 任务开始时间 |
| end_time | datetime | 任务结束时间 |
| error_msg | text | 失败时的关键错误信息 |
| exec_host | varchar(64) | 执行任务的服务器 IP 或主机名 |
| update_time | datetime | 最后更新时间,监控脚本依赖它 |
| alert_flag | tinyint | 是否已告警,0/1 |
job_name 和 batch_date 建议加唯一索引。这样设计有什么讲究?先说 batch_date:很多 ETL 是凌晨跑昨天的数据,如果没有这个字段,你查监控时很难快速定位“是哪个批次出了问题”。再说唯一索引:Kettle 任务万一被误触发了两次,同一批次会有两条记录,监控脚本就会重复告警,加了唯一索引后,在代码里用“存在即更新、不存在即插入”的思路,可以保证一个批次只有一条状态记录。
status 字段里特意保留了“timeout”这个状态,不是 Kettle 写进去的,而是监控脚本根据 start_time 和更新间隔计算出来的。Kettle 任务如果整体卡死,Job 内部可能连失败分支都触发不了,状态一直停在 running;这时候就得靠外部脚本判断“一个任务跑超过 N 分钟还没结束,判定超时”,把状态改成 timeout 再告警。这是很多自建监控容易漏掉的一种情况。
2.2 在 Job 里怎么加“状态写入”节点
有了表之后,接下来是让 Kettle Job 在执行的几个关键节点把状态写进去。可能有些朋友习惯用 Spoon 界面拖节点,然后通过“作业跳”来控制成功失败分支,思路都是一样的。
我的实现方式是这样的:
- 准备一个公用的转换,名字叫“write_job_status”,里面就放一个“表输出”步骤,连接你的状态表库。
- 在表输出步骤里,通过变量动态写入字段值,比如 job_name 来自变量 ${job_name},status 字段根据当前分支情况传参。
- 在 Job 的开始节点后面,先调用一次 write_job_status,传入 status=running, start_time=当
