文章目录
- 前言
- 一、从一个简单例子开始
-
- [1.1 需求描述](#1.1 需求描述)
- [1.2 完整代码实现](#1.2 完整代码实现)
- [1.3 执行结果](#1.3 执行结果)
- [二、深入理解:Job 是什么?](#二、深入理解:Job 是什么?)
-
- [2.1 Job 的定义](#2.1 Job 的定义)
- [2.2 Job 的组成](#2.2 Job 的组成)
- [2.3 代码中的 Job](#2.3 代码中的 Job)
- [三、深入理解:Task 是什么?](#三、深入理解:Task 是什么?)
-
- [3.1 Task 的定义](#3.1 Task 的定义)
- [3.2 Task 的特点](#3.2 Task 的特点)
- [3.3 Task 数量计算](#3.3 Task 数量计算)
- [四、Job 与 Task 的核心区别](#四、Job 与 Task 的核心区别)
-
- [4.1 对比表格](#4.1 对比表格)
- [4.2 关系图解](#4.2 关系图解)
- [五、物理执行架构:JobManager 与 TaskManager](#五、物理执行架构:JobManager 与 TaskManager)
-
- [5.1 完整架构图](#5.1 完整架构图)
- [5.2 角色总结](#5.2 角色总结)
- [六、算子链(Operator Chain)的优化](#六、算子链(Operator Chain)的优化)
-
- [6.1 什么是算子链?](#6.1 什么是算子链?)
- [6.2 我们的例子中的算子链](#6.2 我们的例子中的算子链)
- [七、如何观察 Job 和 Task?](#七、如何观察 Job 和 Task?)
-
- [7.1 提交 Job 时的控制台输出](#7.1 提交 Job 时的控制台输出)
- [7.2 Flink Web UI 观察](#7.2 Flink Web UI 观察)
- [八、常见问题 FAQ](#八、常见问题 FAQ)
-
- [Q1:一个 Job 最多可以有多少个 Task?](#Q1:一个 Job 最多可以有多少个 Task?)
- [Q2:Task 和 SubTask 是什么关系?](#Q2:Task 和 SubTask 是什么关系?)
- [Q3:不同 Job 的 Task 可以在同一个 TaskManager 中吗?](#Q3:不同 Job 的 Task 可以在同一个 TaskManager 中吗?)
- [Q4:如何调整 Task 的数量?](#Q4:如何调整 Task 的数量?)
- 九、总结
-
- [9.1 核心要点](#9.1 核心要点)
- [9.2 一句话记忆](#9.2 一句话记忆)
- [9.3 实战建议](#9.3 实战建议)
一个简单的字符串大小写转换例子,带你彻底理解 Flink 中 Job 和 Task 的区别与联系
前言
在 Flink 的学习过程中,很多初学者都会被 Job 和 Task 这两个概念搞混。有人说 Job 是作业,Task 是任务,但具体有什么区别?它们之间又是如何协作的?今天,我将从一个最简单的字符串大小写转换案例出发,用图文并茂的方式,带你彻底理解 Flink 中 Job 和 Task 的本质区别与内在联系。
一、从一个简单例子开始
1.1 需求描述
假设我们需要实现一个实时数据清洗任务:
- 输入:包含大小写混合、带前后空格的字符串流
- 处理:去除前后空格 + 统一转大写
- 输出:清洗后的结果
1.2 完整代码实现
java
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
/**
* Flink Job 与 Task 概念演示
* 功能:字符串大小写转换 + 去除空格
*/
public class JobVsTaskDemo {
public static void main(String[] args) throws Exception {
// 1. 创建执行环境(这是 Job 的起点)
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 设置全局并行度为 2(这会直接影响 Task 的数量)
env.setParallelism(2);
// 2. Source:数据源(会产生 2 个 Task)
DataStream<String> source = env.fromElements(
" hello world ",
" FLINK STREAMING ",
" Java 8 Lambda ",
" DATA PROCESSING "
).name("String Source");
// 3. Transformation 1:去除空格 + 转大写(会产生 2 个 Task)
DataStream<String> upperCaseStream = source
.map(new UpperCaseTrimFunction())
.name("Upper Case Mapper");
// 4. Transformation 2:转小写(会产生 2 个 Task)
DataStream<String> lowerCaseStream = source
.map(new LowerCaseTrimFunction())
.name("Lower Case Mapper");
// 5. Sink:数据输出(会产生 2 个 Task)
upperCaseStream.print("大写结果");
lowerCaseStream.print("小写结果");
// 6. 执行 Job
env.execute("String Case Conversion Job");
}
/**
* 自定义 MapFunction:去除空格并转大写
*/
public static class UpperCaseTrimFunction implements MapFunction<String, String> {
@Override
public String map(String value) throws Exception {
return value.trim().toUpperCase();
}
}
/**
* 自定义 MapFunction:去除空格并转小写
*/
public static class LowerCaseTrimFunction implements MapFunction<String, String> {
@Override
public String map(String value) throws Exception {
return value.trim().toLowerCase();
}
}
}
1.3 执行结果
大写结果> HELLO WORLD
大写结果> FLINK STREAMING
大写结果> JAVA 8 LAMBDA
大写结果> DATA PROCESSING
小写结果> hello world
小写结果> flink streaming
小写结果> java 8 lambda
小写结果> data processing
二、深入理解:Job 是什么?
2.1 Job 的定义
Job(作业) 是用户提交给 Flink 集群的一个完整的应用程序实例。从代码层面看,从 env.execute() 开始到执行结束,整个过程就是一个 Job。
2.2 Job 的组成
一个 Job 通常包含三大部分:
┌─────────────────────────────────────────────────────────┐
│ Job (作业) │
│ ┌──────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ Source │───▶│Transformation│───▶│ Sink │ │
│ │ 数据源 │ │ 转换算子 │ │ 数据汇 │ │
│ └──────────┘ └──────────────┘ └──────────┘ │
│ │
│ 特点: │
│ • 用户直接编写和提交 │
│ • 有唯一的 JobID │
│ • 完整的执行逻辑 │
│ • 可配置并行度 │
└─────────────────────────────────────────────────────────┘
2.3 代码中的 Job
java
// 这一整段就是一个 Job
env.execute("String Case Conversion Job");
// - Job名称:String Case Conversion Job
// - JobID:由 Flink 自动生成(如:f938e93f0ba6ae26ae3edec67eeff151)
// - 生命周期:从提交到完成/取消
三、深入理解:Task 是什么?
3.1 Task 的定义
Task(任务) 是 Flink 运行时将 Job 中的算子按照并行度拆分后的最小并行执行单元。每个 Task 运行在一个独立的线程中。
3.2 Task 的特点
- 物理执行单元:真正干活的线程
- 独立运行:每个 Task 在自己的 Slot 中运行
- 可分布:可以分布在不同的 TaskManager 上
- 数量确定:Task 数量 = Σ(每个算子的并行度)
3.3 Task 数量计算
以上面的代码为例:
java
env.setParallelism(2); // 全局并行度为 2
算子列表:
1. Source (fromElements) → 并行度 2 → 2 个 Task
2. Map (UpperCaseTrimFunction) → 并行度 2 → 2 个 Task
3. Map (LowerCaseTrimFunction) → 并行度 2 → 2 个 Task
4. Sink (print) → 并行度 2 → 2 个 Task
总 Task 数量 = 2 + 2 + 2 + 2 = 8 个 Task
四、Job 与 Task 的核心区别
4.1 对比表格
| 对比维度 | Job(作业) | Task(任务) |
|---|---|---|
| 定义 | 用户提交的完整应用程序 | 算子的并行执行实例 |
| 层次 | 逻辑概念 | 物理概念 |
| 创建者 | 用户编写代码 | Flink 运行时自动创建 |
| 数量 | 1 个 Job | N 个 Task |
| 生命周期 | 从提交到完成 | 随 Job 创建和销毁 |
| 配置方式 | env.execute("job name") |
setParallelism() 控制数量 |
| 故障影响 | Job 失败则整体失败 | 单个 Task 失败可部分重启 |
| 用户感知 | ✅ 直接可见 | ❌ 不直接感知 |
4.2 关系图解
JOB (1个)
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Operator │ │ Operator │ │ Operator │
│ 1 │ │ 2 │ │ 3 │
│ (Source) │ │ (Map) │ │ (Sink) │
└──────────┘ └──────────┘ └──────────┘
│ │ │
┌──────────┼────┐ │ ┌────┼────┐
│ │ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼ ▼
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│Task │ │Task │ │Task │ │Task │ │Task │ │Task │
│ 1-1 │ │ 1-2 │ │ 2-1 │ │ 2-2 │ │ 3-1 │ │ 3-2 │
└─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘
说明:
- 1 个 Job 包含 3 个算子
- 每个算子并行度为 2
- 总共 6 个 Task (2 + 2 + 2 = 6)
五、物理执行架构:JobManager 与 TaskManager
5.1 完整架构图
┌─────────────────────────────────────────────────────────────────────────┐
│ Client (客户端) │
│ 提交 Job: "String Case Conversion Job" │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ JobManager (Master - 管调度) │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 职责: │ │
│ │ • 接收用户提交的 Job │ │
│ │ • 生成 ExecutionGraph(执行图) │ │
│ │ • 调度 Task 到 TaskManager │ │
│ │ • 协调 Checkpoint(容错) │ │
│ │ • 监控 Task 执行状态,故障恢复 │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
┌─────────────────┼─────────────────┐
│ (调度分配) │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ TaskManager 1 (Worker - 干苦力) │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Slot 1 Slot 2 │ │
│ │ ┌─────────────────────────┐ ┌─────────────────────────┐ │ │
│ │ │ Task: Source-1 │ │ Task: UpperCase Map-1 │ │ │
│ │ │ 处理: "hello world" │ │ 处理: "HELLO WORLD" │ │ │
│ │ └─────────────────────────┘ └─────────────────────────┘ │ │
│ │ │ │
│ │ Slot 3 Slot 4 │ │
│ │ ┌─────────────────────────┐ ┌─────────────────────────┐ │ │
│ │ │ Task: LowerCase Map-1 │ │ Task: Sink-1 │ │ │
│ │ │ 处理: "hello world" │ │ 输出: "HELLO WORLD" │ │ │
│ │ └─────────────────────────┘ └─────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ TaskManager 2 (Worker - 干苦力) │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Slot 1 Slot 2 │ │
│ │ ┌─────────────────────────┐ ┌─────────────────────────┐ │ │
│ │ │ Task: Source-2 │ │ Task: UpperCase Map-2 │ │ │
│ │ │ 处理: "FLINK STREAMING" │ │ 处理: "FLINK STREAMING" │ │ │
│ │ └─────────────────────────┘ └─────────────────────────┘ │ │
│ │ │ │
│ │ Slot 3 Slot 4 │ │
│ │ ┌─────────────────────────┐ ┌─────────────────────────┐ │ │
│ │ │ Task: LowerCase Map-2 │ │ Task: Sink-2 │ │ │
│ │ │ 处理: "flink streaming" │ │ 输出: "flink streaming" │ │ │
│ │ └─────────────────────────┘ └─────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
5.2 角色总结
| 组件 | 角色定位 | 核心职责 | 管理对象 |
|---|---|---|---|
| JobManager | Master(管调度) | 调度、协调、容错 | 管理整个 Job |
| TaskManager | Worker(干苦力) | 执行具体计算 | 管理 Slot 和 Task |
| Slot | 资源单元 | 提供运行环境 | 运行一个 Task |
| Task | 具体活 | 执行数据处理 | 处理具体数据 |
六、算子链(Operator Chain)的优化
6.1 什么是算子链?
Flink 会将多个算子合并到一个 Task 中执行,减少线程切换和序列化开销。
java
// 代码示例
source.map(...).filter(...).map(...).print();
// 优化前(假设不合并)
Task1: Source
Task2: Map
Task3: Filter
Task4: Map
Task5: Sink
// 优化后(合并)
Task1: [Source → Map → Filter → Map → Sink] // 一个 Task 搞定
6.2 我们的例子中的算子链
java
// 代码中实际会发生算子链优化
source.map(new UpperCaseTrimFunction()).print();
// Flink 会优化为:
// Task: [Source → UpperCaseTrimFunction → Sink]
// 一个 Task 包含三个算子的串行执行
七、如何观察 Job 和 Task?
7.1 提交 Job 时的控制台输出
bash
$ ./bin/flink run -c JobVsTaskDemo my-job.jar
Job has been submitted with JobID: a1b2c3d4e5f67890
Waiting for job completion...
7.2 Flink Web UI 观察
访问 http://localhost:8081,你可以看到:
┌─────────────────────────────────────────────────────────────┐
│ Job ID: a1b2c3d4e5f67890 │
│ Job Name: String Case Conversion Job │
│ Status: FINISHED │
│ Duration: 00:00:03 │
├─────────────────────────────────────────────────────────────┤
│ Operators (点击可查看 Task 详情): │
│ │
│ ┌─────────────────┬──────────┬──────────┬────────────────┐ │
│ │ Operator Name │ Parallelism│ Subtasks│ Status │ │
│ ├─────────────────┼──────────┼──────────┼────────────────┤ │
│ │ String Source │ 2 │ 2/2 │ FINISHED │ │
│ │ Upper Case Mapper│ 2 │ 2/2 │ FINISHED │ │
│ │ Lower Case Mapper│ 2 │ 2/2 │ FINISHED │ │
│ │ Print to Std.Out│ 2 │ 2/2 │ FINISHED │ │
│ └─────────────────┴──────────┴──────────┴────────────────┘ │
└─────────────────────────────────────────────────────────────┘
八、常见问题 FAQ
Q1:一个 Job 最多可以有多少个 Task?
A:没有硬性上限,取决于:
- 每个算子的并行度设置
- 集群的 Slot 数量
- 可用内存和 CPU 资源
Q2:Task 和 SubTask 是什么关系?
A:它们是同一个概念。SubTask 是 Task 的另一种叫法,强调它是某个算子的"子任务"。
Q3:不同 Job 的 Task 可以在同一个 TaskManager 中吗?
A:可以。TaskManager 是进程级别的资源单元,可以运行来自不同 Job 的 Task,只要 Slot 资源足够。
Q4:如何调整 Task 的数量?
A:通过设置并行度:
java
env.setParallelism(4); // 全局并行度
source.map(...).setParallelism(8); // 单独设置某个算子
九、总结
9.1 核心要点
- Job 是逻辑概念:用户编写的完整应用程序,一个 Job 包含多个算子
- Task 是物理概念:算子的并行执行实例,真正干活的线程
- 数量关系:1 个 Job = N 个 Task(N = Σ各算子并行度)
- 职责分离:JobManager 管调度,TaskManager 干苦力
- 算子链优化:多个算子可能合并到一个 Task 中执行
9.2 一句话记忆
Job 是用户提交的"蓝图",Task 是 Flink 执行的"工人";一个蓝图可以指挥多个工人,工人分布在不同的 TaskManager 中协同完成工作。
9.3 实战建议
- 合理设置并行度,避免 Task 数量过多导致资源浪费
- 通过 Web UI 监控 Task 的执行情况
- 利用算子链优化减少序列化开销
- 根据数据量动态调整 Task 分布
如需获取更多关于 Flink流处理核心机制、状态管理与容错、实时数仓架构 等深度解析,请持续关注本专栏《Flink核心技术深度与实践》系列文章。
