一篇文章带你搞定 Flink 开发环境搭建、作业提交、以及与 Kafka、MySQL 的完整交互

前言
最近在实践 Flink 实时计算,从环境搭建到作业开发,踩了不少坑。今天就把这些经验整理出来,希望能帮助到正在学习 Flink 的你。
本文会涵盖:
-
Docker Compose 一键搭建 Flink + Kafka + MySQL 开发环境
-
Flink 作业的打包与提交
-
从 Kafka 消费数据,实时计算 PV/UV,写入 MySQL
-
常见问题的解决方案
一、为什么选择 Docker Compose?
学习 Flink 最大的痛点就是环境搭建。要装 Hadoop、Kafka、MySQL、Flink 集群...想想就头大。
使用 Docker Compose,一个命令就能启动所有组件:
bash
XML
docker-compose up -d
所有组件都在容器中运行,互不干扰,用完即焚,非常适合本地开发调试。
二、环境配置详解
2.1 docker-compose.yml 核心配置
yaml
XML
services:
# Flink JobManager
jobmanager:
image: flink:1.18.0-scala_2.12-java11
ports:
- "8081:8081" # Flink Web UI
command: jobmanager
volumes:
- ./target:/opt/flink/usrlib # 挂载打包目录
environment:
FLINK_PROPERTIES: |
jobmanager.rpc.address: jobmanager
taskmanager.numberOfTaskSlots: 4
parallelism.default: 4
# Flink TaskManager
taskmanager:
image: flink:1.18.0-scala_2.12-java11
depends_on:
- jobmanager
command: taskmanager
scale: 1
# Kafka (KRaft模式,无需Zookeeper)
kafka:
image: confluentinc/cp-kafka:7.5.0
ports:
- "9092:9092"
environment:
KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092'
# MySQL
mysql:
image: mysql:8.0
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: flink_db
关键点:
-
所有服务在同一个 Docker 网络下,可以通过服务名互相访问
-
./target目录挂载到容器,这样 Flink 可以直接加载我们打的 JAR 包
三、Maven 项目配置
3.1 pom.xml 关键依赖
xml
XML
<properties>
<flink.version>1.18.0</flink.version>
<hadoop.version>3.3.4</hadoop.version>
</properties>
<dependencies>
<!-- Flink 核心依赖,scope 为 provided -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java</artifactId>
<version>${flink.version}</version>
<scope>provided</scope>
</dependency>
<!-- Kafka 连接器 -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka</artifactId>
<version>3.1.0-1.18</version>
</dependency>
<!-- JDBC 连接器(用于 MySQL) -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-jdbc</artifactId>
<version>3.1.2-1.18</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
3.2 Shade 插件配置(Fat JAR)
xml
XML
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<!-- 注意:ManifestResourceTransformer 直接用 mainClass,不要套 resource 标签 -->
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.boonya.flink.UserBehaviorAnalysisJob</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
踩坑提醒: ManifestResourceTransformer 的配置不要画蛇添足加 <resource> 标签,否则会报错 Cannot find 'resource' in class ManifestResourceTransformer。
四、作业开发:用户行为分析
4.1 业务场景
假设我们有用户点击流数据(JSON格式)从 Kafka 进来:
json
java
{
"userId": "10001",
"actionType": "click",
"pageUrl": "/product/123",
"productId": "P001",
"province": "广东",
"timestamp": 1700000000000
}
需要实时统计:
-
PV(页面浏览量):每分钟的访问次数
-
UV(独立访客):每分钟的去重用户数
-
漏斗转化:点击 → 加购 → 支付的转化率
4.2 数据模型定义
java
java
// 原始日志
public class UserActionLog {
public String userId;
public String actionType; // click, add_cart, pay
public String productId;
public Long timestamp;
}
// 统计结果
public class BIStatResult {
public String statType; // PV, UV, FUNNEL
public String dimension;
public Long count;
public Long windowStart;
public Long windowEnd;
}
4.3 核心代码实现
java
java
public class UserBehaviorAnalysisJob {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
// 1. Source: 从 Kafka 读取数据
Properties kafkaProps = new Properties();
kafkaProps.setProperty("bootstrap.servers", "kafka:9092");
kafkaProps.setProperty("group.id", "flink-analysis");
DataStream<UserActionLog> sourceStream = env
.addSource(new FlinkKafkaConsumer<>(
"user_action_logs",
new SimpleStringSchema(),
kafkaProps
))
.map(json -> parseJson(json)) // 解析 JSON
.assignTimestampsAndWatermarks(
WatermarkStrategy.<UserActionLog>forBoundedOutOfOrderness(Duration.ofSeconds(5))
.withTimestampAssigner((log, ts) -> log.timestamp)
);
// 2. 转换: 计算 PV
DataStream<BIStatResult> pvStream = sourceStream
.map(log -> Tuple2.of("pv", 1L))
.keyBy(t -> t.f0)
.window(TumblingEventTimeWindows.of(Time.minutes(1)))
.reduce((t1, t2) -> Tuple2.of(t1.f0, t1.f1 + t2.f1))
.map(t -> new BIStatResult("PV", null, t.f1, getWindowStart(), getWindowEnd()));
// 3. 转换: 计算 UV
DataStream<BIStatResult> uvStream = sourceStream
.keyBy(log -> log.userId)
.window(TumblingEventTimeWindows.of(Time.minutes(1)))
.process(new ProcessWindowFunction<UserActionLog, BIStatResult, String, TimeWindow>() {
@Override
public void process(String key, Context ctx,
Iterable<UserActionLog> elements,
Collector<BIStatResult> out) {
BIStatResult result = new BIStatResult();
result.statType = "UV";
result.count = 1L;
result.windowStart = ctx.window().getStart();
result.windowEnd = ctx.window().getEnd();
out.collect(result);
}
})
.keyBy(r -> r.windowEnd)
.windowAll(TumblingEventTimeWindows.of(Time.minutes(1)))
.sum("count");
// 4. Sink: 写入 MySQL
pvStream.addSink(createMySQLSink("bi_pv_stats"));
uvStream.addSink(createMySQLSink("bi_uv_stats"));
env.execute("User Behavior Analysis");
}
private static SinkFunction<BIStatResult> createMySQLSink(String tableName) {
return JdbcSink.sink(
"INSERT INTO " + tableName + " (stat_type, count, window_start, window_end) VALUES (?, ?, ?, ?)",
(stmt, result) -> {
stmt.setString(1, result.statType);
stmt.setLong(2, result.count);
stmt.setLong(3, result.windowStart);
stmt.setLong(4, result.windowEnd);
},
JdbcExecutionOptions.builder().withBatchSize(1000).build(),
new JdbcConnectionOptions.JdbcConnectionOptionsBuilder()
.withUrl("jdbc:mysql://mysql:3306/flink_db")
.withUsername("root")
.withPassword("root")
.build()
);
}
}
五、作业提交的三种方式
5.1 方式一:Web UI 提交
访问 http://localhost:8081,进入 Submit New Job 页面:
-
点击 "Add New" 上传 JAR 包
-
填写 Entry Class(主类全限定名)
-
点击 Submit
5.2 方式二:docker exec 命令(推荐)
bash
bash
# 复制 JAR 到容器
docker cp target/bigdata-flink-1.0.0-SNAPSHOT.jar jobmanager:/opt/flink/usrlib/
# 提交作业
docker exec jobmanager flink run \
-c com.boonya.flink.UserBehaviorAnalysisJob \
/opt/flink/usrlib/bigdata-flink-1.0.0-SNAPSHOT.jar
5.3 方式三:一次性提交容器
在 docker-compose.yml 中添加:
yaml
bash
job-submitter:
image: flink:1.18.0-scala_2.12-java11
depends_on:
jobmanager:
condition: service_healthy
volumes:
- ./target:/opt/flink/usrlib
command: >
bash -c "
sleep 10 &&
flink run -d \
-c com.boonya.flink.UserBehaviorAnalysisJob \
/opt/flink/usrlib/bigdata-flink-1.0.0-SNAPSHOT.jar
"
然后执行:
bash
bash
docker-compose up job-submitter
六、常见踩坑与解决方案
6.1 JobManager 无法启动(目录权限问题)
错误: Directory is in an inconsistent state: storage directory does not exist or is not accessible
解决:
bash
bash
# 手动创建目录并格式化
docker exec -it jobmanager bash
mkdir -p /tmp/hadoop-root/dfs/name
hdfs namenode -format -force
exit
docker-compose restart jobmanager
6.2 Shade 插件报错
错误: Cannot find 'resource' in class ManifestResourceTransformer
原因: 配置了多余的 <resource> 标签
正确配置:
xml
XML
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.boonya.flink.YourJob</mainClass>
</transformer>
6.3 Git 推送失败(证书问题)
错误: error setting certificate verify locations
解决(Windows):
bash
bash
git config --global http.sslBackend schannel
6.4 Git 推送失败(目录所有权问题)
错误: detected dubious ownership in repository
解决:
bash
bash
git config --global --add safe.directory D:/code/bigdata
6.5 作业提交后容器退出
现象: job-submitter 容器运行后就退出
解释: 这是正常行为。一次性任务容器执行完命令就会退出,不影响已提交的 Flink 作业。
如果希望保留容器,可以添加循环:
yaml
bash
command: bash -c "flink run ... && tail -f /dev/null"
七、验证与监控
7.1 查看作业状态
bash
bash
# 查看 Flink Web UI
http://localhost:8081
# 查看容器日志
docker-compose logs -f jobmanager
docker-compose logs -f taskmanager
# 查看已提交的作业
docker exec jobmanager flink list
7.2 在 MySQL 中查询结果
sql
sql
-- 进入 MySQL 容器
docker exec -it mysql mysql -uroot -proot
-- 切换数据库
USE flink_db;
-- 查看 PV 统计
SELECT * FROM bi_pv_stats ORDER BY window_start DESC LIMIT 10;
-- 查看 UV 统计
SELECT * FROM bi_uv_stats ORDER BY window_start DESC LIMIT 10;
八、总结
通过本文,我们完成了:
| 阶段 | 内容 |
|---|---|
| 环境搭建 | Docker Compose 一键启动 Flink + Kafka + MySQL |
| 项目配置 | Maven 依赖配置和 Shade 插件打包 |
| 作业开发 | 实时计算 PV/UV,从 Kafka 读取,写入 MySQL |
| 作业提交 | Web UI、docker exec、一次性容器三种方式 |
| 踩坑记录 | 目录权限、插件配置、Git 问题等解决方案 |
核心要点:
-
Source:从 Kafka 消费数据
-
Transform:使用 KeyBy + Window 进行聚合计算
-
Sink:将结果写入 MySQL
-
网络 :Docker 容器间通过服务名互通(如
kafka:9092、mysql:3306)
掌握了这些,你就具备了开发 Flink 实时计算任务的基础能力。接下来可以尝试更复杂的场景:
-
多流 Join(如订单流和支付流关联)
-
状态编程(如检测用户异常行为)
-
Flink SQL 简化开发
扩展阅读:
本文适合:大数据工程师 数据仓库 数据湖 实时计算