简介:面向数据工程师、ETL初学者及需要在受限网络环境中获取工具的技术人员,这份文档围绕开源ETL工具Kettle(Pentaho Data Integration)9.3版本展开,重点解决官方渠道访问不畅时的资源获取问题。压缩包内共1个docx文件,约14KB,篇幅精炼,主要承载Kettle 9.3安装包的百度云链接与提取码,并对工具定位、核心特性与版本信息作了简要说明。文档梳理了Kettle跨平台运行、图形化工作流设计、多数据源支持等能力,区分Transformation与Job两类脚本的职责,也提及9.3版本在性能与稳定性上的改进,以及对接对象存储等云服务的集成方向。借助这些内容,读者可先判断该工具是否契合自身的数据抽取、清洗与迁移场景,再据此获取安装包;对仍在使用旧版本的用户,也可作为升级前的快速参考。目前已有5743人学习。
1. Kettle 9.3 到手之后:先想清楚它替你省掉哪一步
有个做报表的朋友跟我吐槽,他每天上班第一件事是把两个业务库的订单表导成 CSV,再手工拼成日报,字段名一改就得从头再来一遍。后来换成拖几条线的做法,这件事压到了一分钟。这次拆的是 Kettle 9.3,从网盘直链下到的免安装分发包,属于典型的源码软件类绿色包,解压即用,不用跑安装程序。
Kettle 学名 PDI,纯 Java 写的开源 ETL 工具,Windows、Linux、Unix 都能跑。核心就两种文件:transformation 管一条数据流里的抽取、清洗、转换、写出,job 管整个工作流的调度、条件判断和错误分支。它的价值不在算得多快,而在于把「数据从 A 库搬到 B 表」变成一张能保存、能版本管理、能命令行调度、也能交给同事看的流程图。
适合两类人:一类是不想为每次导数写脚本的运营和分析同学,另一类是手里压着十几条定时任务、需要一个统一编排入口的数据工程同学。做云计算数据落地的,把对象存储当源或目标也能接进同一条链路里。
2. 环境与启动:JDK、目录结构和 Spoon 第一次打开
拿到压缩包之后最怕的就是双击没反应,然后开始怀疑包有问题。Kettle 9.3 的启动失败九成不在包本身,而在 JDK 版本、图形库依赖和内存参数这三件事上。这一章把解压到界面出来这条路径走一遍,顺带把每个参数为什么这么设讲清楚。
2.1 JDK 版本对齐:为什么坚持用 JDK 8 起
Kettle 9.3 的字节码目标是 Java 8,用 JDK 8 跑是最省心的组合。用 JDK 11 或 17 启动,Spoon 主界面通常能出来,但一碰大数据类插件、Hadoop 相关步骤或者某些第三方步骤,就容易抛NoClassDefFoundError或InaccessibleObjectException,因为高版本 JDK 的模块系统封了反射访问。这不是配置问题,是运行时不兼容,改配置改不出来。
常见做法是机器上并存多个 JDK,Kettle 单独指一个。Windows 改Spoon.bat顶部的JAVA_HOME,Linux 在启动脚本前 export,不要依赖系统默认的java。先确认版本,再谈启动:
# 指定 JDK 8,避免 9.3 在高版本 JDK 上的模块访问限制 export JAVA_HOME=/opt/jdk1.8.0_351 export PATH=$JAVA_HOME/bin:$PATH # 确认当前 shell 用的是哪个 java,版本不对后面全是玄学问题 java -versionJAVA_HOME指向 JDK 根目录而不是bin,PATH里把它放最前面,java -version输出里出现1.8.0才算对齐。如果这一步显示的是 11 或 17,后面 Spoon 起来也别急着建转换,先把版本换掉。
内存参数跟着 JDK 一起设。Kettle 的堆由PENTAHO_DI_JAVA_OPTIONS控制,不设的话默认值偏小,几十万行数据加上几个排序步骤就会顶到天花板:
# 堆内存给到 2G 起步,字符集强制 UTF-8,避免日志和中文一起乱 export PENTAHO_DI_JAVA_OPTIONS="-Xms1g -Xmx2g -Dfile.encoding=UTF-8"-Xms和-Xmx设成一样能减少堆伸缩带来的停顿,-Dfile.encoding=UTF-8影响的是 JVM 默认字符集,后面处理 CSV 编码时会用到。机器内存够、单次处理上百万行,-Xmx可以往 4G 走,但别超过物理内存的七成,留出给数据库连接和操作系统的余量。
2.2 解压后的目录结构:驱动往哪放,配置在哪改
网盘拿到的包解压出来是一个># 解压绿色包到统一位置 unzip pdi-ce-9.3.zip -d /opt/ # KETTLE_HOME 决定 kettle.properties、日志、缓存的落点 export KETTLE_HOME=/opt/data-integration cd $KETTLE_HOME # 数据库驱动必须放 lib,放错位置等于没放 cp ~/drivers/mysql-connector-java-8.0.28.jar $KETTLE_HOME/lib/ cp ~/drivers/ojdbc8.jar $KETTLE_HOME/lib/
最后两行是最容易翻车的地方。驱动 jar 放进plugins/下某个子目录、或者只放在转换文件旁边,Spoon 里测试连接可能过得去,一到命令行pan.sh执行就报驱动类找不到,因为两边的类加载路径不一样。统一放lib/,图形界面和命令行用的是同一套。
放完驱动不用重启也能试连接,但如果在 Spoon 已打开的情况下才拷进去,最好关掉重开一次,插件扫描是启动时做的。
2.3 第一次打开 Spoon:界面上的四个区域
Windows 双击Spoon.bat,Linux 执行./spoon.sh。在无桌面环境的服务器上直接跑会抛No more handles或 SWT 相关异常,因为 Spoon 是 SWT 图形程序,必须有 X 显示;纯命令行场景应该直接用pan.sh和kitchen.sh,不要试图在服务器上开界面。
Linux 上如果报 GTK 或 WebKit 缺失,是内嵌浏览器组件没装全,缺的是图形库不是 Java 包,按发行版的包管理器补上 GTK3 相关依赖即可。这个坑很典型:报错信息里全是 Java 类名,很容易误判成 JDK 问题,实际是系统层缺库。
界面打开后认四个区域就够了。左侧是「核心对象」树,按分类列出所有可用步骤,输入、输出、转换、连接、统计、脚本都在里面;中间是画布,转换和作业都在这里拖拽搭建;上方工具栏有新建、打开、保存和执行按钮;下方是执行结果面板,日志、步骤度量、性能曲线都在这一栏。第一次用建议先点「新建转换」,随便拖一个「生成记录」加一个「空操作」,连一条线跑起来,确认执行链路通了再动真数据。
保存时注意扩展名:转换存成.ktr,作业存成.kjb,两个不能混。.ktr用 Spoon 打开,.kjb也是,但命令行执行时前者用pan.sh、后者用kitchen.sh,搞混了会直接报文件类型不匹配。
3. Transformation 与 Job:两种文件的分工和第一个能跑的转换
搞不清.ktr和.kjb的区别,是新手第一个卡点。简单说:转换是一条数据流水线,数据从左边进、右边出;作业是一个调度器,管的是「先跑哪个转换、失败了走哪条分支、跑完发不发通知」。这一章把两种文件的结构拆开,再用一个 CSV 入库的例子把转换串起来。
3.1 转换的步骤-跳线模型:行是怎么在步骤之间流动的
转换由「步骤」和「跳线」组成。每个步骤在运行时是一个独立线程,步骤之间不直接传对象,而是通过一个叫 RowSet 的环形缓冲区传数据行。上游步骤往里写,下游步骤从里读,缓冲区满了上游就阻塞等待。这个设计决定了 Kettle 是流式、并行的,也决定了它的内存行为:缓冲区大小由转换属性里的size_rowset控制,默认 10000 行,调大能提升吞吐,调大过头就是 OOM。
跳线有两种模式,右键跳线可以切换,这是最容易被忽略的设置:
- 复制(Copy):上游的每一行都完整发给所有下游步骤。默认就是这个,一个源接两个目标时,两边拿到的行数一样。
- 分发(Distribute):按轮询把行分给不同下游,一个源接两个目标时,两边各拿一半。
很多人做「一份数据同时写明细表和汇总表」,结果汇总表里数字少一半,就是跳线被设成了分发。反过来,想做并行分片处理却用了复制,等于同一批数据算了两遍。
另一个高频误解是排序。Kettle 的「分组」步骤要求输入已经按分组字段有序,「排序记录」不是可选优化而是前置条件,不排就分组,结果会是错的而不是报错,属于最阴的一类问题。排序步骤本身也吃内存,数据量大时要么加大堆,要么在数据库侧用ORDER BY排好再进来。
3.2 作业的控制流:分支、条件和失败处理
作业里的元素是「条目」,条目之间也是跳线,但传的不是数据行而是执行状态。一条跳线可以设成「成功时走」或「失败时走」,也可以无条件走。这让作业能做条件分支:先检查文件在不在,在就走转换,不在就走告警分支。
常用的作业条目不多,够用的就这几类:
| 条目 | 典型用途 | 关键配置 |
|---|---|---|
| 转换 | 调用一个.ktr | 指定文件路径、传参 |
| 检查文件是否存在 | 上游文件到没到 | 文件路径支持变量 |
| 检查表是否存在 | 目标表要不要建 | 连接名 + 表名 |
| Shell | 调外部脚本、解压、搬文件 | 脚本路径 + 参数 |
| JavaScript | 写复杂判断逻辑 | 用变量接结果 |
| 发送邮件 | 失败告警 | SMTP 配置 |
| 设置变量 | 把上一步结果变成变量 | 作用域要选对 |
作业默认是串行执行的,一个条目跑完才走下一条。想要并行,得在作业属性里开启「运行下一个条目时启动本条目」,或者在条目上勾选「单独运行」。并行不是越多越好,同一个目标表被两个分支同时写,锁冲突比串行还慢。
3.3 一个 CSV 到数据库的完整转换
场景很朴素:每天一个订单 CSV 落到固定目录,读进来、过滤掉金额为 0 的记录、写进订单表。四个步骤就能搞定——「CSV 文件输入」「过滤记录」「字段选择」「表输出」。搭建完之后保存成.ktr,文件本身的 XML 结构大致是这样:
<transformation> <info> <name>csv_to_order</name> <!-- 步骤间缓冲区行数,调大提升吞吐也调大内存占用 --> <size_rowset>10000</size_rowset> <step_performance_capturing_delay>1000</step_performance_capturing_delay> </info> <step> <name>订单CSV</name> <type>CSVInput</type> <!-- 编码必须和文件实际编码一致,GBK 文件写成 UTF-8 就是乱码 --> <encoding>GBK</encoding> <separator>,</separator> <header>Y</header> <!-- 延迟转换:先按字符串读进来,类型转换交给后面的字段选择 --> <lazy_conversion>Y</lazy_conversion> <file> <name>${DATA_DIR}/order_${RUN_DATE}.csv</name> </file> </step> <step> <name>过滤有效单</name> <type>FilterRows</type> <!-- 条件写在 send_true / send_false 两条跳线上 --> </step> <step> <name>写入订单表</name> <type>TableOutput</type> <connection>order_db</connection> <table>dwd_order</table> <!-- 每 2000 行提交一次,太小事务多、太大回滚慢 --> <commit>2000</commit> <!-- 批量插入,比逐行 insert 快一个数量级 --> <use_batch>Y</use_batch> </step> <hop> <from>订单CSV</from><to>过滤有效单</to><enabled>Y</enabled> </hop> <hop> <from>过滤有效单</from><to>写入订单表</to><enabled>Y</enabled> </hop> </transformation>几个参数值得单独说。size_rowset是全局缓冲区行数,10 万行以下的日常任务 10000 够用,上百万行可以调到 50000,但要同步把-Xmx加上去。encoding必须和文件实际编码对齐,同一个转换里不同来源文件编码不一致时,拆成多个输入步骤分别设,不要指望一个全局设置通吃。lazy_conversion打开后所有字段先当字符串读,好处是脏数据不会在读入阶段就整批失败,坏处是类型转换推迟到下游,出错时定位要多跳一步。commit和use_batch是写入性能的关键,2000 到 5000 是比较稳的区间,太小频繁提交,太大一次失败回滚的数据量也大。
「过滤记录」的条件写在步骤属性里,比较符支持=、<>、>、IS NULL等,字符串比较要注意大小写和空格。过滤之后两条跳线,真值走目标表,假值可以接一个「空操作」或者写一张异常表,别让分支悬空,悬空的分支不会报错但也不会执行。
调试阶段用「预览」按钮看每个步骤的输出,比跑完再看结果快得多。预览默认只取 1000 行,看样本够用;想验证全量逻辑,用「执行」然后看下方「步骤度量」里的读写行数,两边对不上就直接定位到出问题的步骤。
4. 命令行与定时调度:pan、kitchen 的参数化落地
图形界面适合搭流程,不适合每天跑。真正上生产是把.ktr和.kjb交给pan.sh、kitchen.sh,由定时任务调度。这一层的核心是两件事:参数怎么传进去,失败了怎么被发现。
4.1 pan 与 kitchen 的常用参数
两个命令的参数高度一致,记住一套就够:
| 参数 | 含义 | 实践建议 |
|---|---|---|
-file= | 指定 ktr / kjb 文件绝对路径 | 一律写绝对路径,别依赖当前目录 |
-param:名称=值 | 传命名参数 | 日期、批次号这类每次都变的走这里 |
-level= | 日志级别 | 日常用Basic,排错用Detailed,追行级用Rowlevel |
-logfile= | 日志输出到文件 | 按日期命名,方便回溯 |
-dir= | 资源库目录 | 用文件方式时不需要 |
-norep | 不连资源库 | 文件方式执行时加上,省一次连接 |
-listparam | 列出转换里声明的参数 | 接手别人的转换先跑这个 |
-level的四个档位差别很大。Basic只打关键节点,日志小但信息少;Detailed会打出每个步骤的读写行数,排「数据少了一半」这类问题必备;Debug再往下加内部状态;Rowlevel逐行打印,只在定位单行异常时开,全量跑会拖慢几倍并写爆磁盘。
4.2 命名参数与变量:作用域搞错等于没传
Kettle 里有两套传值机制,混淆是常见翻车点。命名参数在转换或作业里用${名称}声明,外部用-param:传,优先级最高;全局变量存在kettle.properties里,启动时加载,同一台机器上所有任务共享。传参时命名参数覆盖全局变量,这个顺序记牢。
作用域还有一个细节:在作业里用「设置变量」步骤设的值,默认只在当前作业有效,子转换读不到。要让子转换也能用,得把步骤里的「变量作用域」设成「在父作业中有效」,或者干脆全部走-param传,省得记这些规则。
#!/bin/bash set -u export JAVA_HOME=/opt/jdk1.8.0_351 export KETTLE_HOME=/opt/data-integration LOG_DIR=/data/etl/logs RUN_DATE=$(date +%F) # 命名参数优先于 kettle.properties,日期和目录每次由调度器决定 $KETTLE_HOME/pan.sh \ -file=/data/etl/ktr/csv_to_order.ktr \ -param:RUN_DATE="$RUN_DATE" \ -param:DATA_DIR=/data/incoming \ -level=Detailed \ -logfile="$LOG_DIR/csv_to_order_$RUN_DATE.log" \ -norep CODE=$? # 退出码非 0 才算失败,不要用日志里有没有 ERROR 来判断 if [ $CODE -ne 0 ]; then echo "[$(date '+%F %T')] 转换失败,退出码 $CODE,日志 $LOG_DIR/csv_to_order_$RUN_DATE.log" exit $CODE fi echo "[$(date '+%F %T')] 转换完成"这段脚本的三个要点:RUN_DATE用date +%F生成而不是写死,保证每天跑的是当天文件;-logfile按日期分文件,出问题能精确回溯到某一天;最后的退出码判断是告警的入口,$?拿到的是pan.sh的返回值,不是 shell 脚本里某条命令的。
4.3 退出码与日志:让失败主动冒出来
pan.sh和kitchen.sh的退出码是有语义的:0 表示成功,1 是一般错误,2 是意外错误,3 是加载转换失败,7 是初始化失败,8 是插件加载失败。定时任务里只判断「非 0」就够告警,但要快速定位,区分 3 和 7 很有用——3 通常是文件路径写错或 ktr 损坏,7 多和 JDK、内存参数有关,8 基本是插件目录被改动过。
日志里值得盯的几行:Finished processing后面的耗时和读写行数,ERROR开头的行,以及Transformation detected之类的告警。有些转换会「部分成功」——某些行被跳过、写入失败但整体退出码是 0,这种只能靠步骤度量里的读写行数差额发现。所以调度脚本之外,最好再配一个行数对账,这个放到最后一章讲。
cron 里配任务时加个flock防重入,避免上一次没跑完下一次又起来,两个实例写同一张表,锁等待能把整条链路拖死。
5. 避坑与排查:五个真踩过的坑
这一章的五条都是实际跑任务时撞出来的,现象、原因、解决办法按顺序写,遇到同类问题时可以直接对照。
5.1 Spoon 双击没反应或闪退:先查 JDK 再查图形库
现象:Windows 上双击Spoon.bat窗口一闪就没了,Linux 上执行spoon.sh报一堆 SWT 类名后退出,或者干脆提示No more handles。
原因有两个方向。一是 JDK 版本不对,系统默认java是 11 或 17,SWT 图形库在模块系统下加载失败,报错堆栈里全是 Java 类,看着像代码问题其实是运行时问题。二是 Linux 缺图形依赖,Spoon 内嵌浏览器用到 GTK 和 WebKit 相关库,缺了就起不来。
解决:先在终端跑java -version确认版本,不对就在启动脚本里硬编码JAVA_HOME。Linux 上把 GTK3 相关的运行库补齐,服务器无桌面环境时不要试图开界面,直接用pan.sh跑转换。还有一个隐蔽情况:libswt/目录被误删或被旧版本覆盖,重启前先确认这个目录存在且完整。
5.2 CSV 中文读进来是乱码:编码设了但设错了层
现象:CSV 文件用记事本打开中文正常,进 Kettle 后字段值变成问号或方块,写入数据库后也是乱码。
原因不在数据库,在读取那一步的编码。文件实际是 GBK,步骤里默认或误设成 UTF-8;另一种情况是文件没问题,但 JVM 的file.encoding是系统默认的非 UTF-8,导致日志和部分字符串处理出现偏差。
解决:在「CSV 文件输入」步骤里明确指定encoding,一个文件一种编码,多个来源就拆多个输入步骤。启动参数里加-Dfile.encoding=UTF-8覆盖 JVM 默认值。写入端如果目标表是 UTF-8 字符集,连接串里也把字符集参数带上。判断编码不要靠猜,Linux 下file -i 文件名能直接看出来。
5.3 跑到几十万行就 OOM:缓冲区、排序和提交批次一起看
现象:小数据量跑得好好的,数据量一上来就java.lang.OutOfMemoryError: Java heap space,有时候是卡在某个排序步骤不动。
原因通常是三个叠加:步骤间缓冲区size_rowset设得过大,每增加一行都占内存;「排序记录」步骤把全量数据读进内存排,数据量一大直接顶穿;-Xmx还是默认的小值,没跟着数据量调整。
解决:先看日志里 OOM 前最后完成的步骤,基本就是元凶。排序类操作优先下推到数据库用ORDER BY,非要在 Kettle 里排就加大堆内存并考虑分片。size_rowset从 10000 起调,不要盲目上十万。表输出端的commit设 2000 到 5000,配合use_batch批量插入。如果转换里有「分组」,确认输入已排序,否则它内部还会再排一次,内存翻倍。
5.4 报驱动类找不到:lib 目录和 JDBC URL 版本要配对
现象:Spoon 里测试连接成功,命令行pan.sh执行报ClassNotFoundException找不到驱动类;或者连接能建,但执行 SQL 时报时区、字符集相关的异常。
原因:驱动 jar 放在了plugins/或项目目录下,图形界面能扫到,命令行类加载路径不同就找不到;另一种是驱动版本和数据库服务端版本不匹配,比如用旧版驱动连新版本库,URL 里不带时区参数就会报错。
解决:驱动统一放lib/,放完重启 Spoon。连接串里把必要参数补全,常见的是时区和服务端字符集。多个项目共用一个安装目录时,不同版本驱动会冲突,同一个库只留一个版本的 jar,需要多版本就在不同KETTLE_HOME下隔离。
5.5 定时任务第二天数据翻倍:增量条件没生效或任务重入
现象:手工跑一次结果正常,上了定时任务之后某天开始数据量翻倍,越跑越多。
原因:转换里用的是全量抽取,每次跑都往目标表追加一遍;或者增量条件写的是WHERE create_time > '${LAST_TIME}',但LAST_TIME没被正确更新,每次都是同一个值;再或者上一次任务没跑完,下一次又启动,两个实例同时写。
解决:增量抽取必须有明确的水位线字段,并且每次成功后把新的水位写回配置文件或控制表,不能依赖转换里的固定值。调度层用flock加锁防重入。目标表如果是追加写,转换里加一步「删除当天分区」再插入,做幂等。改完之后连续跑两次对比行数,是最直接的验证方式。
6. 增量水位线与对账:一个让我少加班的收尾习惯
全量重跑在小表上无所谓,数据一上千万行,每次都全量就是把调度窗口往死里挤。我现在的默认做法是给每张表定一个水位线字段,一般是自增主键或者更新时间,转换开始时先从控制表读上一次的值,抽取条件带上> ${LAST_ID},跑完再把本批最大值写回去。
写回这一步最容易做错。变量在作业和转换之间的可见性有坑,稳妥做法是用「表输入」取最大值,接「复制记录到结果」,再在作业里用「从结果获取变量」把它变成作业级变量,最后由「执行 SQL 脚本」更新控制表。整条链路串起来是这样:
-- 控制表只存一行,记录每张表上次抽到哪 CREATE TABLE etl_watermark ( table_name VARCHAR(64) PRIMARY KEY, last_id BIGINT NOT NULL, update_time TIMESTAMP NOT NULL ); -- 抽取时只拿水位线之后的数据 SELECT id, order_no, amount, create_time FROM ods_order WHERE id > ${LAST_ID} ORDER BY id;last_id用自增主键比时间戳可靠,时间戳会遇到同一秒多行、时钟回拨、业务补录历史数据这几类问题。控制表用UPDATE而不是INSERT,保证只有一行。
跑完之后的对账更不能省,三行 SQL 就能看出是漏数还是重复:
-- 源和目标按同一天对账,行数和金额两个维度一起看 SELECT 'src' AS side, COUNT(*) AS cnt, SUM(amount) AS amt FROM ods_order WHERE dt = '${RUN_DATE}' UNION ALL SELECT 'dst', COUNT(*), SUM(amount) FROM dwd_order WHERE dt = '${RUN_DATE}';cnt和amt两边都相等才算过,只对行数会漏掉金额被截断或者类型转换丢失精度的情况。这套对账我一般挂成作业的最后一步,不一致就发邮件,比事后被业务问「昨天的数怎么少了」要体面得多。
血泪经验是:水位线逻辑上线前,一定手工把同一个批次连跑两遍,看第二遍是不是真的零新增。我吃过一次亏,条件是>=而不是>,第一天翻倍,第二天又翻倍,最后是凌晨对着控制表一行行看完才定位到。从那以后我每次改抽取条件,都会先小批量跑两次做幂等验证,再放到调度里。希望帮到你。
本文还有配套的精品资源,点击获取