上一课我们讲了 kettle 里的局部错误处理简单说就是给某个步骤开启错误流把异常行单独捞出来。发出去之后不少朋友留言问webspoon 里面到底怎么把整个任务里的错误都收拢起来而不是每个步骤都去接一根红线。这一课就专门把全局异常/错误捕获这套东西讲透。内容会比较长建议你先收藏再对着操作。这个续篇咱们不重复基础概念直接回答三个问题全局异常抓的是哪些东西、webspoon 里有哪些现成的抓手、怎么搭出一套可复用的全局错误捕获框架。最后把我自己踩过的坑一并列出来方便你直接对照排查。1. 先理清“全局异常”到底要抓哪几类很多朋友一上来就问我“全局异常捕获怎么写”说实话这个问题的前提没理清。全局异常并不是把每个步骤的错误流都接出来就叫全局先把异常分清楚才知道框架该往哪个方向搭。1.1 业务数据错误不致命但要留证这类错误是最常见的比如订单金额字段出现了负数、日期格式不对、客户编号超过了目标表字段长度、主键在目标表里冲突。它们的特点是“行级”错误数据本身有问题但整个转换还能继续跑。我见过不少团队的做法是表输出步骤校验失败后整个作业直接失败调度告警响一晚上。但其实这种错误更应该做的是把坏行剥离出来记录到错误日志表同时让正常数据继续流转。你想想一次同步一百万行里面有两百行脏数据因为这两百行让整批任务回滚重跑时间成本和资源成本都很高。所以对业务数据错误全局框架要做的事情是三个识别、留痕、放行正常数据。1.2 步骤执行异常必须立刻暴露这类错误发生在步骤本身比如 SQL 语句写错、目标表不存在、数据库连接超时、驱动类没加载。它的危害是让整个转换的流程中断后续步骤全部拿不到数据。步骤执行异常不能再“放行”必须立刻暴露。这里有个容易被忽视的点在 webspoon 里步骤执行异常的表现形式和桌面版不完全一样。桌面版本地跑的时候日志窗口会固定弹出错误信息webspoon 里如果你的日志级别设置得不够细或者被浏览器缓存干扰可能看到的就是一个笼统的“失败”。所以全局框架在设计时必须把步骤级的技术异常和行级的数据错误分开对待。前者交给作业控制层处理后者交给数据校验层处理。1.3 作业环境故障属于“看不见的敌人”环境故障包括内存溢出、磁盘空间写满、并发任务抢占数据库连接池、服务器重启导致进程中断。这类错误最麻烦因为它的报错信息往往不体现在具体的某一条数据上而是直接让整个运行环境崩溃。我遇到过一次典型的案例webspoon 跑一个多小时的增量同步跑到最后二十分钟时内存溢出整个流程死掉重新打开后台一看日志全丢了。这时候才发现错误日志没有持久化实时日志又因为服务重启清空了环境故障到底出在哪个环节根本无从查起。这让我后来定下一个原则凡是要上生产调度的 ETL 任务错误信息必须落库不能依赖浏览器里那点实时输出。这是全局异常捕获框架能不能扛住环境故障的关键。2. webspoon 里的捕获机制该怎么选搞明白要抓哪些异常之后再来看 webspoon 里能用的机制。Kettle 本身提供了不少捕获能力但 webspoon 环境下有些侧重和桌面版不一样得先盘一盘。2.1 桌面版和 web 版存在哪些差异先说结论webspoon 的核心引擎和桌面版 PDI 是同一套不能说能力有缺失但交互方式和使用习惯确实不同。桌面版跑转换时日志输出到本地控制台你可以随便滚动翻看还能双击错误日志直接定位到具体步骤。webspoon 的日志在浏览器页面上展示默认情况下刷新页面之后运行记录就没了想回看历史日志只能靠服务端落盘或者日志表持久化。这一点对错误捕获的选型影响很大。另外webspoon 右键菜单依赖浏览器兼容性。我建议用 Chrome 或 Edge 操作遇到某些国产浏览器右键菜单偶尔会失灵连“错误处理”那个选项都弹不出来。真碰上了别以为是功能缺失换个浏览器试试就好。还有一个实践上的差异webspoon 的 lib 目录和桌面版的 lib 目录不是同一个地方。桌面版装个驱动直接丢进 lib重启客户端完事webspoon 需要把驱动放对服务端目录比如 UCanAccess 这类 Access 驱动、一些国产数据库驱动放错了位置加载不到报错信息又不会提示“驱动没找到”而是显示连接超时很容易绕晕。2.2 日志通道、数据库日志表、错误流三板斧Kettle 的捕获机制说到底就三个抓手理解了它们整个全局框架的逻辑就清晰了。第一个是日志通道英文叫 Log Channel。Kettle 里每次运行转换或作业都会生成一个唯一的 channel id日志会按照这个通道归类。你可以把它理解为快递单号所有跟这次运行相关的日志都能通过这个单号查出来。在 webspoon 的运行结果面板里展开每个步骤就能看到对应的通道 ID。第二个是数据库日志表。作业或转换可以配置一个“日志表”标签页把运行日志写入数据库。这一步非常关键它能把运行状态变成可查询的“事实数据”。常用的表包括作业日志表、步骤日志表、日志通道历史表等。配置路径一般是作业属性 → 设置 → 日志表勾选要记录的表然后指定数据库连接。第三个是错误流也就是步骤级 Error Handling。在某一步上右键选择“错误处理”并启用系统会为这个步骤额外生成一个错误出口。这个出口里走的是被判定为失败的行数据你可以把它接到写日志步骤或者接到表输出步骤落库。这三板斧组合起来“行级错误”走错误流“步骤级错误”走日志表“运行环境状态”走日志通道。各司其职不冲突。2.3 我最终选择的组合个人做集成的习惯是分层搭配而不是迷信某一种方案。我自己在 webspoon 里常用的组合如下数据校验层用“字段校验”加“过滤记录”把业务数据错误拦截在流内步骤异常走数据库日志表把执行状态落库作业失败分支接一个专门写错误登记的子作业把环境级异常和步骤级异常汇总成一条统一的错误记录。整体框架并不复杂但每个层次的出口都明确。这套组合唯一的代价是要维护一张自定义的错误日志表。但换来的是所有任务、所有节点的错误都能在一个地方查不用再去翻服务端日志文件。后面第三部分我就按这个思路带你搭一遍。3. 手把手搭一个全局错误捕获框架下面开始实操。假设你已经在 webspoon 里建好了数据库连接和一个测试用的转换我们来搭建框架的核心部分。我以 MySQL 为例其他数据库思路一样。3.1 第一步设计统一的错误日志表先建表。这张表是全局错误捕获的“收口点”一定要设计好后面改起来麻烦。我建议至少包含以下字段。CREATE TABLE etl_error_log ( log_id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 自增主键, exec_id VARCHAR(64) NOT NULL COMMENT 执行批次ID同一次运行保持一致, task_name VARCHAR(200) NOT NULL COMMENT 作业或转换名称, step_name VARCHAR(200) NULL COMMENT 出错步骤名称, error_type VARCHAR(20) NULL COMMENT 错误类型DATA/TECH/ENV, error_code VARCHAR(50) NULL COMMENT 业务错误码如 BUS_001, error_message VARCHAR(1000) NULL COMMENT 错误描述, data_snapshot TEXT NULL COMMENT 原始数据JSON快照, server_name VARCHAR(100) NULL COMMENT 运行节点分布式时定位用, retry_count INT DEFAULT 0 COMMENT 重试次数, error_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 错误发生时间, KEY idx_exec_id (exec_id), KEY idx_error_time (error_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENTETL全局错误日志表;这里重点说几个字段的用意。exec_id非常关键同一次任务运行会可能产生多条错误记录通过它可以把这些记录归拢到同一个批次下排查时直接看这个批次所有错误就够了。data_snapshot是调错神器把出错那一行数据存成 JSON事后分析不需要重新跑一遍流程。server_name在集群部署时尤其有用能在多台节点里快速定位是哪台机器出了事。如果你已经有现成的运维监控体系表里还可以加alert_status这样的字段标记这条错误是否已经触发过告警避免重复告警轰炸。3.2 第二步封装全局错误处理子转换框架不能被业务转换绑死所以我把写日志表的逻辑单独封装成一个子转换取名ETL_ERROR_WRITER。它的输入是错误流上的字段输出就是向etl_error_log表插入一条记录。子转换内部结构很简单表输入不需要直接用“自定义常量”或“获取系统信息”步骤生成exec_id然后接一个“表输出”步骤写入错误日志表。真正要注意的是健壮性这个子转换本身不能出问题否则主流程挂了错误也写不进去框架就白搭了。我在这踩过坑。一开始我用普通表输出直接写结果错误流里的字段长度超过表字段长度子转换自己报错了主流程反而因为错误处理分支失败而卡住。后来我在子转换里做了三件事所有写入字段在进入表输出前统一用“字符串操作”或“字段选择”做长度截断、类型转换强制不超长把连接池配置独立出来不和主流程共用同一个连接子转换外面再加一层作业保护如果连子转换都失败了至少往一个“死信表”里写一条最基础的信息。如果你需要在错误记录里保存整行原始数据我建议用 JavaScript 步骤先拼 JSON 字符串而不是直接用“JSON 输出”步骤因为后者的字段映射在错误流场景里容易产生嵌套层级反而不直观。示例代码如下。// 把原始行拼成JSON快照注意字段名要和前面输入一致 var snapshot {; snapshot \order_id\:\ order_id \,; snapshot \order_amount\:\ order_amount \,; snapshot \customer_id\:\ customer_id \; snapshot };需要提醒的是webspoon 里 JavaScript 步骤用的脚本引擎版本有限部分 ES6 的新语法不一定支持尽量用最简单的字符串拼接别用模板字符串和箭头函数出了问题排查起来很吃力。3.3 第三步接入主作业与日志配置子转换封装好之后接下来把它接入主流程。这里要说清楚“转换级”和“作业级”两个层面。转换级接入在业务转换里对需要监控的步骤启用错误处理建议从“表输入”和“表输出”这两个步骤入手。右键步骤找到“错误处理”启用后给错误流命名比如ERROR_OUT。然后把错误流接到ETL_ERROR_WRITER子转换上。这样只要这一步有数据级错误就会走子转换落库。作业级接入新建一个作业把主转换作为一个作业项拖进去。此时你会发现作业项上有“成功”和“失败”两个出口。失败出口不要闲着接一个“写日志”步骤或者一个小作业负责登记作业级异常。我自己习惯在失败出口再接一个“执行 SQL 脚本”步骤插入一条错误登记记录。INSERT INTO etl_error_log (exec_id, task_name, step_name, error_type, error_code, error_message) VALUES (${exec_id}, ${job_name}, ${step_name}, TECH, ${error_code}, ${error_message});注意这些${}变量能不能正确解析取决于你在作业里有没有定义它们。你可以在作业的开始位置用“设置变量”步骤把当前时间戳生成一个 UUID 之类的值赋给exec_id这样同一批次的错误记录就能关联起来。还有一个容易漏掉的点作业属性里的“日志表”标签页一定要配置。选中作业右键编辑在“日志表”里勾选“作业日志记录”并选择数据库连接。这样即使自定义错误登记没跑成功官方日志表里至少留了一份底不至于全无痕迹。3.4 第四步在 webspoon 里实际跑一次验证框架搭建完成后必须实测验证。我习惯造一条脏数据来测。比如源数据里有一个订单金额为负的记录正常情况下“表输出”会报约束错误。你在 webspoon 中点击运行等任务跑完后不要只看任务是否成功先去数据库里查错误日志表。SELECT exec_id, task_name, step_name, error_type, error_code, error_message, error_time FROM etl_error_log ORDER BY log_id DESC LIMIT 10;如果能看到刚才那条脏数据的错误记录说明转换级捕获生效。接着故意把表输出里目标表名改成一个不存在的表再跑一次看作业失败分支是否成功登记了一条 TECH 类型错误。两条都通过这套全局框架就可以投入使用。这里补充一点 webspoon 的操作细节运行任务时在弹窗里把日志级别调到“基本日志”或者“详细日志”方便观察过程。但正式调度时千万不要用“行级日志”数据量大时浏览器会被日志刷爆我见过有人线上用行级日志跑十分钟直接卡死。4. 三种错误分流写法对比别再只会一种框架搭好了但很多朋友在写具体分流逻辑时容易纠结。我把实战里用的三种写法做了对比你可以按场景选。4.1 步骤级 Error Handling快速、局部这是最直接的写法。在某一步骤右键启用错误处理错误流单独接出去。它的价值在于配置快代码都不用写适用于单个高风险的步骤。但它的缺点也明显每个步骤都要单独配业务一复杂转换里到处都是红箭头整个图看起来像蜘蛛网。后续维护排查时你根本分不清哪根红线对应哪个错误类型。我建议只在表输入、表输出、字段校验这三个最关键的步骤上用其余步骤不要滥用。4.2 校验 条件分流数据质量场景首选如果错误主要是数据质量原因我强烈建议先把异常识别独立出来。做法是在数据流中间加一个“字段校验”步骤把规则集中配置然后用 Switch/Case 或过滤记录把合规数据和异常数据分成两个分支。这种方式最大的好处是规则可视化业务人员也能看懂。你可以在校验步骤里给不同规则配置错误码比如金额非法用BUS_001日期非法用BUS_002这样错误日志表里的error_code就有了业务含义后续做质量报表也方便。缺点是只能捕获你所定义规则以内的错误数据库层面的约束异常、连接异常它管不着所以通常要和作业级分支搭配使用。4.3 作业级失败分支全局兜底上面的写法再多终究管不到“作业启动失败”这类整体性问题。所以每个 ETL 调度任务我都要求在作业层面留一个失败兜底分支只要作业项失败就触发错误登记有条件的话再发一个通知。有些朋友问“通知怎么发”webspoon 里可以用“发送邮件”作业项或者用“HTTP 客户端”调企业微信、钉钉机器人接口。我建议错误登记和通知不要放在同一个作业项里错误登记必须保证执行通知丢了还能事后补查登记丢了就真的什么都找不回来了。选型其实不冲突我自己项目中就是三层都用数据级走校验分流关键步骤走 Error Handling作业失败走既兜底保护。下面这张表供你快速决策。方式配置位置捕获范围优点缺点步骤级 Error Handling单步骤行级数据错误配置快、直接步骤一多图就很乱校验 条件分流转换中间规则内数据错误规则可配置、错误码规范覆盖不到系统异常作业级失败分支作业出口步骤级与环境级全局兜底、可接通知拿不到具体行数据5. 踩坑实录与排查速查表这一部分是我的实战总结每一条都有血泪代价。不要觉得坑小事小生产环境翻车往往就出在这些细节上。5.1 错误日志表一条都没写入框架搭完跑完任务发现错误日志表是空的。先别急按下面这个顺序排查。第一步确认错误流有没有真正接上。很多人建好了错误流入口但表输出步骤自己也有一个连接属性如果错把错误流接到了正常输出上等于什么都写不进日志表。第二步确认字段名是否匹配。错误流里带的字段可能和你子转换里预期的字段不一致字段不匹配时表输出直接报错但日志表不会有记录。第三步确认数据库连接的自动提交设置。某些连接池配置下表输出步骤执行完事务没有提交数据就一直停在缓存里。5.2 全局错误子转换自己挂了这个问题最讽刺错误捕获框架本身成了最不可靠的环节。我遇到的真实场景是错误记录里某个字段值太长导致子转换的表输出步骤报“字段超长”然后子转换也进入错误状态主转换反而被这个错误分支拖死。解决方案前面提过在子转换入口处强制截断字段长度、转换类型错误落库用最简单的 INSERT不做任何复杂逻辑。如果还担心就在作业失败出口再套一个“写日志”步骤把错误信息先打出来至少留个现场。5.3 webspoon 跑大转换内存溢出webspoon 进程在默认配置下内存并不大尤其数据量大或开启行级日志时很容易 OOM。官方社区里推荐的调整方式是在启动脚本里加大 JVM 堆内存。# 以jetty方式运行的webspoon常见调整方式如下 export JETTY_OPTS-Xms512m -Xmx2048m -Dfile.encodingUTF-8改完重启服务。如果你的 webspoon 部署在 Tomcat 下对应调整CATALINA_OPTS。另外如果你不需要实时看日志尽量把运行配置改成“远程执行”让任务在独立的 Carte 引擎上跑减少浏览器和服务端的内存双向压力。5.4 日志中文乱码webspoon 里遇到的乱码多半不是转换流程问题而是编码环境不一致。统一在kettle.properties里设置默认编码KETTLE_DEFAULT_ENCODINGUTF-8数据库连接串上也要注意比如 JDBC URL 追加useUnicodetruecharacterEncodingUTF-8目标表建议直接用 utf8mb4。如果你处理的是 GBK 来源的老系统数据必须在表输入步骤里明确指定输入编码不能只靠全局配置。5.5 常见问题速查表现象可能原因解决方法错误日志表没记录错误流未接入或字段不匹配检查错误流连线逐字段对齐输入输出错误流进入死循环子转换失败又触发分支回来给错误子转换单独设连接池避免回环作业失败但没登记作业日志表未配置在作业“日志表”标签页配置数据库连接webspoon 卡死无响应内存溢出或行级日志调大 JVM 堆生产用基本日志中文乱码编码不统一设置 KETTLE_DEFAULT_ENCODINGURL 加编码参数自定义驱动加载失败驱动放错目录确认驱动放到 webspoon 服务端 lib 目录并重启6. 最后分享一点个人经验我做了这些年 ETL越来越觉得“全局异常捕获”不是某一个步骤能搞定的它更像一个分层的容器数据校验管行级错误日志表管运行痕迹作业分支管全局兜底。三件事缺一不可但也不需要每个步骤都接红线。少即是多把关键节点的错误管住比把整个转换画成一张蜘蛛网有用得多。还有一个小技巧我在每次上线新任务之前都会刻意造两类故障数据做“故障演练”一类是业务脏数据一类是故意写错的 SQL。前者验证错误流能不能正确落库后者验证作业失败分支能不能正确触发。这个习惯帮我拦下了不少线上问题建议你也试试。这一课的内容就是这些了。下一篇我们会继续聊聊基于这套错误日志表做任务运行质量看板的事到时候你会发现全局错误捕获不只是用来“查错”的还能帮你反推数据质量和调度稳定性。