上个月我接了个活儿,把一套老项目的批处理任务迁到新数据平台上,顺手要把海豚调度器(DolphinScheduler)重新部署起来,并且加上钉钉预警。本来以为这活儿半天就能收工——我早在前两份工作里就折腾过这个调度器,DAG拖拽、定时触发、任务依赖,闭着眼都能配。结果真正动起手来才发现,现在的版本跟我记忆里的差别比想象中大,尤其是告警这块,配置路径、插件逻辑、消息内容全变了,旧经验只能算个引子,细节全靠重新踩坑。这篇文章就把这次“再次使用”海豚调度器的过程拆开讲,重点说清楚钉钉预警从零怎么配、配置背后的原理是什么、以及哪些地方容易翻车。如果你正准备给 DolphinScheduler 加钉钉告警,或者你是接手一个已经部署好的调度平台,这里应该有你能直接抄的配置步骤和排查思路。
1. 再次启用海豚调度器,先看版本和告警架构
1.1 从旧记忆中跳出来:版本演进带来的配置差异
我第一次用海豚调度器还是 1.3.x 时代。坦白讲,那个版本的告警模块用起来挺费劲,告警相关的配置散落在 alert.properties 里,加一个钉钉机器人要先改配置、再重启 alert 服务,而且当时钉钉接入还属于需要额外启用的选装件,注释里写着一堆参数,哪个必须填、哪个可以留空,全凭运气。后来在另一个项目里用过 2.0.x,从那时起告警就开始走向插件化了,但说实话,很多从 1.x 升上来的老用户还是习惯性去翻配置文件,容易走弯路。
这次项目用的是 DolphinScheduler 3.1.5,变化就更明显了。告警不再是“配置文件里的一段内容”,而是变成了“安全中心”里的一个独立告警实例。钉钉、企业微信、飞书、邮件、Webhook 这些都是以插件形式预置好的,你只需要在 UI 上创建实例、填参数、保存,然后把它挂到告警组里,剩下的交给 AlertServer 去处理。全程不用动一行 conf 文件,也不需要因为改告警配置去重启服务。
这里我整理了一下几个大版本的对比,给同样有“旧经验”的读者作个参考:
| 版本 | 告警配置方式 | 钉钉支持程度 | 我当时的感受 |
|---|---|---|---|
| 1.x | 修改 alert.properties 后重启服务 | 手动启用,参数隐蔽 | 每次改配置都要心惊胆战重启,容易影响正在跑的实例 |
| 2.0.x | 告警插件化,UI 配置 | 已支持钉钉自定义机器人 | Webhook 和关键词填好基本就能用,开始顺手了 |
| 3.x | 告警实例 + 告警组分离管理 | 支持关键词、加签、@指定人 | 配置路径变了,但灵活性高了,值得重新学一遍 |
所以我给所有“再次使用”海豚调度器的朋友一个建议:先别急着凭肌肉记忆操作,登录 UI 后先去“安全中心”里转一圈,看看当前版本的告警入口在哪里。老经验可以用来理解底层逻辑,但具体操作一定要以当前版本的界面为准。
1.2 钉钉告警链路里到底有哪些角色
配置钉钉告警之前,先把这条链路搞清楚,后面排查问题才能有的放矢。DolphinScheduler 的告警机制可以拆成三段:
第一段是触发侧。工作流或任务节点执行完,MasterServer 会根据工作流定义里配的“通知策略”判断这次运行结果要不要发告警。如果判定需要告警,它不会直接去调钉钉接口,而是把一条告警事件写入元数据库的告警表(t_ds_alert),记录里包含项目、工作流、任务节点、运行状态、告警内容这些信息。
第二段是传输侧。AlertServer 是独立的告警服务,它像一个巡逻员,定时去扫描告警表里还没发送的记录,根据告警实例对应的插件类型,把消息格式化成钉钉能识别的 JSON,然后调用钉钉自定义机器人的 Webhook 地址把消息推出去。
第三段是接收侧。钉钉群里的自定义机器人收到 HTTP 请求后,先做安全校验。如果你开启了自定义关键词,它要检查消息里有没有包含这个词;如果开启了加签,它要验证 URL 上的签名是否合法。校验通过,消息才会出现在群里。
这个机制很像小区物业的巡更流程。安保发现楼道灯坏了,先在值班本上记一笔;巡更员定时翻值班本,拿到记录后打电话通知维修师傅。任何一个环节断了,告警就送不到钉钉。最典型的三种情况:安保根本没写值班本(通知策略没选对)、巡更员在偷懒(AlertServer 没启动)、维修师傅不接电话(钉钉机器人安全校验没过)。后面排查问题的时候,我基本就是按这三段来看的。
2. 动手前准备:钉钉机器人、Webhook 与安全参数
2.1 在钉钉里创建一个告警群并添加自定义机器人
配置 DolphinScheduler 之前,先把钉钉这一侧的“接收端”准备好。我建议单独建一个告警群,不要往大群里发,因为调度告警可能比较频繁,容易打扰到不相关的人。群里一般拉数据开发、运维、以及对这个任务产出有直接诉求的业务同学就够了。
建群之后,进入群设置,找到“智能群助手”,选择“添加机器人”,再选“自定义机器人”。机器人名字可以叫 dolphinscheduler-alert,方便一眼看出是哪个系统发的。创建时记得做好安全设置,钉钉给了三种选项:
- 自定义关键词:要求消息内容里必须包含你设置的词,否则拒绝发送。比如你设了“DolphinScheduler”,那么每一条消息正文里都得有这几个字。好处是简单,坏处是如果你后来又自定义了消息模板,模板里没带关键词,消息就发不出去。
- 加签:钉钉会在发送请求时校验 URL 上的动态签名。DolphinScheduler 的钉钉插件原生支持加签,只需要把密钥填到 Secret 字段里就行。这是我最推荐的一种方式,对消息内容没有额外限制,安全性也更高。
- IP 地址段:只允许从指定出口 IP 发起请求。如果你们公司有固定的公网出口,这个也可以开,但如果 DolphinScheduler 部署在容器环境、出口 IP 经常变,就别选这个了。
三种方式可以多选,但多选时约束会叠加,以后排查问题会多一层变量。我自己的经验是:生产环境只开“加签”一种,最省心。如果你用的是旧版本或者不想折腾加签,那就用自定义关键词,并且保证告警内容里永远带这个关键词。
2.2 保存 Webhook 和关键参数
机器人创建成功之后,钉钉会给你一个 Webhook 地址,格式大概是这样:
https://oapi.dingtalk.com/robot/send?access_token=这里是一长串tokenaccess_token 就是机器人的身份凭证,相当于这群聊的“门禁卡”。这个地址不要贴到公开仓库或文档里,任何人都可以用它往群里发消息。如果开了加签,还会看到一个以 SEC 开头的密钥,这个就是 Secret,待会儿要填进 DolphinScheduler 的告警实例里。
如果你希望告警时能 @ 群里的特定成员,提前把他们的钉钉手机号准备好,后面配置时填到“@指定用户”字段里。这里不建议每个人都 @,只 @ 关键负责人就行,不然告警一多大家容易麻木。
2.3 确认 AlertServer 服务在跑
有几类部署方式,对应 AlertServer 的启动和检查方法不太一样:
- 二进制部署的话,在 DolphinScheduler 安装目录下执行
bin/dolphinscheduler-daemon.sh start alert-server启动,然后可以用jps看进程,会看到一个名字里带 AlertServer 的 Java 进程。 - Docker Compose 部署的话,docker-compose 配置文件里会有一个 alert-server 服务,用
docker compose ps查看状态,用docker compose logs alert-server看日志。 - Kubernetes 部署的话,对应的是一个 Deployment,用
kubectl get pods确认状态。
这个检查很关键。我遇到过不少次,告警配置全对,但就是收不到消息,最后发现是部署时只起了 MasterServer 和 WorkerServer,AlertServer 压根没启动。它没启动的后果是:任务失败后告警记录会一直堆积在 t_ds_alert 表里,状态一直停留在 0(等待发送),钉钉自然什么都收不到。
3. 核心实操:在 DolphinScheduler 里一步步添加钉钉告警
3.1 新建告警实例:Webhook、Secret、@指定成员逐个填
配置入口在 UI 的“安全中心”模块,注意需要管理员账号才能操作。路径是:安全中心 -> 告警实例管理 -> 创建告警实例。点开之后,告警插件选择“钉钉”,然后会出现一堆字段,我逐个说一下填什么。
| 字段 | 填什么 | 说明 |
|---|---|---|
| 告警插件 | 钉钉 | 3.x 里内置的插件类型 |
| 实例名称 | 例如“生产-钉钉运维群” | 建议带上环境标识,避免测试环境告警混进生产群 |
| Webhook | https://oapi.dingtalk.com/robot/send?access_token=xxx | 从钉钉机器人页面复制,整串粘贴 |
| 关键字 | 例如“DolphinScheduler” | 如果钉钉机器人开了“自定义关键词”才需要填,与钉钉侧保持一致 |
| Secret | SECxxxxxxxx | 如果钉钉机器人开了“加签”,把密钥粘过来,不需要自己算签名,插件内部会处理 |
| 是否 @所有人 | 是/否 | 看告警群的诉求,核心系统故障可以开 |
| @指定用户 | 手机号,用逗号分隔 | 不 @ 所有人时,可以只 @ 某几个人 |
保存的时候如果提示参数校验失败,多半是 Webhook 格式不对,或者 Secret 复制的时候没复制全。另外,实例名称里的环境信息很重要,我曾经吃过亏,测试环境的告警实例和生产环境混在一起,测试任务失败时生产群里响成一片,后来所有告警实例都强制带上前缀,这种情况才彻底解决。
3.2 创建告警组:把告警实例和业务解耦
告警实例建好之后,接下来要去“安全中心 -> 告警组管理”里创建一个告警组。比如叫“数据研发告警组”,然后在“选择告警实例”里把刚才建好的钉钉告警实例勾选上。
多这一层有什么好处?最直接的好处是解耦。告警组面向业务,告警实例面向通道。业务方创建工作时不用关心这个组背后是钉钉还是邮件,它只要选中“数据研发告警组”,就代表这个工作流的失败通知会发到对应的人那里。将来运维想换机器人、换钉钉群,只需要改告警实例,不需要去翻每一个工作流定义。在团队协作的时候,这种分层设计能省掉大量沟通成本。
创建告警组之后,可以顺便检查一下项目维度。DolphinScheduler 里不同的项目可以配置不同的告警组,如果你们有多个项目共用一套调度平台,建议按项目把告警组分开,避免 A 项目的任务失败把 B 项目的人半夜吵醒。
3.3 工作流里开启通知:不选“通知策略”等于白配
这是新手最容易漏的一步。很多人以为建好告警实例、告警组,任务失败就会自动发钉钉,其实不是。你还要在工作流这一侧打开“通知”的开关。
有两个地方要设置:
第一个是工作流定义。编辑工作流时,DAG 画布右侧有“工作流属性”面板,里面有一个“告警组”下拉框,要在这里把刚才创建的告警组选上。如果不选,这个工作流跟任何告警通道都是断开的。
第二个是运行或定时调度时的“通知策略”。在 DolphinScheduler 3.x 里,手动点击“运行”启动工作流时,弹窗里会有“失败策略”和“通知策略”两个选项。通知策略有四种:都不发、成功发、失败发、成功和失败都发。如果你希望任务失败时收到钉钉消息,就选“失败发”;如果成功也想知道,就选“成功和失败都发”。
配置定时调度的时候也一样。在“定时管理”里编辑定时任务时,同样能看到通知策略的选项,而且这里更关键,因为定时任务一旦跑起来,你不可能每天都守在屏幕前看结果,靠的就是这个通知策略。
我见过一个比较典型的误操作:告警实例、告警组全都配置正确,工作流属性里的告警组也选了,但定时调度创建的时候通知策略默认是“都不发”,结果任务连续失败三天,钉钉一条消息都没收到。所以配置完一定要回头检查通知策略,这个是整个链路里“最后一公里”的开关。
3.4 验证告警:故意让任务失败一次
配置完之后,强烈建议做一个主动验证,不要等真实任务失败了才发现问题。验证方式很简单:
在工作流定义里新建一个测试工作流,加一个 Shell 节点,节点内容写:
exit 1然后在“运行”弹窗里,通知策略选择“失败发”,启动工作流。任务失败后,先看一眼钉钉群有没有收到消息。如果收到了,说明整条链路是通的,后面把测试工作流删掉或停掉就行。如果没收到,马上去查元数据库,执行:
SELECT id, title, content, alert_status, log, create_time FROM t_ds_alert ORDER BY id DESC LIMIT 5;这里会看到最近几条告警记录,重点关注 alert_status 字段:
- 0 表示等待发送,说明告警事件已经写入,但 AlertServer 还没处理,先检查它有没有启动。
- 1 表示发送成功,说明 AlertServer 已经调用了钉钉接口,但钉钉没收到,问题大概率在钉钉机器人安全设置或 Webhook 地址上。这时候看 log 字段,里面会记录钉钉接口返回的内容。
- 2 表示发送失败,log 字段一般会写明失败原因,比如超时、签名不对、消息格式不对等。
这一步就是前面说的三段式排查思路:先确认触发侧有没有写值班本,再确认巡更员有没有干活,最后确认维修师傅接没接电话。
4. 让钉钉预警真正“好用”的几个细节
4.1 默认告警消息长什么样,够用吗?
DolphinScheduler 通过内置钉钉插件发出来的消息,大致包含项目名、工作流名、任务节点名、执行时间、运行状态、日志地址等信息。对于大多数场景,这个默认内容底子是够用的。它至少能告诉你“哪个工作流的哪个节点挂了”,有了这条信息,登进调度平台去看具体日志通常也不难。
但它的短板也很明显。第一,消息里不会直接告诉你这个任务重试过几次。如果任务因为上游临时抖动失败过一次、重试后成功了,你看到的是一条失败消息,但实际任务已经恢复了,容易让人虚惊一场。第二,日志地址如果部署环境没配好,默认可能是容器内网地址,你在办公网络下点开就是个连接超时。第三,拆链路排查的时候,默认消息不会带工作流实例 ID,你要在平台上翻半天才能定位到具体哪次运行出问题。
所以我的建议是:默认告警拿来当兜底没问题,但如果你们团队对告警质量有要求,比如“一条消息能判断要不要立刻爬起来处理”,那就得在消息内容上做文章。
4.2 用 Shell/Python 节点直发自定义钉钉消息
DolphinScheduler 内置的钉钉插件消息格式是固定的,想完全自定义,最简单的方式不是去改插件源码,而是直接在任务节点里发起 HTTP 调用,自己拼消息体给钉钉机器人。
如果你的钉钉机器人只开了“自定义关键词”安全设置,方案很简单。加一个 Python 节点或 Shell 节点,用 requests 或 curl 直接发 Webhook 请求,JSON 里带上关键词就行。示例:
import requests import json webhook = "https://oapi.dingtalk.com/robot/send?access_token=xxx" data = { "msgtype": "markdown", "markdown": { "title": "数据任务失败", "text": "### 任务失败\\n" "- 项目: demo\\n" "- 工作流: data_daily_summary\\n" "- 错误码: E_0021\\n" "- 建议: 检查上游表是否就绪" }, "at": { "isAtAll": False, "atMobiles": ["13800000000"] } } resp = requests.post(webhook, json=data) print(resp.text)如果你的机器人开了“加签”,那就麻烦一点,因为签名是根据当前时间戳动态计算的。在 Python 节点里可以这样算:
import time import hmac import hashlib import base64 import urllib.parse import requests import json secret = "SEC你的密钥" timestamp = str(round(time.time() * 1000)) string_to_sign = f"{timestamp}\\n{secret}" hmac_code = hmac.new(secret.encode("utf-8"), string_to_sign.encode("utf-8"), digestmod=hashlib.sha256).digest() sign = urllib.parse.quote_plus(base64.b64encode(hmac_code)) webhook = f"https://oapi.dingtalk.com/robot/send?access_token=xxx×tamp={timestamp}&sign={sign}" data = { "msgtype": "text", "text": {"content": "自定义告警内容,带关键词也没问题"}, } resp = requests.post(webhook, json=data) print(resp.text)这里有一个重要提醒:DolphinScheduler 的 HTTP 任务节点只能填静态 URL,没法在请求时动态计算签名,所以如果机器人开了加签,直接用 HTTP 任务节点发送一定会因为缺少签名或签名不合法而失败。要么用 Shell/Python 节点自己写逻辑,要么就把机器人安全设置改成“自定义关键词”。这是我踩过的一个很具体的坑,分享出来给你们避一下。
4.3 告警分级:失败、超时、成功分别怎么发
告警平台最怕的不是没告警,而是告警轰炸。任务一失败就发、一个小抖动就发,群里天天狼来了,真出大事的时候反而没人看了。我现在的做法是分三级控制。
第一级,任务失败重试。DolphinScheduler 的任务节点高级参数里有“失败重试次数”和“失败重试间隔”。对于非核心依赖的上游任务,我会把重试次数设成 2 次,间隔 5 分钟。临时性的资源不足、网络抖动这类问题,重试一次往往就恢复的,没必要一失败就立刻打扰人。重试仍然失败的,才值得一条钉钉消息。
第二级,超时告警。任务卡死但不报错是另一种很难受的情况。比如一个数据同步任务因为数据库连接池被占满,挂着不走,失败策略不会触发,但任务实际上已经没有产出了。DolphinScheduler 任务节点里可以开“超时告警”,设置一个超时时间。时间怎么定?我一般按这个任务正常耗时的 1.5 倍再留 30 分钟余量来设置。比如平时跑 20 分钟,我就设 60 分钟,给它充足的容忍度,但真卡死了 60 分钟一定会触发超时告警。
第三级,成功告警只给核心链路开。日报类、对账类、面向领导看板的任务,跑完发一条成功消息,大家图个安心。但非核心 ETL 任务别开成功告警,一天几十个任务全发成功通知,很快就会被群消息淹没了。
5. 踩坑实录:钉钉告警常见问题与排查
5.1 常见问题速查表
为了方便读者直接对号入座,我把这次实际踩过、以及周边同事反复问过的问题整理成一张表:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 钉钉收不到消息,数据库 alert_status = 1 | 钉钉机器人安全校验失败,或 Webhook 地址错误 | 看 t_ds_alert.log 字段里钉钉接口返回的内容,确认关键词、加签是否匹配 |
| 钉钉收不到消息,数据库 alert_status = 2 | AlertServer 发送异常,如 URL 不可达、连接超时 | 查看 alert-server.log,检查网络能否访问 oapi.dingtalk.com |
| 数据库 alert_status = 0,一直不发 | AlertServer 没启动,或线程池阻塞 | 确认进程存在,必要时重启 alert-server |
| 告警时间比本地时间晚 8 小时 | 服务器或容器时区是 UTC | 二进制部署设TZ=Asia/Shanghai,容器部署在 compose 里加时区环境变量 |
| 告警消息里没有 @ 指定的人 | 告警实例里“是否 @所有人”或“@指定用户”没保存成功 | 重新打开告警实例,确认下拉值和手机号都回显正常 |
| 任务失败但一条告警都没有 | 工作流属性里没选告警组,或通知策略为“都不发” | 在工作流属性绑定告警组,运行/定时时把通知策略改为“失败发” |
| Server 日志报 TLS/SSL 握手失败 | JVM 与钉钉服务端 TLS 版本不兼容 | 在启动参数里加-Dhttps.protocols=TLSv1.2,重启告警服务 |
5.2 典型案例:自定义关键词不匹配导致消息被拒
有一次我给一个任务配置好了钉钉告警,数据库里显示发送成功,但群里就是没消息。我看了 t_ds_alert 的 log 字段,里面有钉钉返回的errcode和errmsg,明确指出消息内容不包含关键词。原因很有意思:钉钉机器人创建的时候,同事顺手填了一个自定义关键词,但他没告诉任何人;DolphinScheduler 告警实例里也填了相同的关键词,按理说应该能过。可是告警消息内容在某些异常场景下可能不包含这个关键词,结果被钉钉拒之门外。
排查思路就是去钉钉后台看机器人的安全设置,确认关键词配置,然后对比告警实例里填的关键字和实际发送的消息内容。如果消息是动态拼接的,干脆把关键词设成一个一定会出现的固定字符,比如项目前缀,或者改用加签方式。
5.3 典型案例:容器时区问题导致告警时间错乱
还有一个容易被忽略的问题:Docker 部署的 DolphinScheduler,默认镜像时区是 UTC,告警消息里的执行时间会比北京时间晚 8 个小时。一开始我还以为是代码 bug,后来把 alert-server 的日志打印时间戳和数据库插入时间对比了一下,发现是时区问题。
解决办法不复杂。Docker Compose 部署时,在相关服务的环境变量里加上:
environment: - TZ=Asia/Shanghai同时可以考虑把宿主机的 /etc/timezone 和 /etc/localtime 挂载进容器。如果你使用的是高版本基础镜像,TZ 环境变量基本就能解决。改完之后重启 alert-server 和 api-server,让整个链路的时间口径统一。
5.4 案例复盘:生产环境重启后告警实例丢失
有一次在测试环境手动部署了一套 DolphinScheduler,配完钉钉告警当天一切正常,第二天重启容器后进 UI 发现告警实例全没了。查了一圈,问题出在网上找的一个 docker-compose 模板把元数据库配置成了默认的 H2 内存库,容器一重启数据全清空。生产环境或者任何需要长期运行的测试环境,都得把元数据库切到外部 MySQL,并且把 MySQL 的存储卷持久化。否则不只是告警实例,你所有的工作流定义、调度计划都会跟着没。
在 DolphinScheduler 的 conf/application.yaml 里,把 spring.datasource 配置指向外部 MySQL,并且数据库用 utf8mb4 字符集。配置完之后重启 api-server,再用 UI 操作一遍,确认配置没问题,然后把数据目录持久化到宿主机或云盘。这一步看着基础,但真出了事故才后悔,就晚了。
最后再说两句
把钉钉告警从“配通”到“配好用”,中间差的那一步,往往是告警内容的打磨和发送噪音的控制。我现在的习惯是:核心工作流失败告警加上最多一次重试后才发;每个告警实例的名字带环境标识,测试环境的告警绝不和生产群混在一起;每周翻一眼告警表,看哪些工作流在重复失败却没什么人响应,这种记录反过来能推动业务方把 SLA 定下来。这套做法其实不复杂,但比单纯拿到一个能用的 Webhook 价值大得多。如果你也刚接手一套 DolphinScheduler,建议先把告警跑通,再照这个思路慢慢调。祝你的任务一次跑成功。