最近在搞实时数据链路,手头有个需求:用 Flink 消费 Kafka 里的业务日志,做一轮清洗和规则过滤之后,再落到下游存储。任务不能一直挂在命令行里跑,得有调度、有告警、有可视化地管理起来。我最终选的是 DolphinScheduler 来做调度,Flink 作为计算引擎,Kafka 作为消息管道,整套环境跑在 Linux 服务器上。
DolphinScheduler 调度 Flink 任务这个组合,是我在多个实时项目里反复验证过的方案。和直接用 crontab 或者 nohup 相比,DolphinScheduler 的流程编排、失败重跑、告警通知、多租户隔离这些能力,能让流式任务真正进入"可运维"的状态。这篇博文不打算讲太多虚的,我从环境准备、Flink 任务开发、DolphinScheduler 任务配置,到常见的坑和排查思路,一步步把整条链路拆开来讲,适合正在搭实时数仓、日志处理管道,或者想把 Flink 任务纳入统一调度平台的读者参考。
1. 项目概述与整体方案选型
1.1 为什么需要调度平台来管理 Flink 任务
很多人刚接触 Flink 的时候,习惯直接在服务器上跑一条命令:
bash复制./bin/flink run -d -c com.example.KafkaFlinkJob /path/to/job.jar
任务确实能跑起来,但一旦进入生产环境,问题就来了:这个任务挂了谁能在 5 分钟内发现?Kafka 消费位点停在哪个 offset?出故障后重新提交的日志在哪里?如果每天要定点启动一批任务,依赖关系怎么编排?
crontab 能解决定时触发,但解决不了流程编排、失败重试、任务依赖和告警。Azkaban 能做 DAG 调度,但安装部署和日常维护成本偏高,权限模型也不够细致。DolphinScheduler 在开源调度里面算是对使用者最友好的一个,提供 Web 界面可视化操作,支持 Shell、SQL、Flink、Spark 多种任务类型,还能做到任务之间上下游依赖的自动触发。
我个人的观点是:调度 Flink 任务,关键不在于"能启动",而在于"可观测、可重试、可治理"。DolphinScheduler 恰好把这些能力打包好了。
1.2 技术选型:Flink、Kafka、DolphinScheduler 的分工
这三个组件放在一起是非常经典的实时计算三件套,各自的定位完全不同:
| 组件 | 定位 | 核心能力 |
|---|---|---|
| Kafka | 消息队列/数据管道 | 高吞吐写入、消息持久化、多消费组隔离、削峰填谷 |
| Flink | 流式计算引擎 | 状态管理、事件时间处理、精确一次语义、丰富的窗口计算 |
| DolphinScheduler | 工作流调度 | 任务编排、定时触发、依赖管理、失败重试、告警通知 |
数据流向大概是这样的:
code复制业务日志 -> Kafka Topic -> Flink 消费清洗 -> 下游存储(MySQL/ClickHouse/ES)
^
|
DolphinScheduler 负责启动和监控 Flink 任务
选择 Flink 而不选 Spark Streaming,除了实时性更强之外,关键还是 Flink 的 checkpoint 机制在做流式聚合、状态计算的时候更成熟。Kafka 在这里承担的是缓冲和解耦的职责,即使 Flink 任务短暂下线,消息也不会丢,任务恢复后可以继续从上次 checkpoint 对应的 offset 消费。DolphinScheduler 则负责在最外层做"调度大脑",按小时、按天触发业务关联的多个 Flink jar 包任务。
1.3 版本组合建议
在动手之前,一定要先确认版本兼容性。我在项目里用过的组合比较多,目前最稳的一套是:
- JDK 1.8(Flink 1.14+ 也支持 JDK 11,但很多公司生产环境还是 1.8)
- Kafka 2.8 或 3.x
- Flink 1.16 或 1.17
- DolphinScheduler 3.1.x
- Zookeeper 3.8(如果 Kafka 没用 KRaft 模式)
这里特别提醒一句:DolphinScheduler 的 Flink 任务节点,本质上是在底层拼接 flink run 命令。所以 DolphinScheduler 的版本只要能正常执行 shell 命令和识别你配置的 FLINK_HOME 就行,它和 Flink 的版本耦合度不高。真正需要在意的是 Flink 本身和 Kafka connector 的版本对应关系,这块我在后面第 3 节细讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Linux 环境准备与基础部署
2.1 Linux 系统初始化
不管是 Flink、Kafka 还是 DolphinScheduler,都需要跑在 Linux 上。部署前我习惯先做一轮系统初始化,避免后面踩环境坑。
第一步:创建独立用户
生产环境不建议用 root 跑任务,我通常会单独建一个用户:
bash复制useradd -m -s /bin/bash datadev
passwd datadev
所有 JDK、Flink、Kafka、DolphinScheduler 的安装目录都统一放在 /opt 或 /data 下面,并把目录属主改成 datadev,这样权限清晰,也好管理。很多同学用 root 装完直接启动,后面在 DolphinScheduler 里用租户提交任务时经常遇到权限问题,就是因为目录属主不对。
第二步:安装 JDK 并配置环境变量
编辑 /etc/profile,追加:
bash复制export JAVA_HOME=/opt/jdk1.8.0_202
export PATH=$JAVA_HOME/bin:$PATH
执行 source /etc/profile 后验证:
bash复制java -version
第三步:关闭防火墙并配置 hosts
本地测试环境可以关闭防火墙:
bash复制systemctl stop firewalld
systemctl disable firewalld
如果是云服务器,需要在安全组里放行对应端口。然后编辑 /etc/hosts,把各节点的主机名和 IP 对应起来。分布式环境里主机名解析如果不对,Kafka 的 advertised.listeners 会经常出问题,这个很关键。
第四步:时间同步
流式任务对服务器时间很敏感,Kafka 的消息时间戳、Flink 的事件时间处理都依赖时钟。配置 NTP 或 chrony 同步时间,这一步别省。
2.2 Flink 部署要点
Flink 支持 standalone、YARN、Kubernetes 等多种部署模式。DolphinScheduler 提交 Flink 任务的时候,可以指定运行模式。我先说最简单的 standalone。
下载 Flink 二进制包并解压:
bash复制tar -zxvf flink-1.16.2-bin-scala_2.12.tgz -C /opt
cd /opt/flink-1.16.2
修改 conf/flink-conf.yaml,关键参数:
yaml复制jobmanager.memory.process.size: 2048m
taskmanager.memory.process.size: 4096m
taskmanager.numberOfTaskSlots: 4
rest.port: 8081
这些参数影响到 Flink 能同时跑多少个任务、单个任务的并行度上限。我习惯先按服务器内存的四分之一到三分之一分配给 TaskManager,然后根据需要的并行度反推 slot 数量。
启动集群:
bash复制./bin/start-cluster.sh
验证方式很简单,浏览器访问 http://<服务器IP>:8081,能看到 Flink Web UI 就没问题。
如果后面任务量大了,建议直接切到 YARN 模式,Flink 任务由 YARN 动态分配资源,和 DolphinScheduler 配合反而更顺手。但初学阶段用 standalone 足够理解全链路。
2.3 Kafka 部署要点
Kafka 的部署相对简单,下载解压后修改 config/server.properties:
properties复制broker.id=0
listeners=PLAINTEXT://0.0.0.0:9092
advertised.listeners=PLAINTEXT://192.168.1.10:9092
log.dirs=/data/kafka-logs
zookeeper.connect=localhost:2181
advertised.listeners 这个配置是客户端实际连接的地址,如果写成了 localhost,外部机器的 Flink 任务去消费时就永远连不上。我在多个项目里都遇到过这个问题,排查了很久才发现是指定错了。
启动 Kafka:
bash复制bin/kafka-server-start.sh -daemon config/server.properties
然后创建一个测试 topic 并验证:
bash复制bin/kafka-topics.sh --create --topic test-topic --partitions 3 --replication-factor 1 --bootstrap-server localhost:9092
bin/kafka-console-producer.sh --topic test-topic --bootstrap-server localhost:9092
bin/kafka-console-consumer.sh --topic test-topic --from-beginning --bootstrap-server localhost:9092
生产者输入数据,消费者能收到,就说明 Kafka 链路通了。
2.4 DolphinScheduler 部署要点
DolphinScheduler 的部署方式有源码编译、二进制解压、Docker 等。单机场景下用官方提供的二进制包最快,具体的部署顺序是:初始化数据库 -> 修改配置 -> 启动服务。
版本我建议直接用 3.1.x,下载 apache-dolphinscheduler-3.1.9-bin.tar.gz。
解压后,需要先初始化数据库,DolphinScheduler 使用 MySQL 存储元数据:
sql复制CREATE DATABASE dolphinscheduler DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER 'ds_user'@'%' IDENTIFIED BY 'ds_pass';
GRANT ALL PRIVILEGES ON dolphinscheduler.* TO 'ds_user'@'%';
FLUSH PRIVILEGES;
然后复制并修改 bin/env/install_env.sh 里的配置,再执行:
bash复制sh bin/create-dolphinscheduler.sh
启动服务:
bash复制sh bin/install.sh
启动完,访问 http://<服务器IP>:12345/dolphinscheduler/ui,使用配置文件里的默认管理员账号登录。进入 Web UI 后,记得先去"安全中心"配置环境变量和 Worker 分组,这样创建 Flink 任务时才能选择正确的运行环境。
DolphinScheduler 在单机模式下需要对外提供 12345 端口给前端,还需要 1234 端口给 API 服务,另外 Master 和 Worker 默认通信端口也要保证不冲突。如果只有一台服务器,直接按默认配置起就行。
3. Flink 消费 Kafka 任务开发与核心细节
3.1 连接器选型:KafkaSource 和 FlinkKafkaConsumer 的取舍
写 Flink 消费 Kafka 的代码,要特别注意连接器版本。Flink 1.14 之前,主流的写法是 FlinkKafkaConsumer;Flink 1.14 之后官方推荐新的 KafkaSource API,并逐步把老的 FlinkKafkaConsumer 标记为弃用。
这俩接口的核心差异体现在哪?
| 对比项 | FlinkKafkaConsumer(老) | KafkaSource(新) |
|---|---|---|
| 设置起始 offset | setStartFromLatest() 等专用方法 |
OffsetsInitializer.latest() 统一处理 |
| 动态分区发现 | setStartFromEarliest() 等 |
内置支持 |
| 消费组管理 | 依赖 Properties 里的 group.id | 同样依赖,但 API 更干净 |
| 发展方向 | 已停止新功能开发 | 官方主推 |
我的建议是:如果是新建任务,直接用 KafkaSource。代码更简洁,社区后续的优化也都在这个方向。
添加依赖时,要保证连接器版本和 Flink 主版本匹配。Maven 依赖如下:
xml复制<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka</artifactId>
<version>1.16.2</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java</artifactId>
<version>1.16.2</version>
</dependency>
3.2 核心代码实现:消费 Kafka 并做基础清洗
下面是一个可以直接复用的 Flink 任务骨架,做三件事:连接 Kafka 消费指定 topic 的消息、按 json 做基础解析和过滤、打印结果并写入下游 Sink。
java复制import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.connector.kafka.source.KafkaSource;
import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.CheckpointConfig;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.CheckpointingMode;
public class KafkaFlinkJob {
public static void main(String[] args) throws Exception {
// 1. 创建执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 2. 开启 Checkpoint,这是流式任务不丢数据的核心
env.enableCheckpointing(5000, CheckpointingMode.EXACTLY_ONCE);
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500);
env.getCheckpointConfig().setCheckpointTimeout(60000);
env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
env.getCheckpointConfig().enableExternalizedCheckpoints(
CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
// 3. 构建 KafkaSource
KafkaSource<String> source = KafkaSource.<String>builder()
.setBootstrapServers("192.168.1.10:9092")
.setTopics("test-topic")
.setGroupId("flink-ds-group")
.setStartingOffsets(OffsetsInitializer.earliest())
.setValueOnlyDeserializer(new SimpleStringSchema())
.build();
DataStream<String> stream = env.fromSource(
source,
WatermarkStrategy.noWatermarks(),
"kafka-source"
);
// 4. 业务逻辑:过滤空消息,并简单打印
DataStream<String> result = stream
.filter(line -> line != null && !line.trim().isEmpty())
.map(line -> {
// 这里可以换成 JSON 解析,比如用 Fastjson 或 Jackson
// 我这里只做模拟的标记
return "[RECV] " + line;
});
result.print();
// 5. 提交任务
env.execute("kafka-flink-demo");
}
}
代码里有几个值得注意的点:
开启 Checkpoint 之后,Flink 会定期把状态和消费位点保存到存储(standalone 模式默认保存在 JobManager 内存,生产建议用 HDFS 或 RocksDB)。只有在开启 Checkpoint 的情况下,Flink 才能和 Kafka 配合实现精确一次消费,否则重启任务时 offset 最多是"至少一次",可能重复消费。
setStartingOffsets(OffsetsInitializer.earliest()) 表示从最早的 offset 开始消费。对于实时处理场景,更常见的做法是 OffsetsInitializer.latest(),跳过历史数据只消费最新消息。这个选择要结合业务需求,不能无脑抄。
3.3 消费语义、并行度和反压的取舍
消费语义是流式任务里最绕也最容易出错的地方。Kafka 消息被 Flink 消费后,中间经过 计算、聚合、写出 多个环节,任何一个环节挂了,恢复时怎么处理?三种语义的差异我整理成了表:
| 语义 | 含义 | 什么时候会丢 | 什么时候会重 | 适用场景 |
|---|---|---|---|---|
| At most once | 最多一次 | 处理前提交 offset,崩溃时丢失 | 不重复 | 监控指标等可接受丢失 |
| At least once | 至少一次 | 不丢 | 处理完但未提交 offset,重启后重放 | 日志清洗,可接受重复 |
| Exactly once | 精确一次 | 不丢 | 依赖下游幂等或事务去重 | 支付、库存、金额计算 |
在生产项目里,我用的最多的是 At least once + 下游幂等写入 的组合。比如写到 MySQL,使用唯一键做幂等更新;写到 Kafka 下游 topic,则在消息里带上全局唯一 ID,消费端去重。这样既保证了吞吐,也规避了精确一次带来的性能开销。
并行度方面,有一个经常被忽视的点:Flink 消费 Kafka 时,单个分区的消息只会被一个并行子任务消费。如果你的 source 并行度是 8,但 Kafka topic 只有 3 个分区,那实际只会用 3 个并行度,其余 5 个空闲。设置并行度时先看分区数,不是越大越好。
反压是另一个常见现象。当处理速度跟不上消费速度时,Flink Web UI 上能看到 Source 算子出现高水位反压标记。处理思路不是盲目加并行度,而是先检查下游 Sink 的写入瓶颈,比如数据库连接池不够、批量写参数不合理等。很多时候反压的根因在下游,不在消费能力本身。
3.4 下游输出:JDBC Sink 的实操经验
如果要把清洗后的数据写入 MySQL,下面这段是我常用的 JDBC Sink 写法:
java复制import org.apache.flink.connector.jdbc.JdbcConnectionOptions;
import org.apache.flink.connector.jdbc.JdbcExecutionOptions;
import org.apache.flink.connector.jdbc.JdbcSink;
String insertSql = "INSERT INTO processed_log(id, content, create_time) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE content=VALUES(content)";
result.map(line -> {
// 这里把数据解析成 PreparedStatement 所需的三个字段
// 简化示例,真实项目建议封装成 Row
return new Object[]{1L, line, System.currentTimeMillis()};
}).addSink(JdbcSink.sink(
insertSql,
(ps, value) -> {
ps.setLong(1, (Long) value[0]);
ps.setString(2, (String) value[1]);
ps.setLong(3, (Long) value[2]);
},
JdbcExecutionOptions.builder()
.withBatchSize(1000)
.withBatchIntervalMs(1000)
.build(),
new JdbcConnectionOptions.JdbcConnectionOptionsBuilder()
.withUrl("jdbc:mysql://192.168.1.20:3306/test_db")
.withUsername("root")
.withPassword("password")
.withDriverName("com.mysql.cj.jdbc.Driver")
.build()
));
注意我用了 ON DUPLICATE KEY UPDATE,这就是"下游幂等"的典型做法。即使 Flink 因为重启重复消费了 Kafka 里的数据,写入 MySQL 时也不会产生重复记录。
如果写入量很大,建议开启批量写参数,减少数据库交互次数。还可以把 JdbcExecutionOptions 里的 withBatchSize 调大到 3000~5000,实测写入性能提升非常明显。
4. DolphinScheduler 中配置与提交 Flink 任务
4.1 创建项目、租户与上传资源
DolphinScheduler 的 Web UI 上手并不难,但有几个前置配置必须做好,否则创建 Flink 任务节点时很多选项是灰的。
第一步:创建租户
在"安全中心 -> 租户管理"里创建租户。租户对应的就是 Linux 系统用户(比如前面创建的 datadev),DolphinScheduler 提交任务时会切换到该用户执行命令。这里要注意:租户名必须和 Linux 系统用户一致,而且要保证该用户对 Flink 安装目录和 JAR 包目录有读取权限。
第二步:创建普通用户
在"安全中心 -> 用户管理"中创建用户,并绑定租户。管理员账号一般只做系统管理,实际跑任务的用普通用户。
第三步:创建项目并上传 JAR
在首页"项目管理"中新建项目。然后进入项目的"资源管理"页面,上传你的 Flink 任务 JAR 包。上传后记录文件路径,后面配置节点时要用。官方也支持指定服务器本地路径,但我更推荐先上传到 DolphinScheduler 资源中心,这样多节点部署时不用每台服务器都拷贝一份。
4.2 Flink 任务节点的参数详解
进入工作流定义页面,拖拽一个 "FLINK" 类型的节点,双击进入配置。下面这几项是必填的:
| 配置项 | 我的常用值 | 说明 |
|---|---|---|
| 程序类型 | Java | 根据 main 方法类型选 Java/Scala |
| 主类 | com.example.KafkaFlinkJob |
必须是全类名 |
| JAR 包 | 选择资源中心里上传的文件 | 关键 |
| 运行参数 | --topic test-topic 等 |
传给 main 方法的 args |
| 部署方式 | per-job / yarn-per-job / standalone | 需根据 Flink 部署模式选 |
| 并行度 | 3 | 对应 Kafka 分区数 |
Flink 节点最核心的配置难点是 部署方式。DolphinScheduler 的 Flink 任务节点底层用 flink run 命令来执行,它支持很多运行模式。如果你的 Flink 是 standalone 模式,就选 standalone;如果是 YARN 模式,DolphinScheduler 会拼接 -m yarn-cluster 参数。项目里我实际用的最多的是 per-job 模式,它会为每个任务单独申请 YARN 资源,做资源隔离。
自定义参数 也值得好好利用。比如 Kafka 的 broker 地址、topic 名称这些,我在开发时就把它们写成了 main 方法的参数:
java复制String bootstrapServers = args[0];
String topic = args[1];
然后在 DolphinScheduler 的"运行参数"里这样填:
text复制--bootstrap.servers 192.168.1.10:9092 --topic test-topic
这样任务启动时就不再依赖硬编码,换环境只需要换配置,不用重新打 JAR。这算是我做流式任务的一个习惯:任何环境相关的东西都外部化,代码里永远不要出现 IP、密码这些。
4.3 调度时间、失败重试与告警
DolphinScheduler 的调度功能是这个平台最大的价值所在。在"工作流定义"页面点击"定时"按钮,可以配置 Cron 表达式。比如每 5 分钟执行一次:
text复制0 */5 * * * ?
对于流式任务来说,调度时间一般只在任务挂掉恢复时才有意义。DolphinScheduler 不适合用来做秒级调度,它更适合分钟级、小时级、天级的批处理任务。如果是真正 7x24 小时的实时任务,DolphinScheduler 更多是扮演"守护进程 + 重启工具"的角色。
失败重试的配置在工作流定义页面右侧的属性面板里:
- 失败重试次数:我一般设置 3 次
- 失败重试间隔:60 秒
设置重试前要先想清楚一个问题:Flink 任务重启后,offset 从哪里继续?如果是开启了 Checkpoint 的精确一次任务,重试一般安全;如果任务本身没有做状态持久化,重试会导致重复消费。所以重试不是万能的,它依赖你前面的代码写得好不好。
告警配置在"安全中心 -> 告警组管理"。DolphinScheduler 支持邮件、钉钉、企业微信等告警方式,我通常配置邮件告警,失败时立刻发邮件给负责人。配置邮箱时需要设置发件人 SMTP 信息,这个在"安全中心 -> 告警组管理"里的告警实例配置中完成。填好 SMTP 服务器、端口、账号密码后,测试发送一封验证邮件,能收到就说明配好了。
4.4 运行验证与日志查看
配置完成后,点击"上线",再点击"运行"手动触发一次任务。观察任务实例的状态:
- 如果变成"成功",说明 Flink 任务已经正常提交。
- 如果变成"失败",进入任务实例详情页,点击"查看日志"按钮,DolphinScheduler 会把完整的输出日志展示出来。
这里我要强调一个细节:DolphinScheduler 的日志只到"命令执行完成"级别。它展示的是 flink run 这个 shell 命令本身的输出,并不直接包含 Flink JobManager 的完整运行日志。真正看 Flink 层面的日志,要么去 Flink Web UI(8081 端口)的 Task Manager 页面,要么去 YARN 的日志服务里看。很多初学者在 DolphinScheduler 里翻不到 Flink 内部日志,就误以为任务没启动,其实任务可能已经在 Flink 上跑得好好的了。
5. 常见问题与排查技巧实录
5.1 Flink 任务"提交成功"但消费端没有任何数据
这个现象很典型。在 DolphinScheduler 里任务显示成功,Flink Web UI 上也有 Job 在运行,但 Kafka 的消费组就是没有变化。我排查这类问题时的顺序是:
- 先看任务的并行度与 Kafka 分区数是否匹配,如果并行度小于分区数,但 source 并行度配错了,有可能部分分区没被消费。
- 再检查 KafkaSource 的起始 offset 设置。如果任务写的是
latest(),而消息在生产之前就进到 Kafka 了,那自然消费不到老数据。 - 最后检查是否开启了 Checkpoint。如果集群一直重启,任务反复从最近 checkpoint 恢复,也可能绕过部分新消息。
另外,有一次我排查了很久,最后发现是 group.id 配置变了。同一个 topic,换了新的 group.id,Flink 就会从起始 offset 或 latest 重新消费,看起来"没有数据",实际上只是换了消费组。
5.2 Kafka 连接超时 / 鉴权失败
这类报错信息通常类似:
text复制org.apache.kafka.common.errors.TimeoutException: Topic test-topic not present in metadata after 60000 ms
或者:
text复制org.apache.kafka.common.errors.SaslAuthenticationException: Authentication failed
处理思路分几个层面:
第一,查网络连通性。
bash复制telnet 192.168.1.10 9092
如果端口不通,十有八九是安全组、防火墙或者 listeners 配置问题。
第二,核对 advertised.listeners。
在服务器上执行 kafka-console-consumer.sh 能正常消费,但 Flink 任务连不上,大概率就是 advertised.listeners 写成了 localhost 或者内网 IP 不对。Flink 客户端拿到的连接地址来自这个配置,而不是 Kafka 的监听端口。
第三,如果是认证环境,确保参数正确传递。
Kafka 启用 SASL 时,需要在 Flink 任务中传入:
java复制props.setProperty("security.protocol", "SASL_PLAINTEXT");
props.setProperty("sasl.mechanism", "PLAIN");
props.setProperty("sasl.jaas.config", "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"user\" password=\"pass\";");
这些配置在 DolphinScheduler 里可以通过自定义参数传给 main 方法,再由代码写入 Properties。注意不要在代码里硬编码,也不要把密码提交到代码仓库。
5.3 YARN 模式下任务一直处于 ACCEPTED 或 FAILED
如果你的 Flink 跑在 YARN 集群上,DolphinScheduler 提交任务后,经常会出现"任务提交成功但很快失败"的情况。典型报错是:
text复制Application application_xxx_xxxx failed 2 times due to AM Container for appattempt ... exited with exitCode: 1
这种问题通常集中在:
- YARN 资源不足:集群剩余内存不够,任务一直排队。
- Flink 程序里的 main 方法抛异常:比如依赖缺失、配置错误,YARN 会把 AM 也一起杀掉。
- HDFS 权限问题:Flink 依赖的 JAR 包和 checkpoint 目录没有写权限。
排查方法是先到 YARN 集群的 ResourceManager 页面看 Application 日志,或者用命令:
bash复制yarn logs -applicationId application_xxx_xxxx
YARN 日志比 DolphinScheduler 日志信息全得多,大部分"莫名其妙失败"的任务,在 YARN 日志里都能找到根本原因。
5.4 常见报错速查表
我把实际项目中遇到过的高频报错整理成了一张表,方便后面直接照着排查。
| 报错现象 | 可能原因 | 处理方法 |
|---|---|---|
Topic not present in metadata |
Kafka broker 地址不可达或 topic 不存在 | 检查网络、advertised.listeners、topic 名称 |
ClassNotFoundException |
依赖冲突或 JAR 包未提交 | 用 maven-shade-plugin 打 fat-jar,确认依赖完整 |
Checkpoint failed |
存储目录不可用或空间不足 | 检查 HDFS/本地 checkpoint 目录权限和容量 |
No space left on device |
磁盘写满 | 清理日志,扩容磁盘 |
Failed to construct kafka consumer |
Kafka 客户端参数错误 | 检查 Properties 配置项拼写 |
| 反压持续高水位 | 下游写入慢或并行度不足 | 优化 Sink,批量写入,调整并行度 |
could not find a matching constructor |
反序列化器使用不当 | 确认 SimpleStringSchema 或自定义 Deserializer 正确 |
| DolphinScheduler 日志显示成功但没有 Flink Job | 提交到了错误的 YARN 队列/集群 | 检查部署方式和资源队列配置 |
5.5 监控建议与运维技巧
任务上线之后,监控才是长久的痛点。我的建议是至少把这几个监控指标纳入观察范围:
Flink 侧:
- Job 状态(RUNNING / FAILED / RESTARTING)
- Checkpoint 完成时间和失败次数
- 反压情况
- TaskManager 的 GC 情况和内存使用率
Kafka 侧:
- 消费组 Lag 值,这是判断任务是否在正常消费最直观的指标。用命令查看:
bash复制bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group flink-ds-group
Lag 持续增长说明消费能力跟不上生产速度,需要扩容或者优化算子。
DolphinScheduler 侧:
- 工作流实例的成功/失败/超时状态
- 失败重试次数是否超限
- 告警是否及时触达
最后再说一个运维技巧:在 DolphinScheduler 里管理 Flink 任务的时候,尽量给每个任务定义清晰的命名规范,比如 flink_consumer_<业务名称>,并且在任务的自定义参数里保留业务负责人信息。这样别人接手你的项目时,不需要翻代码就知道这个任务干什么的、出问题该找谁。
整个链路跑通之后,我自己最大的感受是:DolphinScheduler 本身不难,难的是把它和你已有的 Flink、Kafka、YARN 环境正确地组合起来。只要环境配置对了,代码里 checkpooint 和参数化做好了,这套方案可以稳定支撑从几十条到百万条每秒的消息处理。项目中遇到问题的时候,记住一条原则:先看数据流(Kafka 消费有没有 Lag),再看计算流(Flink 反压和 checkpoint),最后看调度流(DolphinScheduler 日志),按这个顺序排查,绝大多数问题都能在半小时内定位到根因。
