简介Kettle 的 Web 化版本资源包面向数据集成工程师、ETL 开发者及需要远程协作的数据团队用于在浏览器中完成转换与作业的设计、编辑和运行弥补传统 Spoon 桌面端在分布式协作和跨设备访问上的不足。压缩包共 1002 个文件、约 157.7MB以 482 个 png 界面素材、284 个 jar 依赖库和 112 个 class 编译文件为主体同时包含 svg/gif 图标、xul 界面描述、sh/bat 启动脚本以及 xml、properties 等配置与少量前端资源构成较完整的 Web 应用目录。已有 1554 人学习说明其在 webKettle 部署与二次研究场景中具有一定参考价值。包内保留 Web 端所需的依赖、页面资源与脚本可用于搭建浏览器端 ETL 工作台支持转换作业管理、多人协作编辑及 REST API 集成等实践也便于读者研究 Kettle 从桌面工具向 Web 环境迁移时的工程结构、资源组织与运行依赖。1. 三个 Kettle 版本、两台机器、一堆散落的 .ktrweb 版先解决的是这本糊涂账数据组最典型的场面同事 A 笔记本上装着 Kettle 8服务器上跑着 Kettle 7某个 .ktr 在 A 那里点一下就出数挪到服务器报「找不到步骤插件」。再往后谁改了哪个转换、昨天凌晨那批为什么少两万行、失败之后从哪一步重跑全靠翻聊天记录。Kettle 的 web 版这类资源解决的就是这本糊涂账它把转换和作业的执行、定时、日志、重跑收拢到一个浏览器入口而转换本身还是那套 .ktr / .kjb不用重画。适合已经把 Kettle 用顺手、却被单人单机模式卡住的团队也适合刚接手一堆历史脚本、需要先把现状盘清楚的人。它不替代 Spoon替代的是散在各人电脑上的 pan.sh 和 crontab。2. 拆开 Kettle Web 版控制面上了浏览器执行引擎还是原来那套先划清一件事不然容易抱着不切实际的期待。很多人一听「web 版」以为是浏览器里拖拽画转换其实主流做法不是这样转换的设计仍然在 Spoon 里完成web 端接管的是提交、执行、调度、监控、日志、权限这一层。引擎侧调用的还是 kettle-core / kettle-engine 那套 Java API跑的还是同一个 Trans / Job 对象。想明白这条边界后面所有参数和坑都能对号入座。2.1 Spoon 管设计Web 版管执行能力边界划在哪Spoon 是设计器兼执行器一个人一台机器的时候它够用一旦涉及多人、多环境、定时、追溯它的短板就全暴露出来了——转换文件靠拷贝传递、执行靠命令行、日志在控制台滚完就没了、失败重跑靠人肉判断。Web 版补的正是这几块。能力Spoon 客户端Carte 独立服务Web 版资源包画转换 / 作业支持不支持不支持上传现成文件远程触发执行需手工连支持 HTTP 调用页面点选 接口触发定时调度无无内置调度器运行日志留存控制台文件落库可按批次查多环境参数切换手工改环境变量页面配置注入失败重跑人肉手工按批次重跑从这张表就能看出选型逻辑如果你的转换还在频繁改动、天天要调步骤那主力工具仍然是 Spoonweb 端只当执行入口用如果转换已经固化成「每天跑一遍」的流水线那它就该待在这个框里。2.2 三层结构页面上的一次点击底下走了哪几步拆开看就三层出问题的时候按层定位比漫无目的地翻日志快得多。页面层负责转换和作业的上传、参数填写、任务列表、运行记录查询。服务层是主体一个 Java 服务管任务元数据、调度触发、执行线程池、日志归集、失败重试。引擎层是 Kettle 本身负责解析 .ktr / .kjb、建立数据库连接、按步骤跑数据流。三层之间是同步调用加异步执行的关系页面发起请求后服务层把任务丢进线程池就返回真正的执行在后台进度靠任务 id 回查。层职责常见实现出问题时的典型症状页面层上传、参数、列表、记录前端静态资源 REST页面能开但列表空、上传报 413服务层调度、线程池、日志归集Spring Boot 调度框架任务状态一直「运行中」、调度不触发引擎层解析文件、连库、跑数据流kettle-core / kettle-engine报找不到插件、连不上库、行数不对存储层任务配置、运行记录、步骤日志关系库记录写不进去、中文乱码定位口诀页面打不开看服务层端口和静态资源任务能建但不跑看调度跑了没数据看引擎层跑成功但查不到记录看存储层。这套分法看着朴素真到半夜排障能省不少时间。2.3 四种不适合使用它的场景提前知道才不至于白费功夫第一转换逻辑还在高频迭代。页面改不了步骤每改一次就得回 Spoon 重传来回折腾的反而不如直接命令行。第二只有一台机器、一天跑两三个转换、没有协作诉求——crontab 加 shell 脚本十分钟就能搭完引入一套 web 服务是给自己找维护量。第三需要实时或准实时同步。Kettle 的定位是批量用它做分钟级 CDC 会很难受。第四单次处理数据量特别大、又舍不得给内存。Kettle 的行集在内存里流转几千万行的大表不走分片就是硬扛web 端只是把这个问题放大了——它让并发变容易也就让 OOM 变容易。3. 本地跑起来从建库到第一个转换出数这一章按顺序走一遍目标是让一个 .ktr 在浏览器里点一下能跑通、能看到日志。路径和库名我用了通用的写法实际以你解压后 conf 目录里的配置项为准字段名各版本会有出入别硬套。3.1 环境对齐JDK、驱动、字符集三样先定死JDK 版本跟着 Kettle 引擎走引擎是什么版本 compiled 的服务就用对应大版本别跨太多。数据库驱动单独放进 lib别指望服务里自带的能用——MySQL 8 之后驱动类名换成了com.mysql.cj.jdbc.Driver老包名已经废弃配置文件里写错就是启动即报错。字符集在建库那一步就要定成 utf8mb4后面再改要动表动连接串成本翻几倍。# 1. 建库字符集一次定死 mysql -uroot -p -e CREATE DATABASE kettle_web DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 2. 导入资源包里的建表脚本任务表、调度表、日志表一般都在里面 mysql -uroot -p kettle_web /opt/etl-web/sql/init.sql # 3. 核对时区增量抽取最容易栽在这 mysql -uroot -p -e SELECT global.time_zone, session.time_zone, NOW(); # 4. 把驱动丢进 lib确认版本 ls -l /opt/etl-web/lib | grep -i mysql第 3 步别跳过。数据库时区和 JVM 时区不一致的时候NOW()和 Kettle 里「获取系统信息」拿到的值会差几个小时增量抽取的 where 条件就会漏数据而且不报错只是每天少几条很难被发现。看到SYSTEM就顺手在连接串里显式写上serverTimezoneAsia/Shanghai。3.2 配置文件与启动几个参数决定它能不能长期跑配置文件里真正需要改的没几项其余保持默认。下面这份是常见形态字段名对照你解压出来的那份改。server: port: 8080 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/kettle_web?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: kettle password: 换成你自己的 driver-class-name: com.mysql.cj.jdbc.Driver kettle: home: /opt/etl-web/data # KETTLE_HOME插件和 kettle.properties 从这里找 repository: /opt/etl-web/ktr # .ktr / .kjb 存放根目录页面上的路径都相对它 log-level: Basic # None / Error / Minimal / Basic / Detailed / Debug / Rowlevel pool-size: 4 # 执行线程池见 4.3启动命令有三个参数必须带cd /opt/etl-web nohup java -Dfile.encodingUTF-8 \ -Djava.awt.headlesstrue \ -Xms512m -Xmx2g \ -jar etl-web.jar --spring.config.location./conf/application.yml \ logs/stdout.log 21 -Dfile.encodingUTF-8管中文路径和中文日志缺了就是一堆问号-Djava.awt.headlesstrue让引擎在没有显示设备的服务器上不去碰图形类很多启动期报错都是漏了这个-Xmx后面 4.3 会细说先给 2g 起步。启动后先tail -f logs/stdout.log看有没有初始化异常再开浏览器别一上来就传转换。3.3 第一次执行加载 .ktr 并拿到真实结果页面点「运行」背后干的事用一段代码就能说清。这段也适合写进自己的调试类里页面报错时直接跑一遍能快速区分是文件问题、参数问题还是引擎问题。// 1. 引擎全局初始化整个进程只做一次耗时几秒到十几秒 KettleEnvironment.init(); // 2. 每次执行都新建 TransMeta不要静态缓存原因见 5.5 TransMeta transMeta new TransMeta(/opt/etl-web/ktr/ods_user_sync.ktr); // 3. 注入变量对应 ktr 里 ${DB_HOST} 这类占位 transMeta.setVariable(DB_HOST, 10.0.0.12); transMeta.setVariable(WORK_DIR, /data/etl/ods_user); // 4. 命名参数必须在 execute 之前设少一个直接抛异常 transMeta.setParameterValue(BATCH_DATE, 2024-06-01); // 5. 执行 Trans trans new Trans(transMeta); trans.setLogLevel(LogLevel.BASIC); trans.execute(null); // 传 null 表示无命令行参数 trans.waitUntilFinished(); // 阻塞直到结束 // 6. 判断结果errors 为 0 不代表数据对还要看写行数 if (trans.getErrors() 0) { throw new RuntimeException(转换失败错误数 trans.getErrors()); } long written trans.getTransMeta().getStep(表输出).getStatus() null ? -1 : -1; // 实际写行数从日志表或 TransListener 取更好 trans.cleanup(); // 释放连接和线程漏了就是内存泄漏几个点分开说。KettleEnvironment.init()千万别放在每次请求里它要扫插件目录、注册步骤类型重复调用会让首次响应慢到十几秒标准做法是挂在服务启动钩子里预热一次。setVariable和setParameterValue是两套东西变量是运行期文本替换设不设都不影响加载命名参数在 .ktr 里是有定义的execute之前缺一个就报「参数未赋值」。写模板的时候凡是批次日期这类每次必传的用命名参数凡是库地址这类环境相关的用变量。waitUntilFinished()会阻塞当前线程所以它一定跑在执行线程池里不能直接在调度线程上调。最后cleanup()要放在 finally 里异常路径也要走到。3.4 变量与参数把写死的路径挪出去拿到别人的 .ktr 第一件事不是跑是搜一遍里面有没有写死的绝对路径和库名。改法是把它们全换成占位符赋值收口到一处。类型定义位置赋值时机典型用途缺了会怎样命名参数.ktr 的「命名参数」页签execute 之前批次日期、环境标识直接抛异常跑不起来变量kettle.properties / 代码 setVariable运行期任意时刻库地址、目录前缀占位符原样输出报文件找不到上一步字段数据流内部步骤之间行级处理步骤配置报错优先级从高到低大致是代码或命令行传入 上游作业传入 kettle.properties 步骤里写死的默认值。排查「参数没生效」的时候按这个顺序倒着找一般到第二层就能定位。4. 调度与监控把 crontab 那套搬到页面上之后坑也跟着搬过来了能手工跑了下一步就是定时。这块看起来简单——填个 cron 表达式而已——实际上问题最集中因为它同时牵扯调度框架、时区、并发控制和日志存储四件事。4.1 调度模型cron 表达式的段数、时区和并发先记住一件事Quartz 系的 cron 是六到七段第一位是秒跟 Linux crontab 的五段不是一回事。从 crontab 直接复制粘贴最常见的后果是解析失败其次是把「每天 2 点」写成「每 2 分钟」。场景正确写法容易错在哪每天凌晨 2 点0 0 2 * * ?照抄 crontab 少一位写成0 2 * * *解析报错每 10 分钟0 0/10 * * * ?写成*/10 * * * *段数不够每月 1 号 6 点0 0 6 1 * ?日和周同时写具体值会被判冲突工作日 8 点0 0 8 ? * MON-FRI周字段写成1-5有的实现不认调度触发只做一件事——把任务丢给执行池自己立刻返回DisallowConcurrentExecution // 同一任务不允许重叠执行防上一批没跑完下一批又起来 public class KtrQuartzJob implements Job { Override public void execute(JobExecutionContext ctx) { JobDataMap data ctx.getMergedJobDataMap(); String ktrPath data.getString(ktrPath); String batchDate data.getString(batchDate); // 可支持 ${yyyy-MM-dd} 这类占位 String taskId data.getString(taskId); // 关键不在调度线程里同步跑转换交给独立执行池 EtlExecutors.submit(taskId, ktrPath, batchDate); } }为什么不在这里直接跑调度框架自己的工作线程池通常就十几个转换一跑半小时几个任务就能把它占满后面所有任务全部排队饿死表现是「定时的都没触发」实际上触发了、只是没线程可用。执行池和调度池分开是这套结构里最值钱的一条经验。4.2 日志采集三张表撑起可追溯运行记录这张表要自己建字段不用多但几个关键的不能省CREATE TABLE etl_run_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, task_id VARCHAR(64) NOT NULL COMMENT 任务标识, ktr_name VARCHAR(128) NOT NULL COMMENT 转换文件名, batch_date VARCHAR(16) NOT NULL COMMENT 业务批次日期, status TINYINT NOT NULL COMMENT 0运行中 1成功 2失败, start_time DATETIME NOT NULL, end_time DATETIME NULL, read_rows BIGINT DEFAULT 0, write_rows BIGINT DEFAULT 0, error_rows BIGINT DEFAULT 0, err_msg TEXT NULL, KEY idx_ktr_date (ktr_name, batch_date), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;status是重跑判断的依据重跑前先查这个批次是不是已经成功避免重复灌数write_rows是判断「跑成功但没数据」的唯一抓手err_msg存前 2000 字符就够完整的还是落文件。引擎侧怎么拿到这些值日志文本通过KettleLogStore.getAppender()挂一个 appender 抓行数从步骤度量或转换结果里取。appender 用完必须 remove否则每执行一次就多挂一个见 5.5。还有一类是步骤级日志表用来定位「到底卡在哪个步骤」。它需要单独的 DDL通常在初始化脚本里就有没有的话手工建一次然后在转换的日志配置里选上。日常排障顺序是先看 run_log 判断成功失败再看步骤日志定位到具体步骤最后看详细日志里的 SQL。三层递进比一上来就开 Debug 级别强。4.3 并发、内存与资源参数调大之前先算一笔账Kettle 的数据流在内存里以行集形式流转每个步骤之间都有一个缓冲区默认一万行。一个二十步的转换、每行一 KB光缓冲区就是两百 MB 级别再乘并发数。所以资源参数不是越大越好是先算峰值再定上限。参数起步值调整依据调大的代价-Xmx2g单次峰值 × 并发数 30% 余量GC 停顿变长反而拖慢行集大小10000 行字段特别宽时降到 1000~2000太小吞吐掉一半执行池并发2~4CPU 核数的一半左右抢数据库连接互相等锁调度线程数10定时任务条数和执行池混用会互相拖死日志级别Basic排障时临时 DetailedRowlevel 会写爆磁盘调并发之前先问一句瓶颈在数据库还是在本机如果目标是同一个库的同一张表并发调大只会让锁等待变长整体耗时反而上升。真正需要并发的场景是多个互不相干的源库同步或者分片跑同一张表的不同区间。5. 排查与避坑五条踩过的记录按现象—原因—解决写这一章的五条都是「照文档做完全没问题、真跑起来就出事」的类型。每条按现象、原因、解决三步写方便直接对号。5.1 启动即报图形类异常或 NoClassDefFoundError 指向 swt现象服务起不来日志里出现java.awt.HeadlessException或者某个org.eclipse.swt开头的类找不到。原因资源包的 lib 里混进了 kettle-ui 这一层的图形依赖。Spoon 需要图形界面引擎不需要但依赖传递的时候容易一起被带进来。服务器上没有显示设备一碰图形类就炸。解决打包时排除 swt 相关 jar只保留 kettle-core、kettle-engine、kettle-dbdialect 和数据库驱动启动参数补上-Djava.awt.headlesstrue。验证办法是启动后看日志里KettleEnvironment.init()有没有正常打印版本信息打印出来就说明引擎层通了。5.2 本地跑得好好的转换服务器报找不到文件现象同一份 .ktr在本地 Spoon 里跑完出数传到服务器上执行报「Couldnt find file」或者输出到了一个莫名其妙的目录。原因两个。一是路径里带 Windows 反斜杠Linux 下直接当文件名的一部分二是用了相对路径而相对路径的基准是进程工作目录不是你以为的 ktr 所在目录。解决所有路径统一正斜杠并用变量拼${WORK_DIR}/ods_user/WORK_DIR在启动时注入启动脚本里先cd到固定目录再执行 java这一条能消掉一半的路径问题文本文件输入这类步骤把「从字段获取文件名」打开源目录用变量给避免写死。5.3 定时设了每天 2 点结果没触发或者疯狂重复现象要么一整天没动静要么两分钟跑一次把库压得很难受。原因cron 段数不对是最常见的其次容器时区是 UTC页面上显示的「下次执行时间」按 UTC 算跟业务时间差 8 小时还有一种情况是服务部署了两个实例两个调度器都在跑同一批任务。解决先在页面上看「下次触发时间」的回显回显不对就是表达式或时区问题容器里显式设TZAsia/Shanghai并在 JVM 参数里加-Duser.timezoneAsia/Shanghai多实例部署时要么只让一个实例开调度要么给任务加分布式锁配合DisallowConcurrentExecution双保险。5.4 任务状态显示成功但下游数据没更新现象运行记录里一片绿业务方反馈表还是空的或者昨天的数据。原因判定条件只看了错误数。Kettle 里「过滤记录」把全部行过滤掉、或者「表输入」查到零行都不算错误错误数依然是 0。另外日志表连接和业务库不是同一个日志写到了另一个库里去。解决判定条件改成「错误数为 0 且关键步骤写行数大于 0」写行数为 0 直接标失败并告警在转换末尾加一个校验步骤把实际写入行数跟期望行数比一下确认日志表连接的库和页面上配的是同一个字符集一致。5.5 跑三五天内存缓慢上涨最后 OOM现象刚重启一切正常跑几天越来越慢最终堆溢出重启后又好了循环往复。原因三个常见泄漏点。一是 TransMeta 被静态缓存后又调setVariable变量会挂在这个实例上后面每次执行都带着上次的值二是KettleLogStore.getAppender()每执行一次加一个 appender从不 remove日志全积在内存里三是Trans结束后没调cleanup()连接和线程没释放。解决缓存的是 .ktr 文件内容或路径每次执行new TransMeta(...)宁可多花几十毫秒解析appender 在用完之后finally里 removecleanup()一律放 finally。做完这三条再加个堆内存监控跑一周看曲线是不是平的平了就说明真修好了。6. 进阶把转换压成模板上线前过一遍自检表到这一步服务能跑了接下来决定它好不好用的是模板化程度。我的习惯是一个业务域只留一份 .ktr 作为模板环境差异全部收敛到五个变量里新需求复制一份改 SQL 就行不再从零画。变量 / 参数含义谁赋值备注ETL_ENVdev / test / prod页面按环境配用来切库、切目录DB_HOSTDB_PORT源库地址页面注入禁止写进步骤DB_USERDB_PWD源库账号配置中心或页面别硬编码在 ktr 里WORK_DIR文件落盘根目录启动注入统一正斜杠BATCH_DATE业务批次日期命名参数每次必传增量条件全靠它模板化的判断标准很简单把这份 .ktr 从测试环境搬到生产需要改的东西是不是只有变量值。如果还要打开 Spoon 改连接、改路径、改 SQL 里的库名那就还没到位。我一般会做一次「空白环境演练」——找一个干净的库只注入变量、不改文件能一次跑通才算合格。上线前这张表我每次都过一遍少一项就要出事检查项判据不通过的典型表现路径是否全变量化全文搜/和\\无硬编码换机器就报找不到文件参数是否齐空白环境只注变量能跑通报参数未赋值失败判定错误数为 0 且写行数 0绿着但没数据重跑幂等同批次跑两次结果一致数据翻倍日志可查按批次能查到起止和行数出问题只能翻 stdout并发上限同时跑不超过执行池大小数据库连接被打满幂等那条最容易被忽略。做法无非两种按批次日期先删后插或者用「插入/更新」步骤按主键合并。前者简单粗暴后者适合大表。千万别用纯「表输出」直接追加重跑一次数据就翻一倍事后清洗的代价远高于当初多配一个步骤。说个真实教训。早些年我接手一套别人留下的调度页面上十几个任务全是绿的业务方却说数据不对。查了两天才发现问题出在判定条件上——只看错误数而那个转换的过滤条件写错了所有行都被过滤掉一步都没报错。从那以后我每次上线新转换都强制走一遍「先跑一次看行数、再故意跑一次看幂等」这两步哪怕再赶时间也不省。数据这东西绿的不一定是对的有数才算数。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?