简介:围绕 Kettle 9.3 整理的这份 DOCX 文档,面向需要构建数据抽取、转换与加载流程的数据工程师、ETL 初学者及 BI 实施人员,重点梳理 Pentaho Data Integration 的跨平台运行、图形化工作流设计、Transformation 与 Job 两类脚本的职责,以及数据库与云存储等数据源的接入思路。资源包共 1 个 DOCX 文件,大小约 14KB,内容以版本特性、核心组件和典型使用场景的文字说明为主,并给出百度云获取入口与提取码,方便按需取用安装包。已有 5743 人学习或下载,具备一定参考热度。文档对 9.3 版本的更新方向、Spoon 客户端使用、转换步骤配置、作业调度、测试调试及云计算集成均有展开,适合用来快速建立 Kettle 的整体认知,也可作为搭建 ETL 流程前查阅概念与排错思路的轻量笔记。
1. Kettle 9.3 的下载包里到底装了什么:先弄清 transformation 与 job 的分工
很多人拿到 Kettle 9.3 下载包,解压后看到一堆 jar、一个 spoon.sh、一个 kitchen.sh,双击没反应,命令行还报 JAVA_HOME 找不到,第一反应是"这包是不是坏的"。其实 PDI-CE 9.3 从来不需要安装向导,它就是一套 Java 目录结构,双击Spoon.bat或spoon.sh才是入口。它的价值在于把数据搬运这件事拆成两层:transformation 负责单条链路上的抽取、清洗、转换、写入,job 负责把多条 transformation 按成功、失败、条件分支串成可控流程。适合手上有 CSV、Excel、关系库、S3 这类异构数据源,又不想为一次迁移写几百行脚本的人。这份网盘分发的安装包,好处是拿到就能跑,但跑不跑得起来,取决于后面几章的底座配置。
2. 装之前先把运行底座搭对:JDK 版本、驱动目录与 Spoon 启动参数
Kettle 报错里有相当一部分和 ETL 逻辑无关,问题出在 Java 环境、驱动 jar 的位置、启动参数的编码上。这三件事在解压后半小时内确认完,后面能少掉一半莫名其妙的故障。我一般的顺序是:先把解压路径定死,再确认 JDK,再把驱动扔到正确目录,最后改启动脚本里的三个参数,然后才去碰 Spoon 界面。
2.1 JDK 选型与目录约定:为什么路径里不能有空格和中文
Kettle 9.3 是纯 Java 应用,没有 JRE 就起不来。9.x 这条线上,JDK 8 是验证最充分的组合,JDK 11 也有不少人在跑,但部分老插件在 11 上会报模块访问相关的错。我给生产环境的默认选择还是 JDK 8,除非目标库驱动强制要求更高版本。判断标准很简单:驱动 jar 的 MANIFEST 里写了什么最低 JDK 要求,就按那个来,不要凭感觉升级。
目录这一步是血泪经验。Kettle 的启动脚本里大量使用变量拼接,路径里带空格(比如Program Files)或中文,在 Windows 下经常出现脚本解析截断、临时文件写不进去的问题;Linux 下则是 shell 变量没有加引号导致路径被拆开。我统一约定成无空格、纯英文、层级浅的路径,比如 Linux 下/opt/pdi-9.3,Windows 下D:\pdi93。解压完第一件事不是打开 Spoon,是验证 Java 能不能被这个目录下的脚本找到。
# 解压到无空格、无中文的路径,这是后续所有脚本能正常拼接路径的前提 mkdir -p /opt/pdi unzip pdi-ce-9.3.0.0-*.zip -d /opt/pdi mv /opt/pdi/data-integration /opt/pdi/pdi-9.3 # 显式指定 JDK,不要依赖系统默认的那个 export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 export PATH=$JAVA_HOME/bin:$PATH # 确认版本,Kettle 启动脚本会读 JAVA_HOME,读不到就直接退出 java -version echo $JAVA_HOME这段命令的关键不在解压,而在最后两行的自检。java -version输出的是系统里第一个命中的 java,不一定是JAVA_HOME指向的那个,两个都要看。如果JAVA_HOME为空而java -version有输出,说明只是 PATH 里有 java,Kettle 的spoon.sh依然会报错退出,因为脚本里判断的是JAVA_HOME变量而不是which java。
Windows 下的做法类似,但改的是Spoon.bat文件头部的set JAVA_HOME=那一行,直接写死绝对路径,不要指向%JAVA_HOME%。我见过有人在系统环境变量里配了 JAVA_HOME,但 Spoon.bat 内部又有一行覆盖,结果每次启动都用错版本,查了半小时才发现是脚本里那行。
2.2 数据库驱动放哪:lib 目录、类加载顺序与 jar 冲突
Kettle 自带一批数据库驱动,但版本往往偏老。连 MySQL 8、Oracle 19c、PostgreSQL 14 这类较新的库,通常要自己换 jar。放的位置有三个候选:lib/、lib/jdbc/、plugins/。我一般放lib/,因为它在这个目录下的类加载优先级最高,Spoon 界面里加连接时能直接选到。
放多个版本的 jar 是典型翻车点。比如lib/里同时有mysql-connector-java-5.1.49.jar和mysql-connector-java-8.0.28.jar,类加载器命中的是哪个不确定,表现就是同一份 ktr 在同事机器上能跑,在你这里报Unknown system variable 'query_cache_size'或者驱动类名不对(com.mysql.jdbc.Driver与com.mysql.cj.jdbc.Driver是两代不同的类名)。动手前先列一遍现有 jar。
# 列出所有数据库驱动,同名不同版本是冲突的根源 ls -1 /opt/pdi/pdi-9.3/lib | grep -iE "mysql|ojdbc|postgresql|sqlserver|mssql" # 看 jar 里声明的实现版本,别只看文件名 unzip -p /opt/pdi/pdi-9.3/lib/mysql-connector-java-*.jar \ META-INF/MANIFEST.MF | grep -iE "Implementation-Version|Bundle-Version"第一条命令是排查重复,只要同一个数据库出现两个 jar,先删旧的再谈其它。第二条命令用来确认版本号是不是你以为的那个,文件名被改过的情况不少见。确认完之后,连接配置里的驱动类名要跟 jar 版本对齐:8.x 的 MySQL 写com.mysql.cj.jdbc.Driver,5.x 写com.mysql.jdbc.Driver,写错的表现是测试连接时提示找不到驱动类,但目录里明明有 jar。
还有一个容易忽略的点:改了lib/里的 jar 之后必须重启 Spoon,界面里的连接池不会自动重新加载类。有同事加完驱动直接点测试,一直报错,重启一下就好了,白白怀疑了半天驱动文件损坏。
2.3 Spoon 首次启动的三个必改项:资源库、内存、编码
第一次打开 Spoon,多数人卡在资源库对话框上。它默认让你连一个资源库,但本地开发根本不需要,选"取消"或"没有资源库"进入即可,ktr、kjb 文件完全可以以本地文件形式打开和保存。资源库适合多人共享,个人用反而增加心智负担,还容易把本地改的流程误提交。
第二个必改项是内存。启动脚本里默认的堆上限对大文件不够用,处理几十万行以上就可能OutOfMemoryError。改法是编辑spoon.sh(Windows 下是Spoon.bat),找到PENTAHO_DI_JAVA_OPTIONS或-Xmx那一行,按机器内存的 50%~60% 给。注意给的是堆,不是整机内存,留一部分给操作系统和文件缓存。
第三个是编码。跨平台搬运时,Windows 默认 GBK、Linux 默认 UTF-8,同一个 ktr 在两台机器上跑出不同结果,八成是这里。启动参数里显式加-Dfile.encoding=UTF-8,比在每一步里单独设编码靠谱。
# 以下参数写在 spoon.sh 的 PENTAHO_DI_JAVA_OPTIONS 变量里 # -Xms 与 -Xmx 设成一样,避免运行中反复扩堆 # -Dfile.encoding 统一字符集,跨平台可复现的前提 # -Djava.io.tmpdir 指向空间充足的盘,避免大文件排序时写满根分区 export PENTAHO_DI_JAVA_OPTIONS="-Xms2048m -Xmx4096m -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/data/tmp"参数含义逐个说清楚:-Xms是初始堆,设成和-Xmx相同可以减少扩容带来的停顿;-Xmx是上限,超过就抛溢出,不是越大越好,超过物理内存会触发交换,反而慢;file.encoding影响 JVM 读写的默认字符集,影响日志文件和部分步骤的默认编码;java.io.tmpdir是 Kettle 做排序、去重、聚合时落盘的目录,大表场景下可能吃掉几十 GB,指向根分区是常见的磁盘打满原因。
改完启动,看一眼日志窗口,确认输出里打印的 JVM 参数和你写的一致,这一步花十秒,能省掉后面"明明改了怎么没生效"的排查。
3. 第一个可复现的 Transformation:CSV 到 MySQL 的八个节点与三条校验
选 CSV 到 MySQL 这条链路做第一条转换,不是因为它简单,而是因为它把最容易出问题的几件事全占了:文件编码、分隔符、字段类型推断、空值处理、目标表结构对齐、批量提交。走通这一条,后面换 Excel、换 Oracle、换 S3 只是换源头节点的事,中间和目标的思路可以复用。
搭之前先把目标表建好,不要指望 Kettle 自动建表。自动建表出来的字段类型和长度往往不符合预期,比如把DECIMAL建成DOUBLE,把VARCHAR(20)建成VARCHAR(255),后面再改就要重建。
-- 目标表显式定义类型,不要依赖 Kettle 的自动建表 CREATE TABLE ods_order ( order_id VARCHAR(32) NOT NULL COMMENT '订单号', customer_id VARCHAR(32) DEFAULT NULL, amount DECIMAL(18,2) DEFAULT NULL COMMENT '金额,禁止用 DOUBLE', order_time DATETIME DEFAULT NULL, src_file VARCHAR(128) DEFAULT NULL, etl_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (order_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;金额字段用DECIMAL而不是DOUBLE是硬要求,浮点在累加和比较时会产生尾差,跨系统对账时这类差异极难解释。src_file用来记录数据来源文件,出问题时能第一时间定位是哪批文件污染了表,这个字段在真实项目里救过我很多次。
3.1 源头节点怎么配:文本文件输入的分隔符、编码与头部行
拖出"文本文件输入"步骤后,要填的东西比想象中多。文件页签里指定路径时,可以用通配符或正则一次性吃多个文件,比如/data/in/order_*.csv,Kettle 会逐个读取并把文件名写进一个可选字段,配合上面那个src_file用。注意"从字段中包含的文件名"这一项要填个字段名,不填的话就丢了来源信息。
内容页签是重点。分隔符要显式指定,不要用"自动推断",推断在字段为空时会出错。编码必须手动选,源文件是 GBK 就选 GBK,是 UTF-8 就选 UTF-8,选错的表现是中文全变问号或者出现乱码。头部行有就跳过 1 行,但要注意有些导出的 CSV 头部行之外还有一行说明文字,那就要跳过 2 行。文件里有坏行时,可以在"错误处理"里设定跳过前 N 行错误,避免一行脏数据卡死整个流程。
字段页签里点"获取字段",Kettle 会扫一遍样本推断类型。推断结果是参考不是结论,日期字段经常被推成 String,金额被推成 Integer。推断完逐个核对,涉及日期和数值的一定手动改成目标类型,日期格式按源文件实际写,比如yyyy-MM-dd HH:mm:ss,格式串写错的话整列会变成 NULL,而且不报错,这是最阴的一类问题。
3.2 中间节点:字段选择、字符串裁剪与类型转换
源头的类型设好之后,中间一般要过一到两个加工步骤。最常见的三个动作是改名、去空格、转类型。
"字段选择"步骤分两个页签:选择和元数据。选择页签里把不需要的列去掉,减少后续行集的内存占用;改名也在这里做,把 CSV 里的order_id(带个尾随空格)改成order_id。元数据页签里改类型和格式,这是整条链路里唯一建议做类型转换的地方,不要在每个下游节点各转一次,转多了精度和格式会互相覆盖。
字符串去空格用"字符串操作"步骤,选 Trim type 为 both,字段选需要处理的列。注意这个步骤是全表扫描,几十万行以上会有可感知的耗时,能放到源头处理就放源头。类型转换里有"字符串转日期"这类专用步骤,比在字段选择里改元数据更可控,因为可以指定转换失败时的行为:是置空、是报错、还是走错误分支。我一般选走错误分支,把脏数据单独落到一张错误表,而不是静默变成 NULL。
中间段还有一条隐性规则:步骤之间的行集大小。默认 10000 行,意味着上游最多领先下游 1 万行。这个值在内存紧张时调小(比如 1000),在吞吐优先时调大,但不要超过-Xmx能承受的范围。很多人调优只盯着目标端的批量提交,忽略了行集,结果内存还是爆。
3.3 目标节点:表输出的批量提交、更新/插入与空值处理
"表输出"步骤里,连接选好、目标表填好之后,有三个参数必须动。第一个是"提交记录数量",默认 1000,意思是攒够 1000 行发一次 commit。这个值太小则事务开销大,太大则失败回滚的代价高、锁持有时间长。经验区间是 1000 到 5000,宽表或有大字段时往小调。
第二个是写入模式。表输出默认是纯插入,主键冲突直接报错。需要幂等重跑时,换成"插入/更新",并指定用来比对的键字段(一般就是主键)。这个模式内部是先查后写,性能比纯插入差,但换来的是可重复执行,跑失败重跑不会产生重复数据。对于每天全量覆盖的表,用"插入/更新"配合前置的清理步骤更稳妥。
第三个是空值处理。CSV 里的空字段读进来是空字符串,写进数据库会变成''而不是NULL,两种语义在业务上完全不同。表输出里勾上"使用空值代替 NULL"或者其反向选项(不同小版本措辞略有差异,看界面提示),再配合字段选择里把空串转 NULL,才能保证入库语义一致。这一条不做,后面做IS NULL统计时数字永远对不上。
如果目标表字段名和流里字段名不一致,不要靠位置映射,在表输出的字段页签里显式指定对应关系。位置映射在中间插入一个字段之后就会全部错位,而且不会报错,数据静默写错列,这种事故排查成本极高。
3.4 用 pan 命令行复跑,并把日志落到文件
界面里点运行只适合调试,真正落地要能命令行执行,因为要挂定时任务、要能写进调度系统、要有可追溯的日志。pan.sh就是跑单条 transformation 的入口。
# -file 指定 ktr,-param 透传命名参数,-level 控制日志详细程度 # 注意命令结尾立刻检查退出码,非 0 就不要往下走 /opt/pdi/pdi-9.3/pan.sh \ -file=/data/ktr/csv_to_mysql.ktr \ -param:SRC_FILE=/data/in/order_20240101.csv \ -param:BATCH_SIZE=2000 \ -level=Detailed \ -logfile=/data/logs/csv_to_mysql_$(date +%F).log rc=$? if [ $rc -ne 0 ]; then echo "transformation failed, exit code=$rc" >&2 exit $rc fi参数逐个说明:-file指定本地 ktr 路径,用本地文件模式时不要把-rep一起加上,否则会去连资源库然后报找不到文件;-param:KEY=VALUE对应 ktr 里定义的命名参数,没定义就传会被忽略,所以先在 Spoon 的"转换属性"里把参数声明好;-level从粗到细有 Error、Minimal、Basic、Detailed、Debug 几档,日常跑用 Basic,排查问题临时切 Detailed,Debug 会打出每行数据,只在极小数据集上用;-logfile把日志写文件,同时控制台也会有输出。
退出码的判断不能省。Kettle 在转换内部捕获异常时,命令行进程仍然可能返回 0,尤其是某些步骤配了错误处理分支的情况下。所以除了看退出码,还要在日志里搜关键字,比如ERROR、Stopped、Errors detected。我一般在脚本里加一条日志扫描,有命中就告警,比只看返回码可靠。
ktr 文件本身是 XML,出问题时可以直接看文本内容,判断图表里的连线和步骤配置到底存了什么。
<!-- ktr 的骨架,不是让你手写,是出问题时能看懂文件里存了什么 --> <transformation> <info> <name>csv_to_mysql</name> <directory>/data/ktr</directory> <parameters> <parameter><name>SRC_FILE</name><default_value/></parameter> </parameters> </info> <step> <name>读CSV</name> <type>TextFileInput</type> </step> <step> <name>写MySQL</name> <type>TableOutput</type> </step> <order> <hop><from>读CSV</from><to>写MySQL</to><enabled>Y</enabled></hop> </order> </transformation>这段结构说明三件事:parameters里声明了哪些参数,step里每个节点叫什么类型,order里的hop决定了连线方向。版本控制里合并冲突时,hop段的冲突最常见,两个人各加了一个节点,合并后可能连出一个环,Spoon 打开会提示流程非法,这时候看 XML 比在界面上瞎点快得多。
4. 把单条转换串成工作流:job 的分支控制、参数透传与调度
一条 transformation 能跑通只是开始。真实场景里往往是"先抽昨天的增量,再跑清洗,再跑汇总,任何一步失败就停下来发通知,成功才继续"。这一层编排交给 job,它的价值不在步骤多,而在分支可控、失败可见、能重跑。
4.1 job 的三类跳线:成功、失败与无条件
job 里的连线(hop)有三种求值方式:成功、失败、无条件。默认是成功,意思是上游步骤返回 true 才走这条线。这个默认值被很多人忽略,结果加了错误处理分支却永远走不到,因为那条线的求值方式还是"成功"。
正确做法是:主流程用成功连线,异常处理用失败连线,需要并行或无论结果都要执行的收尾步骤(比如删临时文件、写结束时间)用无条件连线。失败连线的上游步骤要能真正返回 false,比如"转换"步骤在转换内部报错时会返回 false,但如果你在转换里配了错误处理分支把异常吃掉了,它就返回 true,失败线永远不触发。
job 里还有一个"评估变量"或"简单评估"的步骤,用来做条件判断,比如判断变量ROW_COUNT是否大于 0 决定要不要往下跑。这类判断放在 job 层比放在 transformation 里清晰,因为 job 的流程图能一眼看出分支走向。注意评估类的步骤往往需要"锁"步骤配合,Kettle 的执行引擎是多线程的,不锁的话多个分支会同时往下冲,日志顺序全乱,排查时非常痛苦。
job 的组织粒度建议按"业务对象 + 频率"拆,不要把所有东西塞进一个 kjb。一个每日作业里挂十几条转换,任何一条改了都要整体回归;拆成"订单日更""客户日更"两个 job,出问题影响面小,也方便单独重跑。
4.2 变量、命名参数与 ktr 之间的传参写法
job 调 transformation 时,参数怎么传进去是新手最容易卡住的地方。Kettle 里的变量分几层:系统环境变量、JVM 变量、job 里用"设置变量"步骤定义的变量、ktr 自己的命名参数。层级之间不会自动打通,必须显式传。
我推荐的组合是:入口层(shell 脚本或调度平台)传参数给 job 的命名参数,job 里用"设置变量"把参数转成变量,调转换时在"转换"步骤的"参数"页签里逐个映射到 ktr 的命名参数,ktr 内部用${PARAM_NAME}引用。这条链路看着长,但每一层都可控,出错时能定位到是哪一层没传过去。
参数多的时候,用属性文件比一个个写在命令行里好维护。
# /data/kjb/etl_params.properties # 一行一个键值,不要加引号,不要有 BOM DB_HOST=10.0.0.12 DB_PORT=3306 DB_NAME=ods SRC_DIR=/data/in/order BATCH_SIZE=2000job 里用"获取变量"或直接加载属性文件的方式读进来,然后-param:逐个透传到 ktr。注意属性文件里不能有 BOM,Windows 记事本保存的 UTF-8 带 BOM 会让第一行的键名变成\ufeffDB_HOST,读出来永远取不到值,这个坑极其隐蔽。
时间参数(比如跑哪一天的数据)不要用date命令在哪个脚本里就地取,统一在入口层取一次然后往下传。否则 job 跨零点执行时,前半段用的是昨天,后半段用的是今天,数据就串了。
4.3 kitchen 定时执行与退出码校验
job 的命令行入口是kitchen.sh,用法和pan.sh基本对称。
# 跑 job,参数透传,日志落文件,退出码必须判断 /opt/pdi/pdi-9.3/kitchen.sh \ -file=/data/kjb/daily_order.kjb \ -param:RUN_DATE=$(date -d "yesterday" +%Y%m%d) \ -param:PARAM_FILE=/data/kjb/etl_params.properties \ -level=Basic \ -logfile=/data/logs/daily_order_$(date +%Y%m%d_%H%M).log rc=$? if [ $rc -ne 0 ]; then # 不要只判 0/非 0 就完事,把日志尾部一起带出来便于定位 tail -n 50 /data/logs/daily_order_$(date +%Y%m%d_%H%M).log exit $rc fi三个参数值得说明。-file用本地 kjb,同样不要和-rep混用;-param:RUN_DATE传日期,注意date -d "yesterday"在 macOS 和 Linux 上写法不同,调度机器上统一用 Linux 语法,跨平台开发时这里经常翻车;-level建议 Basic,Detailed 的日志量在长流程里会很大,一天下来能吃掉几个 G。
退出码只判 0 和非 0 就够用,不要试图对具体的数字做精细判断,不同版本的返回码语义不完全一致,依赖它做流程控制不划算。真正需要精确判断的地方,用 job 内部的失败连线和日志关键字扫描来代替。
定时触发这层不建议用 Kettle 自己的调度功能,交给系统层的 cron 或已有调度平台,原因是 Kettle 内置调度在作业密集时会互相阻塞,而且缺少重试、依赖、告警这些配套。cron 里加个flock防止上一次没跑完就被下一次覆盖,是成本最低的保护。
4.4 日志分级、断点续跑与失败重试
日志这块,除了-level,job 里还可以配"写日志"步骤把执行记录落到数据库表里,记录作业名、开始时间、结束时间、状态、错误数。这张表在事后复盘时价值极高,尤其是"昨天那次到底跑没跑"这种问题,翻日志文件太慢,查表一条 SQL 就出来。
重试不要做成无限循环。我一般设两层:单条转换内部对数据库连接类的瞬时错误重试 2 次,间隔 30 秒;job 层对整条链路设置最多重试 1 次,且只在失败步骤是"转换"类型时触发。重试前要确认写入是幂等的,纯插入模式重试会产生重复数据,这也是前面建议用"插入/更新"的原因。
断点续跑没有通用方案。可行的做法是按批处理日志表记录已完成的分片,比如按日期或按文件分片,重跑时先查日志表跳过已完成的分片。这套东西要自己搭,Kettle 本身不提供。投入产出比取决于数据量,小批量场景不如直接全量重跑。
5. 排错清单:六条来自真实报错的现象、成因与修法
下面六条是跑 Kettle 时出现频率最高的,按"现象 → 原因 → 解决"写,都是能直接对上号的。遇到没见过的报错,先按这个顺序过一遍,命中率不低。
5.1 中文变问号或乱码
现象:CSV 里中文正常,入库后全是???,或者变成一串看不懂的字符。
原因:两个环节都可能出错。一是源文件的实际编码和"文本文件输入"里选的编码不一致,源文件是 GBK 但选了 UTF-8,或者反过来;二是 JVM 默认编码和数据库连接串里的字符集参数没对齐,characterEncoding没写或者写错。
解决:先用file -i 文件名确认源文件真实编码,不要靠猜;源头步骤里显式选对应编码,不依赖自动推断;JDBC 连接串上显式带useUnicode=true&characterEncoding=utf8;启动参数里统一-Dfile.encoding=UTF-8。三处对齐之后,中文问题基本绝迹。
5.2 报找不到驱动类
现象:目录里明明有 mysql 的 jar,测试连接时提示ClassNotFoundException或者驱动类名不存在。
原因:jar 没放在lib/下,或者放了但没重启 Spoon;或者驱动版本和类名不匹配,8.x 的 jar 填了com.mysql.jdbc.Driver;或者同一个库里存在多个版本的 jar,加载到了旧的那个。
解决:先ls lib/ | grep 驱动名确认只有一个版本且位置正确,重启 Spoon 再测;类名按 jar 版本对齐,8.x 用com.mysql.cj.jdbc.Driver;如果是在 Docker 或集群里跑,确认镜像里的lib/也同步更新了,本地改了镜像没改是最常见的"本地能跑线上不行"。
5.3 跑着跑着内存溢出
现象:前几万行很快,之后越来越慢,最后抛OutOfMemoryError: Java heap space,或者整个进程被系统杀掉。
原因:堆给得太小;步骤之间的行集太大;某个步骤做了全量排序或聚合(排序记录、分组、去重)而未设置"临时文件目录"或"缓存行数";在转换里用了"预览"或"复制记录到结果"这类会把数据全捞进内存的操作。
解决:先把-Xmx调到机器内存的 50%~60%;把行集从 10000 降到 1000~2000;排序、去重类步骤显式指定临时目录并把"排序缓存大小"控制在几十 MB 级别;生产 ktr 里删掉所有调试用的预览步骤,这类步骤在界面里点一下就会把整个结果集拉进内存。
5.4 日期和金额对不上
现象:入库的日期差 8 小时,或者金额出现0.30000000000000004这种尾数。
原因:日期字段在源步骤里被推断成 String,格式串写错或者时区处理不当;金额字段在 Kettle 里是 Double 类型,浮点运算本身有误差,写进DECIMAL列也不会自动修正。
解决:源头就把日期改成 Date 类型并显式指定格式串,不要留 String;金额链路全程避免 Double,字段选择里直接改成BigDecimal(Kettle 里表现为 Number 类型配合精度设置),目标表用DECIMAL(18,2);连接串里如果有serverTimezone参数,统一成同一个时区,不要一半机器写UTC一半写Asia/Shanghai。
5.5 目标表锁等待或死锁
现象:转换卡在写库阶段不动,日志里出现Lock wait timeout exceeded,或者数据库侧报死锁。
原因:批量提交的行数太大,一个事务持有锁的时间过长;多条转换并发写同一张表;写入顺序和另一个事务的加锁顺序相反。
解决:把提交记录数量从 5000 往下降到 500~1000,缩短单事务持锁时间;同一张表的写入串行化,或者按主键区间分片;确认目标表的索引设计,缺索引会导致更新时扫全表,锁的范围远超预期。这类问题不要只在 Kettle 侧调,SHOW ENGINE INNODB STATUS看到的具体锁信息才是根因。
5.6 定时任务显示跑了但没数据
现象:cron 显示执行过,日志文件也有,但目标表当天没有数据。
原因:参数没传进去,${RUN_DATE}变成了字面量,源文件路径因此找不到,但步骤配了错误处理把异常吞掉了;或者工作目录不对,ktr 里用了相对路径,cron 环境下的当前目录和手动执行时不同;或者环境变量没加载,JAVA_HOME在交互式 shell 里有、在 cron 里没有。
解决:cron 里显式cd到脚本目录并source环境变量文件;ktr 里所有路径用绝对路径或命名参数,禁止相对路径;把-level临时调到 Detailed 跑一次,看日志里实际展开后的参数值;错误处理分支要有落表或告警,不能静默跳过,静默失败比报错更危险。
6. 版本验证与进阶用法:确认手里的 9.3 没被动过手脚
网盘分发的安装包有个现实问题:你没法从文件名判断里面到底是不是原版。我拿到任何第三方分发的 PDI 包,第一件事不是解压运行,是先做三项校验,花几分钟,能挡掉大部分改包风险。
第一项是启动后的版本号。Spoon 界面里帮助 → 关于,会显示完整的版本串和构建号;命令行更直接,./spoon.sh -version会打印版本后退出。把这里显示的版本和你预期的主版本对一下,9.3 的版本串应该以 9.3 开头,构建号是一串日期。如果显示的是 8.2 或者更早,说明包里混了老文件。
第二项是 lib 目录里核心包的版本一致性。Kettle 的版本升级会同时更新一批pentaho-*开头的 jar,这些 jar 文件名里带版本号,全部应该指向同一个版本段。
# 核心包版本一致性检查:正常情况下列表里只应出现一个版本段 ls -1 /opt/pdi/pdi-9.3/lib | grep -E "^pentaho-" | \ sed -E 's/.*-([0-9]+\.[0-9]+\.[0-9]+[^-]*)\.jar/\1/' | sort | uniq -c # 核验压缩包完整性,和分发方给的校验值逐个比对 sha256sum pdi-ce-9.3.0.0-*.zip > /tmp/pdi.sha256 cat /tmp/pdi.sha256第一条命令的输出如果出现两三个不同版本段,说明这个目录被不同版本的包覆盖过,最稳妥的处理是重新解压一份干净的,不要在原目录上打补丁。第二条命令产生哈希值,和分发方公布的比对;如果对方没给,至少把这个值记下来,下次换机器时能确认拿到的是同一个包。
第三项是插件目录。plugins/下如果出现来源不明的自定义插件,加载时会执行其中的代码,这在生产环境是不可接受的风险。我一般只保留官方自带的那几个目录,其余全部移走,需要什么能力自己加。
日常使用上,有一个值得坚持的习惯:把 ktr 和 kjb 全部纳入版本控制,.kettle/这类本地配置目录排除掉。ktr 是纯文本 XML,diff 结果可读,改了什么一目了然;出问题时回滚到上一个提交,比在界面上逐步撤销快得多。团队协作时约定好不要两个人同时改同一个 ktr,合并冲突处理起来比重写还费时间。
再往上一层是执行层的分离:Spoon 只用来开发和调试,生产执行统一走pan.sh和kitchen.sh,参数从属性文件来,日志按天归档,退出码进监控。这套东西搭起来不到一天,但它把"能跑"和"跑得稳、出问题能查"分开了。
从那以后我每次拿到一个新的 Kettle 分发包,都会先跑一遍版本串、lib 一致性、哈希这三项,再解压到固定路径试跑一条最小转换,确认链路通了才往项目里引。看起来多花二十分钟,实际省下的是后面排查"到底是环境问题还是包问题"的几个小时。希望帮到你。
本文还有配套的精品资源,点击获取