本文为前端开发者转型大数据的实战学习日志,系统梳理
- Flink环境搭建(WSL2+IDEA+Maven)
- 开发规范(项目结构、Maven管理)
- 运行模式(本地彩排与集群提交)
- 网络调试(nc、端口冲突排查)
- JVM基础(内存模型、GC、类加载)
强调"先clean后package"、避免依赖冲突、理解FatJar与provided范围的重要性,并结合前端思维类比V8与JVM,助力高效跨域进阶。
这份学习日志专为**"前端转大数据"**量身定制,涵盖了从环境踩坑到底层原理的完整心路历程。按知识模块分门别类,适合作为备忘录或博客分享。
📚 Flink 学习日志:前端转大数据的实战避坑指南
一、 环境搭建与版本控制(地基篇)
核心原则:脱离教程环境(Mac),构建统一的本地生态(Win11 + WSL2 + IDEA)。
-
工具选型 :放弃 Docker(难调式)和 PyFlink(不符教程),选择 WSL2 本地安装。WSL2 的 Ubuntu 提供了与生产环境高度一致的 Linux 体验。
-
版本对齐 :教程用 Flink 1.15,且后续包含 Hive 3.1.2(仅支持 Java 8),因此果断将 WSL2 默认 Java 切回 1.8,并下载
flink-1.15.4。 -
环境隔离 :IDEA 是 Windows 应用,但配置其使用的 Maven 和 JDK 为 WSL2 内的版本,避免路径格式冲突(
C:\vs/mnt/)。
二、 IDEA 开发实操与 Maven 管理
核心认知:IDEA 是强工程化工具,不是简单的文件夹打开器。
-
层级关系 :必须理清
项目(Project) > 模块(Module) > 包(Package) > 类(Class)。写代码必须在src/main/java下建"Package",放配置在resources下建"Directory"。 -
Maven 依赖 :添加
flink-runtime-web依赖后,必须点击 "重新加载所有 Maven 项目"(红色循环箭头) 强制同步。 -
打包防坑 :执行
package前,务必开启 "跳过测试模式" (闪电图标),并先执行clean,确保打出纯净的 Fat Jar。
Fat Jar(胖包 / Uber Jar) ,就是把你自己写的代码 + 你依赖的所有第三方 jar 包(比如各种 connector、工具类) ,全部解压并合并塞进一个超级大的 jar 包里。
🍔 前端思维类比
普通 Jar 包 :就像你只写了业务代码(几十 KB),没有打包
node_modules。扔到服务器上,一运行就报Module not found。Fat Jar(胖包) :就像你用 Webpack/Vite 跑了一遍
npm run build,把业务代码和所有依赖的库全部打包进了dist文件夹(几十 MB)。扔到任何地方,解压就能直接跑,不需要额外装环境。⚖️ 普通 Jar vs Fat Jar
维度 普通 Jar 包 Fat Jar(胖包) 内容 只有你写的 .class和配置文件你的代码 + 所有第三方依赖包(如 Flink 核心包、Kafka 客户端等) 体积 极小(几十 KB) 很大(几十 MB 到上百 MB) 运行环境 必须配合完整的类路径( -cp)或部署在完整的框架里随时随地 java -jar独立运行
🔍 怎么看打出来的包是不是 Fat Jar?(3种方法)
方法一:看体积(最直观)
去 IDEA 的
target目录下看打出来的 jar 包。如果只有几 KB 或十几 KB,那是普通包;如果是几十 MB 甚至更大,那就是 Fat Jar。
方法二:用解压软件打开看
Jar 包本质就是个 ZIP 压缩包。你可以用 Windows 的 7-Zip 或 Bandizip 打开它:
如果里面只有
com/pk/flink/...(你写的代码),那就是普通包。如果里面除了你的代码,还有一大堆
org/apache/flink/...、com/google/...、org/slf4j/...等文件夹,那它就是 Fat Jar。方法三:看
META-INF/MANIFEST.MF文件(最核心)解压 jar 包,找到
META-INF/MANIFEST.MF文件,用记事本打开:
如果里面有
Main-Class: com.pk.flink.basic.FlinkSocketWCApp这一行,说明它不仅是个 Fat Jar,还指定了入口类。这种包提交给 Flink 时,可以省略-c参数。如果没有这一行,说明没配置主类,提交时必须手动写
-c com.pk.flink.basic...。
💡 Flink 中的避坑大法
在 Flink 开发中,打 Fat Jar 通常使用
maven-shade-plugin。
特别注意 :Flink 自己的核心依赖(如
flink-streaming-java、flink-clients)在你的pom.xml里必须加<scope>provided</scope>。
这意味着:"打包的时候别把 Flink 的核心包打进去,集群上已经有了。"如果不加,你的 Fat Jar 会和集群自带的 Flink 发生严重冲突,导致启动失败。
在哪个 pom.xml 里加<scope>provided</scope>,项目根目录的 pom.xml,还是模块的 pom.xml?
加在你实际写 Flink 业务代码的那个子模块的
pom.xml里 (比如你之前截图的pk-flink-basic/pom.xml)。
💡 为什么加在子模块,而不是根目录?
子模块的
pom.xml:定义了当前模块具体需要什么依赖 。因为你是在pk-flink-basic里写了 Flink 代码,所以这个模块在编译时需要 Flink 的 API。根目录的
pom.xml(父工程) :通常只负责统一管理版本号 (比如定义<flink.version>1.15.4</flink.version>),它自己不写业务代码,所以不需要引入具体的依赖。
⚠️ 为什么必须加
<scope>provided</scope>?(前端思维类比)
Flink 集群:就像是已经装好了 Node.js 运行环境(包含 Flink 核心包)的服务器。
你的代码:就像是你的业务代码。
不加
provided(默认是compile) :你打包时会把 Flink 核心包也塞进你的 Fat Jar 里。提交到集群后,你的 Jar 包里的 Flink 类和集群自带的 Flink 类同名打架 ,直接报ClassCastException或NoSuchMethodError。加了
<scope>provided:相当于告诉 Maven:"编译代码的时候需要用到它,但打包的时候千万别把它带上,因为集群上已经有了。"
🛠️ 具体怎么改?(推荐的企业级标准做法)
第一步:在父工程(根目录的
pom.xml)里统一管理在根目录的
<dependencyManagement>中定义好版本和scope。这样所有子模块都不用重复写版本号了:xml
XML<!-- 根目录的 pom.xml --> <dependencyManagement> <dependencies> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-streaming-java</artifactId> <version>${flink.version}</version> <scope>provided</scope> <!-- 在这里统一定义 --> </dependency> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-clients</artifactId> <version>${flink.version}</version> <scope>provided</scope> </dependency> </dependencies> </dependencyManagement>第二步:在子模块(
pk-flink-basic/pom.xml)里引入依赖在子模块里引入时,不需要写版本号和
scope,它会自动继承父工程的配置:xml
XML<!-- pk-flink-basic/pom.xml --> <dependencies> <!-- 不需要写 version,也不写 scope,自动继承父工程 --> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-streaming-java</artifactId> </dependency> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-clients</artifactId> </dependency> <!-- 注意:你之前加的 flink-runtime-web 通常不用加 provided --> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-runtime-web</artifactId> <version>${flink.version}</version> </dependency> </dependencies>🚨 特例:什么时候不 加
provided?你代码里用来做本地测试的 WebUI 依赖(
flink-runtime-web),通常不加provided。因为有些集群默认没带这个 UI 模块,你需要把它打进包里。但核心的flink-clients、flink-streaming-java等,一定要加provided。

如果不开启"跳过测试模式",直接执行打包(比如双击
package或install),大概率会经历一次"打包失败"的折磨,但这取决于你的项目里有没有测试代码。
Maven 的设计非常健壮,它允许你一天打包 100 次。但在实际效果上,取决于你加没加
clean命令,会有两种不同的情况:情况一:不加
clean,直接重复打包(mvn package)情况二:加上
clean再打包(mvn clean package)------ 👑 标准做法
既然你在学 Flink,经常需要打包 jar 提交到集群。为了避免遇到灵异 Bug:
每次打包养成顺手点一下
clean的习惯 。在 IDEA 的 Maven 面板里,先双击clean,再双击package。或者直接在终端里敲mvn clean package -DskipTests。看准
target目录 :打包成功后,所有的产物(包括最终 fat-jar)都在target目录下。如果你觉得打出来的包有问题,直接右键把target文件夹删掉,重新打一次,能解决 80% 的玄学报错。总结 :随意重复打包,电脑扛得住。但为了代码的纯净,"先 clean,后 package" 是每个 Java 开发者的肌肉记忆。
当你双击
package后,注意看 IDEA 底部弹出的 Build 窗口 ,如果最后显示绿色的BUILD SUCCESS,就说明打包成功了。
打出来的 jar 包通常在这里:
pk-flink-basic目录下的target文件夹里(名字大概叫pk-flink-basic-1.0.jar)。你后续提交到 Flink 集群的,就是这个东西。
Flink 学习需要具备的 Java 基础知识总结-CSDN博客
Java 3 天入门:"最小必要知识"的功利性学法-CSDN博客
IntelliJ IDEA 2026.1.3 极简入门指南(新建项目、新建模块、新建包、新建类):以构建 Flink 应用程序(IDEA+Maven、Flink官方提供的命令)为例-CSDN博客
IntelliJ IDEA 补充篇:环境与终端避坑指南:Win11 + WSL2(nc -lk 9527)+ IDEA,禁用Kotlin插件解决无法复制形参-CSDN博客
在 IDEA 本地运行环境中开启 Flink WebUI
只需三步:
1. 引入 WebUI 依赖(pom.xml)
必须引入运行时 Web 模块,否则启动会报错
NoClassDefFoundError:xml
XML<dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-runtime-web</artifactId> <version>1.15.4</version> </dependency>2. 修改代码指定端口
不要用默认的
getExecutionEnvironment(),改用以下代码,并设定一个不冲突的端口(如 8082):java
javaimport org.apache.flink.configuration.Configuration; Configuration conf = new Configuration(); conf.setInteger("rest.port", 8082); // 避免与本地已启动的 8081 集群冲突 StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(conf);3. 运行并访问
运行程序后,在浏览器访问
http://localhost:8082即可看到本地 MiniCluster 的 WebUI 界面。注意:程序停止后,UI 就无法访问了。
三、 Flink 运行模式与生命周期(集群篇)
在 IDEA 里运行是"本地彩排",提交到集群是"正式演出"。
你用前端思维来理解:
IDEA 里运行(Run) :就像是你在本地用
npm run dev起了一个本地服务,只要你在 IDEA 里点了红色方块(Stop),程序就彻底死掉了。提交到 Flink 集群(Submit) :就像是前端项目打包后,推送到正式的云服务器(比如 K8s 或 Docker)上跑。一旦提交成功,你就算把 IDEA 关掉,把电脑合上,只要集群没断电,这个 Flink 程序就会 24 小时一直跑着。
下面我们详细拆解一下这背后的区别:
1. 到底有什么区别?
维度 在 IDEA 中运行 / 取消 提交 / 取消 Flink 应用 运行位置 直接在你的 WSL2 内存里跑(使用的是本地的 MiniCluster)。提交给独立的 JobManager和TaskManager集群去跑。生命周期 绑死在 IDEA 上。点停止,或者关掉 IDEA,程序就终止。 独立运行。提交后,哪怕你关掉 IDEA、甚至关掉电脑,集群里的程序依然在运行。 资源分配 只能使用你电脑当前的内存和 CPU。 由集群统一调度,可以使用几十台服务器的资源。 适用场景 本地开发、写代码、断点调试。 生产环境、服务器上真实的定时任务或流式任务。 2. 为什么要有"提交"这个概念?
你在 IDEA 里写完代码,点"运行"成功了,那只是测试通过了。真实的企业场景中:
你需要把代码打成
.jar包(用我们之前说的 Mavenpackage)。把包提交给真正的集群,让成百上千个 TaskManager 去分布式的读取和处理数据。
3. 为什么要"取消"?
因为一旦提交到集群,程序就是 7x24 小时无休止运行的。如果你发现业务逻辑写错了,或者不需要跑了,如果不"取消",它就会一直占用集群的 CPU 和内存资源 ,把公司的服务器资源吃光。
所以,取消作业就是告诉 JobManager:"把这个任务的资源释放掉,别跑了。"
4. 什么时候需要用到?
在 IDEA 里:写代码、改 Bug、跑个测试数据看看效果。
在集群里(WebUI/命令行):把写好的完美代码打包扔上去,每天看它自动处理数据。如果你在集群的 WebUI 里发现数据算错了,就去点 Cancel 取消掉它。
总结 :你之前一直用
nc+IDEA 运行来测试,是因为你还在开发阶段。跟着老师学到第 3 章,就是在教你怎么把写好的程序,脱离 IDEA,独立运行在真正的 Flink 集群里。
核心认知:IDEA 运行是"本地彩排",集群提交是"正式演出"。
-
本地运行(IDEA) :使用
createLocalEnvironmentWithWebUI启动 MiniCluster,指定8082端口(避开原集群 8081),随点随停,适合 Debug。 -
集群提交(WebUI/命令行):
-
命令行:
flink run -d -c <主类> <jar包路径>(-d为后台分离模式)。 -
WebUI:在 8081 端口上传 Jar 包,填入 Main Class 提交。
-
-
作业取消 :用
flink cancel <JobID>或 WebUI 点击 Cancel,释放集群资源。Jar 包不需要提前传到 HDFS,Flink 客户端会自动分发。

基于你之前写的代码(
FlinkSocketWCApp),在 WebUI 上提交作业时,最关键的是要告诉 Flink 程序的入口在哪 ,以及需要什么参数。
按照下面的指南填写即可:
📝 核心填写指南
1. Entry Class(入口类) ------ 必填 🔴
填什么 :
com.pk.flink.basic.FlinkSocketWCApp说明 :告诉 Flink 从哪个类的
main方法开始执行。必须写全限定名 (包名+类名)。如果不填,Flink 会去读 jar 包里的META-INF/MANIFEST.MF,如果你打包时没配置主类,就会报错No Main-Class found。2. Program Arguments(程序参数) ------ 选填 🟡
填什么 :
--host localhost --port 9527说明 :这对应你代码里的
ParameterTool。
如果你填了,Flink 会把这两个参数传给你的程序。
如果你不填 ,你的代码里写了默认值,它也会自动用
localhost和9527,一样能跑通。建议填上,练习参数传递。3. Parallelism(并行度) ------ 选填 🟡
填什么 :
1说明:因为你的本地集群默认只有 1 个 Task Slot(可以看左侧 Task Managers 里 Available Slots 是 1)。如果不填,默认就是 1;如果填了大于 1 的数字,会报资源不足的错误。
4. Savepoint Path(保存点路径) ------ 留空 ⚪
- 说明:这是容错恢复用的。你第一次跑这个作业,没有历史状态,留空即可。
5. Allow Non Restored State ------ 不勾选 ⚪
- 说明:新手不用管,留空。
⚠️ 提交前极其重要的前置动作
不要直接点 Submit!
打开你的 WSL2 终端。
确保你已经执行了
nc -lk 9527并且光标在等待输入。🚀 提交与验证
填好
Entry Class和Program Arguments后,点击 Submit。页面会自动跳到 Running Jobs 。你会看到一个状态为
RUNNING的作业,名字叫FlinkSocketWCApp--作业名字。怎么查看输出? 因为是通过集群跑,IDEA 控制台不会有输出。你需要点击左边栏的 Task Managers -> 点击里面唯一的那个 TaskManager -> 点击 Stdout 标签页。
回到 WSL2 终端,输入
pk,pk,pk,你就能在 WebUI 的 Stdout 页面看到实时的词频统计输出了!
提交/取消自己开发的Flink应用程序,WebUI 和命令行两种方式
这里为你简略总结 Flink 应用程序的提交与取消方式(前提:已通过 Maven 打包生成
.jar文件,假设集群 WebUI 端口为 8081)。
🚀 提交应用程序
1. 命令行方式
在 WSL2 终端执行
flink run命令:bash
# 基础提交(前台运行,Ctrl+C 会取消作业) flink run -c com.pk.flink.basic.FlinkSocketWCApp /path/to/pk-flink-basic-1.0.jar # 分离模式提交(后台运行,最常用) flink run -d -c com.pk.flink.basic.FlinkSocketWCApp /path/to/pk-flink-basic-1.0.jar注:
-c指定主类全限定名;如果打的是 Fat Jar 且配置了Main-Class,可省略-c。
2. WebUI 方式
浏览器打开
http://localhost:8081。点击左侧 Submit New Job -> 点击右上角 + Add New 上传 jar 包。
选中上传的 jar 包,在 Entry Class 填入主类全限定名。
在 Program Arguments 填入所需参数(如
--host localhost --port 9527)。点击 Submit 提交。
🛑 取消应用程序
1. 命令行方式
需要先知道作业的
JobID(可通过flink list查看):bash
flink cancel <JobID>2. WebUI 方式
浏览器打开
http://localhost:8081。点击左侧 Running Jobs。
找到正在运行的作业,点击右侧的 Cancel 按钮。
Copy Reference(复制引用)是 IntelliJ IDEA 中一个非常实用的功能,用于快速复制代码元素(如类、方法、字段)的完全限定名(Fully Qualified Name)。
🎯 功能与效果
将光标放在类、方法或字段上执行此操作,它会将完整的引用路径复制到剪贴板:
Java 类 :
com.pk.flink.basic.FlinkSocketWCApp(包名.类名)Java 方法 :
com.pk.flink.basic.FlinkSocketWCApp#main(java.lang.String[])(全类名#方法签名)文件行 :如果光标在非代码元素的行上,可能会复制出类似
路径/文件名.java:行号的格式。
在 IDEA 顶部菜单栏点击
Edit,在下拉菜单中直接选择Copy Reference。
Flink 代码跑在 IDEA 里,尝试连接 Kafka?Kafka 装在 WSL2 中,怎么连接?-CSDN博客
四、 Linux 基础与网络测试
-
WSL2 重启大法 :遇到端口占用、网络不通或卡死,在 Windows PowerShell 执行
wsl --shutdown。 -
Netcat 神器 :
nc -lk 9527用于模拟 Socket 数据源。必须先启动监听,再运行 Flink,最后输入数据,否则数据会丢失(发给空气)。 -
端口冲突排查 :使用
jps查看残留 Java 进程,用kill -9 <PID>强杀。
Caused by: java.net.BindException: Could not start rest endpoint on any port in port range 8081
核心原因:端口冲突(8081 被占用了)。
🛠️ 解决步骤(按顺序执行)
第一步:杀掉所有残留的 Flink 进程
打开 WSL2 终端,把之前残留的进程全部干掉:
bash
# 查看当前所有 Java 进程 jps # 如果有 FlinkSocketWCApp、StandaloneSessionClusterEntrypoint、TaskManagerRunner # 把它们对应的 PID 全部杀掉(比如你之前的 827359,或者其他看到的数字) kill -9 <PID1> <PID2>(如果嫌麻烦,可以直接在 Windows PowerShell 里执行
wsl --shutdown,然后重新打开终端,这招能瞬间清空所有后台进程。)
第二步:强制 IDEA 重新编译
回到 IDEA,点击顶部菜单栏:
Build->Rebuild Project(重新构建项目)。
这一步绝对不能省,确保你改了 8082 的代码真正生效了。
第三步:换个"绝对冷门"的端口
为了防止再次被占用,建议直接把代码里的端口改大一点,比如 8888:
java
Configuration configuration = new Configuration(); configuration.setInteger("rest.port", 8888); StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(configuration);
第四步:重新运行
再次在 IDEA 点击运行。如果看到
Starting rest endpoint.并且没有报错,就成功了。浏览器访问http://localhost:8888就能看到 WebUI。
💡 验证到底是谁占用了端口(进阶排查)
如果你想知道 8081 到底被谁占用了,可以在 WSL2 终端敲:
bash
bashsudo lsof -i :8081或者
bash
bashss -tlnp | grep 8081你会看到具体的进程名和 PID,找到后
kill -9 <PID>即可。
总结 :这种
BindException是大数据开发最常见的错误之一。养成一个好习惯:每次在 IDEA 里跑带 WebUI 的代码之前,先确保之前跑的程序已经点红色方块彻底停掉了。
如果遇到关不掉的,直接在终端
kill -9。
五、 Java 底层基础补充(进阶篇)
JVM(Java 虚拟机)
🎯 为什么 JVM 是学 Flink 的"重要基础"
Flink 的 JobManager 和 TaskManager,本质上都是 Java 进程,运行在 JVM 之上。
这意味着,你在 Flink 里做的很多事,最后都会落到 JVM 上:
内存配置:你给 TaskManager 设置的内存大小,最终会转化为 JVM 的堆内存(Heap)和堆外内存(Off-Heap)。
性能调优 :Flink 作业卡顿,很多时候需要调整 JVM 的垃圾回收(GC) 策略,或者排查内存泄漏。
本地调试 :你现在在 IDEA 里直接
Run的 Flink 程序,就是跑在一个本地 JVM 里的"迷你集群"。如果不懂 JVM,遇到
OutOfMemoryError或者 GC 导致的延迟飙升时,就很难定位问题。
📚 给你的学习路径建议
作为前端出身,你不需要像科班 Java 开发那样一上来就啃透 JVM 底层。跟着 Flink 课程,按需了解这几个模块就够了:
内存模型 :搞清楚堆(Heap)和栈(Stack)的区别,以及 Flink 的堆外内存(Direct Memory)为什么重要。
垃圾回收(GC):知道什么是 Minor GC / Full GC,以及为什么 Full GC 会导致 Flink 作业"卡住"甚至超时。
类加载机制 :了解 Flink 的
UserCodeClassLoader是怎么隔离你的代码和 Flink 框架代码的(这对解决ClassNotFoundException很关键)。
建议你先从理解 JVM 内存模型 和 GC 基础开始,能把这些概念和你正在写的 Flink 程序对应起来,就达到老师说的"基础"要求了。
核心认知:Flink 底层跑在 JVM 上,理解内存是调优的前提。
Jar 包本质上就是一个 ZIP 压缩包 ,后缀名改成了
.jar(Java ARchive)。里面装的是你写的代码编译后的字节码(.class)、配置文件、以及可能包含的第三方依赖。
📦 Jar 包里到底装了什么?
你写的
.java文件是给人看的,计算机不认识。Java 虚拟机(JVM)只认识.class字节码文件。当你点击 Maven 的
package打包时,Maven 会做这几件事:
把
src/main/java下的所有.java代码编译 成.class文件。把
src/main/resources下的配置文件(如log4j2.properties)拷贝过来。把这一大堆东西,按照原本的目录结构(比如
com/pk/flink/basic/FlinkSocketWCApp.class),塞进一个压缩包里,命名为pk-flink-basic-1.0.jar。
❓ 为什么必须打包?(为什么不直接跑代码?)
这就好比你问:"我前端写完代码,为什么不能直接把整个项目文件夹发给客户,非要跑一遍
npm run build打成一个dist包?"
原因有三个:
1. 脱离开发环境运行
你在 IDEA 里能跑代码,是因为 IDEA 帮你做了编译 、找依赖 、配置启动参数 等脏活累活。
真实的企业集群(比如 Flink 的 TaskManager 节点)上,根本没有 IDEA,也没有你的源代码,只有一个纯纯的 Java 运行环境(JRE)。你只能把打包好的 Jar 扔给它,它才能直接运行。
2. 解决依赖地狱(这就是前端最熟悉的 node_modules)
你的 Flink 代码可能用到了 Jackson 解析 JSON,用到了 Log4j 打日志。打包时(特别是打 Fat Jar / Uber Jar ),Maven 会把所有你依赖的第三方 Jar 包,全部分解并合并到你自己的 Jar 包里 。
这样你提交作业时,只需要上传这一个大文件,集群就能正常跑了。
3. 统一交付标准
Jar 包是 Java 世界绝对的标准。无论你是用 Mac、Win11 还是 Linux,打出来的 Jar 包都一样。你可以把它当做一个可执行的工具 ,通过命令行随时运行(
java -jar xxx.jar)。
🚀 结合 Flink 的实战场景
当你学完后面几章,准备把代码提交到真实的集群时:
你在 WSL2 里执行
mvn clean package,在target目录下生成了一个pk-flink-basic-1.0.jar。你在 Flink WebUI 上点击"上传",选中这个 Jar 包。
集群会下载这个 Jar 包,解压,找到里面的
FlinkSocketWCApp.class运行。
💡 一个非常关键的避坑点:Fat Jar 与
provided前端里,像 React、Vue 这种库,通常打包时会被抽离(不打进
dist里),由 CDN 或者宿主机提供。
在 Java 里,Flink 自己的核心包(
flink-streaming-java等)你在pom.xml里必须加上<scope>provided</scope>。
为什么? 因为如果你把 Flink 框架本身的类也打进了你的 Jar 包,提交到集群时,你的 Jar 包和集群自带的 Flink 类就会发生严重冲突,直接报
ClassNotFoundException或NoSuchMethodError。provided的意思就是:"打包时别带上我,集群里有"。
总结 :Jar 包就是你的 Java 代码打完包后的"最终产物"。等你以后打包遇到红字
BUILD FAILURE时,记得回来看这篇。
-
Jar 包本质 :就是 ZIP 压缩包(类似前端的
dist),包含编译后的.class和依赖。打包时需注意provided范围,避免与集群框架冲突。 -
JVM 内存模型:
-
堆(Heap) :存
new出来的对象(类似 V8 的引用类型)。 -
栈(Stack):存方法局部变量(类似 JS 调用栈)。
-
堆外内存(Direct Memory):🔥 Flink 重度使用,绕过 GC 提升性能。
-
-
STW 隐患:垃圾回收时的 Stop-The-World 会导致线程停顿,若超时会引起 Flink 心跳失败。G1 是 Flink 推荐的回收器。
V8 是 Google 开发的、用于执行 JavaScript 代码的高性能引擎。
简单来说,它就是 JavaScript 的"发动机":
跑在哪里:Chrome 浏览器和 Node.js 底层用的都是它。
干了什么:你写的 JS 代码计算机看不懂,V8 负责把它编译成机器能直接执行的机器码,并自动管理内存(也就是你在前端排查过的内存泄漏和垃圾回收)。
呼应前文 :前端代码由 V8 引擎 负责跑;Java/Flink 代码由 JVM(Java 虚拟机) 负责跑。两者都是"代码翻译官 + 内存大管家"。
🗺️ JVM 最小知识图谱
分支一:内存结构(东西存在哪?)
堆 (Heap) :存放所有
new出来的对象(如Tuple2、字符串)。受-Xmx限制 。如果这里满了,报OutOfMemoryError: Java heap space。栈 (Stack):存放方法的局部变量和调用记录。每个线程独立(Flink 的每个 Task 都在一个线程里跑)。
元空间 (Metaspace):存放类的定义、方法代码。
🌟 堆外内存 (Direct Memory) :Flink 最核心的优化点 。Flink 大量使用堆外内存做网络传输和状态存储,绕过 JVM 的垃圾回收,避免卡顿。如果这里满了,报
OutOfMemoryError: Direct buffer memory。分支二:垃圾回收 GC(谁来打扫卫生?)
分代回收:对象先放"新生代",活久了移到"老年代"。
🌟 STW (Stop-The-World) :垃圾回收时,所有工作线程都要暂停。如果 STW 时间过长,Flink 任务会延迟,甚至导致 TaskManager 心跳超时被踢出集群。
G1 回收器:Flink 官方推荐的垃圾回收器,特点是 STW 时间可控。
分支三:类加载机制(代码怎么跑起来的?)
双亲委派:Java 默认的类加载规则(先找爹加载,爹没有才自己加载)。
🌟 Flink 的类加载隔离 (UserCodeClassLoader) :因为一个集群跑多个人的代码,Flink 打破了双亲委派,实现了隔离。如果你打包时依赖冲突(比如你引入了不同版本的 Kafka 客户端),就会在这里报
ClassNotFoundException。分支四:核心调优参数(怎么调大小?)
-Xms和-Xmx:初始和最大堆内存(避免频繁扩容/缩容)。
-XX:+UseG1GC:启用 G1 回收器。
-XX:MaxDirectMemorySize:限制堆外内存大小。
💡 学习减负指南
你不需要了解:
JVM 字节码指令集(如
iload_0,astore_1)。垃圾回收底层的三色标记法、卡表(Card Table)。
如何用 C++ 写一个 JVM。
你在 Flink 开发中会遇到的真实场景:
Flink 作业卡顿:去 WebUI 看 GC 日志,发现是频繁 Full GC 导致的 STW。
打包冲突 :作业提交报错
ClassNotFoundException,知道是类加载隔离导致的依赖没打进去。内存溢出 :看到 OOM,能分清是堆内存(
Java heap space)不够,还是堆外内存(Direct buffer)漏了。
把这个图谱存下来,下次老师讲到 JVM 的时候,你脑海中直接映射到这几块,就不会觉得抽象了。
🗺️ 进阶版 JVM 与 Flink 知识图谱
模块一:运行时数据区(内存到底长啥样?)
你不仅要背出分区,还要知道它们在 Flink 里的特殊表现。
堆(Heap):
结构:新生代(Eden + Survivor S0/S1)、老年代。
Flink 关联 :你代码里
new的对象都在这里。注意,Flink 的 State(状态)不一定在堆里!如果使用 RocksDB 作为 State Backend,状态是存在堆外(甚至磁盘)的,只有读出来处理的那一瞬间才短暂进入堆内存。虚拟机栈(VM Stack):
结构:栈帧(局部变量表、操作数栈、动态链接、方法返回地址)。
Flink 关联 :Flink 的每个并行子任务(SubTask)都是一个独立的线程。你的业务代码是在某个线程的栈里执行的 。如果发生递归调用或嵌套过深,会抛
StackOverflowError。方法区 / 元空间(Metaspace):
结构:在 Java 8 之后,移到了本地内存(元空间),存放类元数据、常量池。
Flink 关联 :Flink 采用
UserCodeClassLoader加载你的代码。如果你频繁提交带大量自定义类的作业,或者热更新作业,元空间会不断膨胀,导致Metaspace OOM。🔥 直接内存(Direct Memory / Off-Heap):
前端类比 :Node.js 的
Buffer或浏览器的ArrayBuffer,不归 V8 管,不参与 V8 的 GC。Flink 关联(极重要) :Flink 在网络传输(Netty Buffer)和管理状态时,为了避免 JVM 堆内对象过多导致 GC 停顿,重度使用了 Direct Memory 。如果分配超限,报错:
java.lang.OutOfMemoryError: Direct buffer memory。
模块二:垃圾回收(GC:如何优雅地打扫战场?)
大数据场景下,内存通常很大(几十G),GC 策略极其重要。
对象生死判定:可达性分析算法(从 GC Roots 往下找,找不到的就可以回收)。
回收算法:
复制算法:新生代使用。速度快,但浪费空间。
标记-清除/整理:老年代使用。会产生内存碎片或导致移动对象耗时。
STW(Stop-The-World):
原理:无论哪种 GC,在某些阶段都要求所有用户线程暂停。
🔥 Flink 关联:如果 STW 超过 10 秒,TaskManager 可能无法向 JobManager 发送心跳,导致 JM 认为 TM 挂了,从而触发 Failover(作业重启)。
垃圾收集器(重点懂 G1):
CMS:老一代回收器,已被淘汰。
G1(Garbage-First) :将堆划分为多个 Region,支持设置预期停顿时间(
-XX:MaxGCPauseMillis)。Flink 官方推荐使用 G1,平衡吞吐量和延迟。ZGC(进阶):JDK 11+ 引入的超低延迟回收器(停顿在 1ms 内),超大数据量场景(如几百G内存)下,Flink 生产环境越来越多地转向 ZGC。
模块三:类加载机制(代码怎么变成可执行的字节码?)
懂这个,你就能解决所有
ClassNotFoundException和NoSuchMethodError。
双亲委派模型:
- Bootstrap ClassLoader -> Extension ClassLoader -> App ClassLoader。目的是防止你写个
java.lang.String把系统底层的覆盖了。🔥 Flink 打破双亲委派:
痛点:Flink 集群上可能同时跑着 A 团队(用 Kafka 2.0)和 B 团队(用 Kafka 3.0)的作业。如果默认全由 AppClassLoader 加载,就会冲突。
解决 :Flink 设计了 FlinkUserCodeClassLoader 。它优先加载用户提交的 jar 包,最后才委派给系统加载器。实现了作业之间的依赖隔离。
模块四:JVM 调优参数与排错工具
你在大数据平台上看别人调优,调的就是这些参数。
常见参数:
-Xms / -Xmx:初始/最大堆内存(通常设成一样大,避免动态扩容引起抖动)。
-XX:MaxMetaspaceSize:限制元空间大小。
-XX:MaxDirectMemorySize:限制堆外内存大小。
-XX:+HeapDumpOnOutOfMemoryError:发生 OOM 时自动 Dump 出内存快照(救命神药)。常用命令行工具:
jps:看当前有哪些 Java 进程(你之前用过)。
jstat -gc <PID> 1000:每秒钟打印一次 GC 状况(看 Young GC / Full GC 频率)。
jmap -histo:live <PID> | head -20:查看堆内存中哪个类的对象最多。可视化分析工具:
- JProfiler / MAT (Memory Analyzer Tool) :用来分析
HeapDump文件,找出内存泄漏的元凶。
模块五:🌟 Flink 的统一内存模型(串联实战)
Flink 1.10 之后,引入了任务级内存管理。这就是 JVM 内存在 Flink 中的真实划分:
JVM Overhead:JVM 自身运行(线程栈、代码缓存、GC)所需内存。
JVM Metaspace:类加载的元数据。
Flink Total Memory:
Managed Memory(托管内存) :由 Flink 接管,完全在堆外。用于批处理算子(Sort、Hash Join)和 RocksDB。它不受 JVM GC 管理,用完直接释放,效率极高。
Network Memory(网络内存):用于数据传输缓冲区(如上游发给下游)。
Task Heap(任务堆内存) :留给你的代码
new对象的。如果这里爆了,就是典型的用户代码 OOM。
前端转 Java 的优势:你懂事件循环、懂闭包、懂内存泄漏怎么用 Chrome DevTools 排查。把 V8 的思维平移过来,JVM 就是另一个引擎,只是它的垃圾回收更复杂、堆外内存操作更频繁而已。
💡 学习感悟 :
前端讲"所见即所得",后端讲"约定优于配置"。理清环境边界(哪台机器在跑)、搞对操作顺序(谁先连谁后发)、认准 Maven 的强管控,就能跨过大数据入门的门槛。