Java管道项目环境搭建:JDK、Maven与运行避坑
简介:面向Java开发者与CI/CD初学者的一份Jenkins流水线实战项目,围绕持续集成与持续部署的落地展开,帮助读者理解如何用Jenkinsfile描述编译、测试、打包、部署的完整流程,并配合Ant构建配置完成自动化构建,适合具备Java基础、希望掌握流水线编排与版本控制触发机制的学习者。压缩包共10个文件,约222KB,以3个Java源码文件和2个jar依赖为主,辅以2个xml构建与测试配置、一个Jenkinsfile流水线脚本、.gitignore及README说明,结构紧凑,可直接导入实践。已有304人学习下载。项目通过stage-step结构把构建过程拆分为清晰阶段,涉及并行化构建、Git事件触发、蓝绿部署与滚动更新等思路,读者可据此理解从代码提交到生产发布的自动化链路,并借助README与源码快速复现实验,为后续接入Maven、Gradle或Docker等工具打下基础。
1. 从一次 UnsupportedClassVersionError 说起:Java 管道项目为什么总在环境上翻车
某开发者把 Java 管道项目拷到一台新机器上,mvn clean package一路绿灯,java -jar一敲就抛出 UnsupportedClassVersionError。编译用的是 17,运行的是机器上残留的 8,中间隔着一个 PATH 顺序。这类管道项目通常是「源头读数据 → 若干处理阶段 → 写到目标端」的结构,比普通 Demo 更依赖环境的完整性:JDK 版本、构建工具、外部配置目录、输出目录、日志目录,少一环都可能在跑到一半时爆出来,而且报错位置往往指向业务代码,让人误以为是程序写错了。合理的安装顺序是先定版本,再装 JDK 和 Maven,然后配构建与运行参数,最后拿一条最小数据端到端跑通。下面按这个顺序把每一步落到命令上。
2. 环境清单与版本取舍:JDK 17、Maven 3.9 和一套目录约定
装之前先把版本定死,比装到一半再回头换 JDK 省事得多。Java 管道项目的特殊性在于它很少依赖大量第三方框架,pom 往往只有日志、JSON 解析、测试这么几项,所以真正决定成败的不是依赖冲突,而是 JDK 主版本和构建工具的编译参数是否一致。同一份代码,用 21 编译、用 17 运行,字节码版本对不上就直接拒绝加载;反过来用 8 编译、17 运行通常没问题,但代码里一旦用了var、文本块、switch表达式,8 根本编不过去。这也是为什么我习惯在动手前先翻一遍pom.xml里的maven.compiler.release和代码里有没有新语法,再决定装哪个版本。
机器资源这块也顺手看一眼。管道项目通常是流式处理,内存给少了会在批处理阶段频繁 Full GC,给多了又浪费;磁盘主要看输出目录和日志目录,如果输入是文件,中间结果还会额外占一份空间。常见做法是先给 2 核 4G 起步,跑通之后再按实际吞吐调。
2.1 JDK 8/11/17/21 怎么选:一张对照表把版本定死
版本号对不上时,JVM 只会给你一句「class file version 61.0」,不会告诉你该装哪个。把这张表存下来,出问题直接对号。
| JDK 主版本 | class 文件版本 | 在管道项目里的适用面 | 容易踩的地方 |
|---|---|---|---|
| 8 | 52 | 老代码、老依赖,兼容性最宽 | 无var、无文本块,新语法直接编译失败 |
| 11 | 55 | 过渡期项目,部分新 API 可用 | 一些 17 才有的 API 缺失,升级时容易漏 |
| 17 | 61 | 绝大多数管道项目的落点,LTS | 默认字符集仍是平台编码,中文要显式指定 |
| 21 | 65 | 代码里用到虚拟线程、结构化并发 | 部分监控、字节码增强工具版本跟不上 |
判断一份 jar 是什么版本编的,不用猜,javap -verbose看 major version 就行,61 就是 17,65 就是 21。
# 找到 class 文件后看字节码主版本号 javap -verbose target/classes/com/example/pipeline/Stage.class | grep "major version" # 输出 major version: 61 说明是 JDK 17 编译的javap是 JDK 自带的字节码反汇编工具,不需要额外装。-verbose会打印常量池和类文件头信息,grep只是把关心的那一行捞出来。如果看到 52 却在用 17 跑,那说明依赖里混进了老 jar,或者本机JAVA_HOME没生效,mvn -v里显示的 Java 版本才是编译时真正用的那个。
至于选 17 还是 21,我的判断标准很朴素:代码里出现Thread.ofVirtual()、Executors.newVirtualThreadPerTaskExecutor()这类写法就必须 21,否则 17 足够,工具链生态更成熟,出问题搜到的答案也更多。
2.2 构建工具与目录约定:先看清这份管道项目要什么
构建工具用 Maven 还是 Gradle,取决于仓库里有没有pom.xml。这类项目绝大多数是 Maven,因为管道配置、打包插件、依赖版本都写在 pom 里,一眼能看全,接手的人不用先学一套构建脚本。
装之前先确认目录骨架,缺目录是新手最常见的翻车点。管道项目一般长这样:
| 路径 | 作用 | 装环境时要不要手动建 |
|---|---|---|
src/main/java | 各处理阶段的实现类 | 仓库里已有 |
src/main/resources | pipeline.yml等配置 | 仓库里已有 |
data/in | 输入数据落地目录 | 要,多数仓库不提交 |
data/out | 输出结果目录 | 要 |
logs | 运行日志 | 要,程序一般不自动建 |
target | 构建产物 | Maven 自动生成 |
data/in、data/out、logs这三个目录被.gitignore挡掉是常态,克隆下来就是不存在。程序启动时如果用的是相对路径,工作目录一换就找不到文件,报的还是「文件不存在」这种看不出根因的错。
# 在项目根目录补齐运行时目录 mkdir -p data/in data/out logs # 确认当前工作目录,相对路径都基于它 pwdmkdir -p的好处是目录已存在也不报错,可以放心重复执行。pwd这一步别省,java -jar的相对路径是相对启动时的工作目录,不是 jar 所在目录,在target/里启动和在项目根目录启动,读到的配置可能完全不是一份。
2.3 开跑之前的机器账:内存、磁盘和文件句柄
管道项目跑起来之后最容易撞的两堵墙是内存和文件句柄。堆内存按数据批大小估,批大小 1000 条、单条几百 KB 的场景,2G 堆够用;如果阶段之间有缓冲队列,队列长度乘以单条大小也要算进去。文件句柄数在 Linux 上默认常见是 1024,管道并发读多个文件时容易打满。
# 看当前 shell 的文件句柄上限 ulimit -n # 临时调高,只对当前会话有效 ulimit -n 65535 # 看内存和磁盘余量 free -h && df -h .ulimit -n改的是当前会话,重启就回去了,要持久化得写/etc/security/limits.conf。free -h看的是物理内存和 swap,df -h .只看当前分区,输出目录如果挂在别的盘上,要单独df -h那个路径。这几条命令不解决问题,但能在装之前排除一半「跑着跑着就挂」的玄学。
3. 安装 JDK 与 Maven:Linux 和 Windows 两条路各自的环境变量写法
环境变量这块是血泪经验最集中的地方。同一个 JDK,解压路径写错、软链指错、PATH 顺序颠倒,表现出的症状完全不同:有的是java能用javac不能用,有的是 Maven 编译用的 17、运行用的 8,还有的是服务账号登录后JAVA_HOME是空的。核心原则只有一条——让所有入口指向同一个真实路径,中间只留一层软链。
Linux 上我一般把 JDK 解压到/opt,再用/opt/java这个不带版本号的软链指过去。升级时只改软链,JAVA_HOME不用动,配置文件里也不会散落一堆版本号。Windows 上则相反,路径尽量短、不带空格和中文,JAVA_HOME指向安装目录本身而不是bin。
3.1 Linux 上的 JDK 17:归档包、软链和 profile.d
先从官方渠道拿归档包,别用来路不明的压缩包。拿到之后先校验,再解压。
# 假设归档包已经放到 /tmp,文件名按实际替换 cd /tmp sha256sum jdk-17_linux-x64_bin.tar.gz # 与官方公布的校验值比对,一致再往下走 sudo mkdir -p /opt/jdk-17 sudo tar -zxvf jdk-17_linux-x64_bin.tar.gz -C /opt/jdk-17 --strip-components=1 # 建立不带版本号的软链,后续升级只改这一行 sudo ln -sfn /opt/jdk-17 /opt/java ls -l /opt/javatar的-z是 gzip 解压,-x解包,-v显示过程,-f指定文件,-C指定解压目标目录。--strip-components=1会去掉压缩包里的第一层目录,避免出现/opt/jdk-17/jdk-17.0.x/这种套娃。ln -sfn里的-n很关键,当目标已经是软链时,不加它会在软链目录里再建一个链,越套越深。
接着写环境变量。不要往~/.bashrc里塞,服务账号和非交互 shell 读不到。放/etc/profile.d/下,所有用户、所有登录方式都能拿到。
# 新建 /etc/profile.d/jdk17.sh export JAVA_HOME=/opt/java export JRE_HOME=$JAVA_HOME/jre export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar export PATH=$JAVA_HOME/bin:$PATHJAVA_HOME指向软链而不是真实目录,升级时只动软链。PATH里把$JAVA_HOME/bin放在最前面,是为了压过系统自带的/usr/bin/java。CLASSPATH里那个点代表当前目录,老项目常用;新项目基本靠 Maven 管理,写上也无害。JDK 9 之后没有独立的jre目录,JRE_HOME这行留着是为了兼容某些还在找它的启动脚本,指向一个不存在的目录也不会报错,只是别指望它真有用。
写完执行source /etc/profile.d/jdk17.sh让当前会话生效,然后echo $JAVA_HOME确认输出是/opt/java而不是空。
3.2 Maven 3.9 的安装与 MAVEN_OPTS
Maven 本身不复杂,解压、配 PATH 就完事,麻烦的是它默认会去中央仓库拉依赖,网络一卡就停在Downloading。安装这一步先把本地仓库位置定好,后面换镜像、做离线包都方便。
# 解压到 /opt sudo tar -zxvf apache-maven-3.9.x-bin.tar.gz -C /opt sudo ln -sfn /opt/apache-maven-3.9.x /opt/maven # 追加到同一个 profile.d 文件里 cat >> /etc/profile.d/jdk17.sh <<'EOF' export MAVEN_HOME=/opt/maven export PATH=$MAVEN_HOME/bin:$PATH # 给 Maven 自己的 JVM 留内存,和业务进程的堆是两回事 export MAVEN_OPTS="-Xms256m -Xmx1024m -Dfile.encoding=UTF-8" EOF source /etc/profile.d/jdk17.shMAVEN_OPTS里的-Xmx1024m是给 Maven 进程用的,编译大项目不够会 OOM,但和后面java -jar的-Xmx互不影响,别混为一谈。-Dfile.encoding=UTF-8是为了让编译期读源码不乱码,尤其在 JDK 17 上,默认字符集还跟着系统 locale 走,机器 locale 是POSIX的时候中文注释会出问题。
3.3 Windows 上的路径与 Path 顺序
Windows 装 JDK 就三步:安装包选自定义路径,别放C:\Program Files这种带空格的目录;新建系统变量JAVA_HOME,值是安装目录本身,比如D:\dev\jdk17,不是bin;编辑Path,把%JAVA_HOME%\bin加到最前面。
命令行里用setx也能改,但setx写的是用户级变量,且对已经打开的终端不生效,改完必须重开一个新窗口验证。这是「明明配了却没生效」的头号原因。
:: 以管理员身份运行,写系统级变量 setx JAVA_HOME "D:\dev\jdk17" /M :: Path 追加到最前面,注意这里是在原有 Path 前面拼接 setx PATH "%JAVA_HOME%\bin;%PATH%" /Msetx有 1024 字符长度限制,Path太长的机器上直接截断,后果是某些工具突然找不到了。遇到这种机器我就改成图形界面手点,或者用 PowerShell 的[Environment]::SetEnvironmentVariable来写,不占命令行长度。
另一个高频坑是机器上同时装了 8 和 17,安装器会把新的java.exe拷到C:\Windows\System32,而System32在Path里的优先级比JAVA_HOME\bin高。表现就是JAVA_HOME明明指着 17,java -version还是 8。解决方式是去System32里把java.exe、javaw.exe、javac.exe这几个删掉或改名,让Path顺序说了算。
3.4 装完自检:四个命令确认三者同源
装完别急着构建,先确认java、javac、mvn三个命令认的是同一个 JDK。
java -version javac -version mvn -v # Linux 下追一下真实路径,Windows 用 where java readlink -f "$(which java)" readlink -f "$(which javac)"四条命令的输出里,前两条的大版本必须一致,mvn -v里的Java version也要是同一个。readlink -f会把软链一路解到真实文件,如果java解出来是/usr/lib/jvm/...,而JAVA_HOME是/opt/java,说明 PATH 里系统自带的 JDK 还在前面,回头检查 profile 有没有 source、有没有被后面的配置覆盖。mvn -v里还会打印Maven home和Java home,Java home这一行是判断 Maven 用哪个 JDK 编代码的唯一依据,比JAVA_HOME更可信。
4. 配置构建与运行:settings.xml 镜像、pom 打包和启动参数
环境装好之后,真正决定项目能不能跑起来的是三份配置:Maven 的settings.xml管依赖从哪来,pom.xml管编出什么产物,pipeline.yml加启动参数管跑起来之后怎么分配资源。这三份配置改错任何一份,症状都不一样——依赖拉不下来是网络或镜像问题,打包出来跑不了是插件和主类问题,跑起来卡住或者 OOM 是并发度和堆内存问题。
顺序上我习惯先配settings.xml,让构建过程不卡;再调pom.xml,保证能打出一个带主类的可执行 jar;最后才是运行参数,因为运行参数需要反复试,改起来最频繁。
4.1 settings.xml:本地仓库、镜像和超时
settings.xml有两个位置:Maven 安装目录的conf/下是全局的,~/.m2/下是当前用户的。改用户级的不影响别人,我一般优先改这个。
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"> <!-- 本地仓库单独放一块盘,避免和系统盘抢空间 --> <localRepository>/data/maven-repo</localRepository> <mirrors> <mirror> <!-- id 自定义,只要和仓库 id 不冲突即可 --> <id>central-mirror</id> <mirrorOf>central</mirrorOf> <!-- 换成你所在网络能访问到的镜像地址 --> <url>https://mirror.example.com/maven/</url> </mirror> </mirrors> <profiles> <profile> <id>jdk17</id> <activation><activeByDefault>true</activeByDefault></activation> <properties> <maven.compiler.release>17</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> </profile> </profiles> </settings>mirrorOf写central表示只接管中央仓库,不会影响 pom 里显式声明的其他仓库;如果写*会把所有仓库都劫走,内网有私有仓库的机器上这么干会直接失败。localRepository换到数据盘,主要考虑是~/.m2在容器或小系统盘上容易被写满,一旦满了报的是「No space left on device」,看着像磁盘问题,实际是依赖缓存撑爆的。
maven.compiler.release放在 profile 里是个兜底,pom 里如果也写了以 pom 为准。release参数比老的source/target组合更严格,它同时约束语法和 API,防止代码在 17 上编过了却调用了 21 才有的方法。
4.2 pom.xml:release 17、编码与可执行 jar
管道项目最后一定要打出一个能java -jar直接跑的可执行包,否则每次启动都得拼一长串 classpath。
<properties> <!-- 编译目标,和本机 JDK 大版本保持一致 --> <maven.compiler.release>17</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <build> <finalName>pipeline</finalName> <plugins> <!-- 打包含全部依赖的 fat jar,并指定入口类 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.5.1</version> <executions> <execution> <phase>package</phase> <goals><goal>shade</goal></goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.example.pipeline.PipelineMain</mainClass> </transformer> <!-- 合并多个依赖里的 SPI 文件,否则运行时会漏实现类 --> <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build>finalName决定产物叫什么,写死成pipeline之后启动脚本里就不用每次跟着版本号改。mainClass必须和源码里的入口类全限定名一模一样,大小写错了打包不报错,运行时报no main manifest attribute。ServicesResourceTransformer是个容易被忽略的细节,日志框架、JSON 库大量用 SPI 注册实现,不合并这些文件的话,fat jar 里只保留最后一个,运行时会报找不到实现类,而且报错信息完全不指向打包问题。
maven.compiler.release建议写在 pom 里而不是只写在 settings,这样换台机器、换个人构建,结果都一样,不会因为某个人本地 settings 没配就编出个 8 的包。
4.3 启动参数与 pipeline 配置文件
启动参数分两部分:JVM 参数和程序自己的配置。JVM 参数管内存和编码,程序配置管并发度、批大小、输入输出路径。
java \ -Xms1g -Xmx2g \ -Dfile.encoding=UTF-8 \ -Dpipeline.config=./src/main/resources/pipeline.yml \ -jar target/pipeline.jar \ --input ./data/in --output ./data/out-Xms和-Xmx设成一样大,是为了避免运行中反复扩堆带来的停顿,管道这类长时间跑的程序尤其明显。-Dfile.encoding=UTF-8在 JDK 17 上仍然要显式写,不写就跟着系统 locale。-Dpipeline.config是程序自己读的系统属性,用来定位配置文件。--input、--output是程序参数,和-D的区别是它们在main方法的args里,程序得自己解析,命令行顺序上必须放在-jar之后。
配置文件本身一般是 YAML,键名以仓库里那份为准,改的时候只动值:
pipeline: # 并发处理的阶段数,建议不超过 CPU 核数,IO 密集可以适当放大 concurrency: 4 # 单批处理条数,越大吞吐越高但单批内存占用也越大 batchSize: 500 stages: - name: read input: ./data/in - name: transform # 单阶段超时,防止某个阶段卡死拖住整条管道 timeoutMs: 30000 - name: write output: ./data/outconcurrency不是越大越好。纯 CPU 计算的阶段调到核数以上只会增加线程切换开销;IO 等待多的阶段可以放到核数的 2 到 4 倍。batchSize和堆内存是联动的,500 条一批、单条几十 KB 的时候 2G 堆绰绰有余,改成 5000 条就可能 OOM,调大之前先把-Xmx一起调。timeoutMs属于防御性配置,阶段性卡住时能自己失败退出,而不是挂在那里让人盯着日志猜。
4.4 依赖拉不动:-U、清理 lastUpdated 与离线包
内网机器上最常见的情况是构建卡在Downloading from central,等半天报Could not transfer artifact。先确认镜像地址通不通,再处理本地残留。
# 强制更新快照和失败的依赖 mvn -U clean package -DskipTests # 清理失败标记,Maven 会为每次失败留下 .lastUpdated 文件 find /data/maven-repo -name "*.lastUpdated" -delete # 完全没网的机器,提前在能联网的机器上把依赖拉到本地仓库,再整体拷过去 mvn -o clean package # -o 离线模式,缺依赖会直接报错,不会傻等-U只是强制检查更新,并不会删除已经写坏的标记文件,所以两个命令要一起用。.lastUpdated文件的存在感极低,但只要它在,Maven 就认为这个依赖上次拉失败了,可能直接跳过重试。-o离线模式适合在内网验证依赖是否齐全,跑一遍全绿说明本地仓库够用,可以整个目录打包带走。拷过去之后记得路径要一致,本地仓库路径写在settings.xml里,路径对不上等于没拷。
5. 避坑与排查:五条装完才发现的坑
下面这五条都不是装的时候报错,而是装完、跑起来之后才冒出来的,共同点是错误信息指向的位置和真实原因隔了一层。
5.1 编译 17 运行 8:报 UnsupportedClassVersionError
现象:mvn package成功,java -jar立刻抛UnsupportedClassVersionError: ... has been compiled by a more recent version of the Java Runtime。
原因:构建和运行用了两个 JDK。常见于机器上装了多个版本,mvn -v里Java home是 17,但交互式 shell 里PATH顺序被后来的配置覆盖,java指向了 8。还有一种情况是启动脚本里写死了/usr/bin/java。
解决:先用mvn -v和java -version对一遍大版本,再用readlink -f $(which java)看真实路径。启动脚本里不要写绝对路径的java,改成依赖JAVA_HOME,让环境变量统一管理。容器里部署的话,基础镜像的 JDK 版本要和构建阶段保持一致,别用 17 构建、8 的运行镜像。
5.2 中文乱码:日志和输出文件里全是问号
现象:程序跑完了,日志里的中文变成????,输出文件用编辑器打开也是乱码。
原因:三个地方的字符集任意一处没对齐都会乱——源码文件编码、编译时的file.encoding、运行时的file.encoding。JDK 17 的默认字符集仍然取决于系统 locale,机器 locale 是POSIX或者C的时候默认不是 UTF-8。
解决:编译期在 pom 里写project.build.sourceEncoding,运行期在启动命令里加-Dfile.encoding=UTF-8,读文件的地方显式指定StandardCharsets.UTF_8而不是用平台默认。三个都做才彻底,只做其中一个的话,换台机器又会出现。
5.3 打包成功但跑不起来:no main manifest attribute
现象:target/pipeline.jar生成了,java -jar报no main manifest attribute, in pipeline.jar。
原因:打包插件没配Main-Class,或者配了但没绑到package阶段,实际打出来的是普通 jar,不是 fat jar。也有一种情况是用了maven-jar-plugin却忘了配manifest。
解决:先unzip -p target/pipeline.jar META-INF/MANIFEST.MF看里面有没有Main-Class这一行,没有就是插件没生效。检查 shade 插件的executions里有没有<phase>package</phase>,mainClass的全限定名和源码里的包名类名是否完全一致。另外注意finalName和 shade 的输出可能产生original-pipeline.jar这种备份文件,别拿错包去跑。
5.4 跑一会儿就 OOM:批大小和堆内存没对上
现象:启动正常,处理了几批数据之后抛OutOfMemoryError: Java heap space,或者 GC 日志里 Full GC 越来越频繁。
原因:batchSize调大了但-Xmx没跟着调。管道项目在阶段之间通常有缓冲队列,实际内存占用是「批大小 × 单条大小 × 缓冲队列长度」,只看批大小会严重低估。另外-Xms设得太小,堆反复扩张也会拖慢启动。
解决:先把-Xms和-Xmx设成同一个值,再把batchSize降一半试跑,确认不是内存问题之后再逐步往上加。真要定位是哪一步吃内存,加-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./logs,OOM 的瞬间会 dump 一份堆快照,用jmap或者分析工具打开就能看到是谁在占。
5.5 线程数打满、任务卡住不动:并发度超过机器承受
现象:程序不报错,但日志停在某一个阶段不再往后走,top看 CPU 不高,jstack里一堆线程在WAITING。
原因:concurrency设得比机器能承受的高,线程之间抢同一份资源(文件句柄、数据库连接、锁),排队等成了死结。另一种是某个阶段的下游处理慢,上游一直往里塞,缓冲队列满了之后全体阻塞。
解决:jstack <pid>打线程栈,看多少个线程卡在同一个方法上,基本就能定位到争抢点。并发度先降到 CPU 核数跑一遍,确认能跑通再往上升。文件句柄不够的话,ulimit -n调大,日志里通常会伴随Too many open files。
6. 跑通验证:三步自检和一份最小输入
装完之后不要直接上真实数据,先用最小输入把整条链路走一遍。我习惯分三步:空跑、最小数据端到端、中断重跑。
6.1 第一步:空跑自检
# 不接数据,只验证配置加载和入口是否正常 java -jar target/pipeline.jar --config ./src/main/resources/pipeline.yml --dry-run--dry-run这类参数不是每个项目都有,没有的话就构造一个空的data/in目录跑一次。这一步要看到的是「配置解析成功」「阶段初始化完成」这类日志,如果连配置都读不到,后面全是白搭。跑完检查logs/下有没有生成日志文件,没有说明日志配置的路径不对。
6.2 第二步:最小数据端到端
往data/in里放一条或几条最小样本,跑完整流程,然后核对三件事。
| 检查项 | 怎么看 | 不正常的表现 |
|---|---|---|
| 输入被读到 | 日志里有读入条数 | 条数为 0,多半是路径不对 |
| 每个阶段都执行 | 日志里各阶段有耗时记录 | 某阶段没打印,配置里没启用 |
| 输出落盘 | data/out下有文件且内容正确 | 目录空或文件为 0 字节 |
# 放一条最小样本后完整跑一遍 echo '{"id":1,"value":"test"}' > data/in/sample.json java -jar target/pipeline.jar --input ./data/in --output ./data/out ls -l data/out && head -c 200 data/out/*输出文件为 0 字节是最容易被忽略的情况——程序没报错,流程也走完了,但写阶段因为权限或者路径问题什么都没写进去。ls -l看大小,head -c看内容,两个都过才算真跑通。
6.3 第三步:中断重跑看幂等
管道项目最怕的是重跑之后数据翻倍。手动Ctrl+C中断一次,再原样启动一次,看输出目录里的结果是不是和只跑一次一样。不一样就说明写阶段没有做覆盖或者去重,生产环境里一次失败重试就会污染数据。
从那以后我每次装完这套环境,都强制把这三步走一遍才敢接真实数据,尤其是第三步,宁可多花十分钟,也不想第二天早上对着一份翻倍的结果排查。希望帮到你。
本文还有配套的精品资源,点击获取