ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Kettle同步数据到Doris实践:从JDBC直写到Stream Load的完整指南

Kettle同步数据到Doris实践:从JDBC直写到Stream Load的完整指南 上个月接了一个数仓需求业务库里十几张表要按天同步到 Doris 里做报表分析。最开始我图省事写了几段 Python 脚本结果字段映射、主键更新、跑批失败重试这些事全混在一起脚本越改越长最后连自己都不敢动了。后来换回 Kettle 那套老本行用 Spoon 把整个流程拖出来逻辑一眼可见调度也顺手。这次踩坑最多的就是 Kettle 写 Doris 这步。Doris 不是常规的 OLTP 库直接拿 Kettle 当 MySQL 写能跑但跑起来之后会暴露出批量写入性能、字段类型、小文件版本合并这些隐藏问题。这篇文章就把我从下载安装 Doris、配 Kettle 环境到多表合并、定时同步、慢查询定位的实操过程完整写一遍重点是每一步为什么这么做以及哪些地方最容易卡住。1. 为什么这个需求会落到 Kettle Doris 上1.1 这套组合解决的是什么问题先对齐一个基本认知。Doris 是分析型数据库列式存储、MPP 架构、支持高并发查询典型用法是承接从业务库同步过来的明细数据给 BI 报表和多维分析用。Kettle 是开源 ETL 工具Spoon 是它的图形化客户端适合做数据抽取、清洗、转换、加载。两者放一起解决的是“业务系统数据定期进入分析库”这条流水线问题。业务系统是 Oracle、MySQL、SQL Server分析库是 Doris中间这层搬运不能靠人工导出再导入了需要一条可重复执行的同步链路。Kettle 在这里扮演的就是搬运工角色但它不是傻搬它能在搬运过程中做字段映射、类型转换、增量过滤、异常记录。这个场景非常常见尤其适合那些已经习惯了 Spoon 的团队。很多人问我为什么不直接用 DataX后面会讲工具本身没有优劣关键是适不适合你手头这套流程。1.2 Doris 的“MySQL 协议”是 Kettle 能直接写的关键Doris 没有自己的专用驱动它对外暴露的查询接口是 MySQL 协议。也就是说凡是能连 MySQL 的客户端理论上都可以连 Doris。Kettle 里新建数据库连接时选 MySQL 类型填上 Doris 地址和端口就能直接干活。这里有个特别容易忽略的细节Doris 的 MySQL 协议端口不是 3306 而是 9030。还有另一个端口 8030 是 HTTP 接口Stream Load 批量导入走的就是这个。如果你在 Kettle 里连接时把端口写成 3306大概率超时。协议兼容不等于 MySQL 全部功能都支持。Doris 没有 MySQL 那套复杂的行级锁和事务隔离级别Kettle 的事务控制对它的意义也不大。但对我们做同步来说能连上、能读写、能批量插入就足够了。1.3 和 DataX、手工写脚本比Kettle 赢在哪里输在哪里先说 DataX。DataX 的 DorisWriter 性能很强配置好就能批量导但它本质是一个独立跑批工具强项是“同步”而不是“处理”。如果你中间要按业务逻辑拆字段、要拿另一张表的维度做关联、要做复杂的正则清洗DataX 的 JSON 配置会越来越难维护。再说手工脚本。Python 脚本做同步也不是不行但脚本本身没有可视化的流程字段变化时要去改代码跑批失败时只能看日志猜。Kettle 最大的好处是流程可视化字段映射、排序、去重、异常行都能在图里看到。后续接新表时复制转换改一下 SQL 就行。Kettle 的短板也很明显JDBC 直写 Doris 的性能上限不高百万行以上的全量同步会明显变慢。这个问题不是 Kettle 本身不行而是 Doris 对大批量导入有更高效的方式。我的做法是分层小表、增量表直接用 JDBC 写大表全量或者重刷场景走 Stream Load。这个结合方案后面专门写。2. 环境搭建Doris 跑起来Kettle 装明白2.1 Windows 上把 Doris 拉起来的最快路径Doris 官方并不原生支持 Windows它的 FE 和 BE 都是面向 Linux 设计的。但很多同事本地电脑是 Windows想先搭一套环境验证一下怎么办我推荐直接用 Docker Desktop 跑 Doris。具体步骤不复杂拉取官方镜像按照 Doris 官方文档 docker/runtime 目录下的 compose 脚本启动 FE 和 BE。启动完成后用 MySQL 客户端连一下127.0.0.1:9030能看到信息就说明环境通了。有一点必须提醒用 Docker 起的 Doris 只是本地试验环境端口映射、数据持久化都做了简化处理。正式环境还是建议放到 Linux 机器上按照官方文档下载二进制包分别部署 FE 和 BE。Windows 上部署 Doris 的热词我也搜过网络上的教程大多是老版本的编译方式非常折腾不推荐浪费时间在源码编译上用容器先跑通业务验证更有意义。2.2 Kettle 版本选择和 JDK 环境Kettle 现在的正式名叫 PDIPentaho Data Integration。下载地址直接搜官网社区版是免费的。建议下载当前最新社区版老版本在驱动兼容上问题多没必要纠结。解压之后 Windows 下运行Spoon.bat。如果双击后没反应先检查 Java 环境。PDI 不同版本对 Java 版本要求不一样9.x 系列常用 JDK 8 或者 JDK 11新版本已经支持 JDK 17。装好之后在命令行执行java -version确认 JAVA_HOME 指向正确再启动 Spoon 就正常了。这里有个小经验很多人喜欢把 Kettle 解压到带中文的路径下比如C:\Users\张三\下载\pdi-ce结果启动时报一些莫名其妙的错。Kettle 对路径中的中文支持不好解压目录尽量用纯英文能省掉很多麻烦。2.3 驱动与连接配置这一步最容易卡住Kettle 连 Doris 需要用到 MySQL 驱动。把mysql-connector-java.jar、mysql-connector-j.jar这类驱动包放到 Kettle 安装目录的lib文件夹下重启 Spoon 生效。新建数据库连接时连接类型选 MySQL然后填这些参数主机名127.0.0.1数据库名称你的 Doris 库名比如demo端口9030用户名root密码Doris 默认 root 没有密码如果有就填设置的密码自定义连接 URL 建议写成jdbc:mysql://127.0.0.1:9030/demo?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/ShanghaiUrl 里必须带上useUnicodetruecharacterEncodingutf8mb4不然中文数据可能乱码。serverTimezoneAsia/Shanghai是解决日期时间字段变成 UTC 时区的问题这个坑后面第 3 章还会具体说。连接测试不通时先按顺序自查端口是不是 9030、Doris FE 是不是启动了、防火墙是不是把端口挡了、驱动版本是不是和 Kettle 匹配。大部分连接问题都出在这四个原因上。3. 第一条链路表输入到表输出Kettle 直写 Doris3.1 从源库读数SQL 别写复杂字段先对齐Kettle 写 Doris 的第一步是从源库读取数据。在 Spoon 左边“核心对象”里找到“输入”分类下的“表输入”拖到画布上配置源库连接和 SQL。这条 SQL 是源库的 SQL比如从 Oracle 里读那就用 Oracle 的语法。我个人的习惯是源 SQL 先不要写太多关联。Kettle 的优势是处理但复杂的多表关联如果在源库本身就能完成就尽量推到源库执行。原因很简单源库是业务库在线事务还在跑把重活留在业务库压力很大。读出来后需要做字段选择。Kettle 新版本里对应的是“字段选择”步骤可以在这里改名、删字段、调整顺序、做类型转换。这一步非常关键因为 Kettle 的字段名和 Doris 的列名很可能对不上后面表输出就要逐个映射很痛苦。建议在做表输出之前先用“字段选择”把要写入的字段都理一遍中文名改成英文数字类型明确是 int 还是 decimal字符串字段确认最大长度。字段对齐了后面的写入就顺畅很多。3.2 表输出的关键设置批量、字段映射、提交时机表输出在“输出”分类下拖到画布后连接在字段选择后面。双击打开选择目标库填 Doris 表名。接着勾上“指定数据库字段”下面会出现一张字段映射表左边是流里的字段右边是目标表的字段。这一步常见问题是默认按名称匹配如果 Doris 表列名和 Kettle 流字段名不一致会有一堆字段匹配不上。我的做法是先手工检查一遍映射关系宁可花两分钟看也不要留着隐患。批量插入大小是另一个关键参数默认是 1000。Doris 这类分析库提交批次太小意味着会不断产生小版本的插入事务数据落盘后 Doris 后台要花大量时间做合并。我实际测试下来 1000 到 2000 是个比较稳妥的范围不要一上来就填 10000Kettle 内存和 Doris 导入压力都受不住。还要注意表输出默认是“追加”写。如果业务上要求重跑当天数据就需要在作业里先做清表或删除分区再执行追加写入而不是指望表输出自己覆盖。3.3 我最常遇到的三个报错类型、编码、提交时机第一类是类型过长。Doris 的 VARCHAR 长度有限制如果源库某个字段是 VARCHAR2(4000)而你 Doris 里建成了 VARCHAR(100)写入时就会报 Data Too Long。处理方式有两种建表时按源库最大长度预留或者统一用 STRING 类型。Doris 的 STRING 类型能存很长的字符串适合这种不确定长度的字段。第二类是时区问题。Kettle 读出的日期时间本来是正常的东八区时间写入 Doris 之后再查变成 UTC。这个一般就是连接 URL 里少了serverTimezoneAsia/Shanghai或者时区写错了还一种可能是源库驱动本身在用 UTC 解析。解决方式是连接 URL 统一加上时区参数然后 Doris 侧查一下show variables like %time_zone%。第三类是提交时机。数据量比较大的时候Kettle 表输出每攒够一批就提交一次任何一个批次失败会导致整个转换失败。看日志时经常只看到最后一条报错真正的问题在前面的某一条脏数据。我的经验是先在表输出前面加一个“数据校验”步骤对必要的字段做非空、长度、格式校验把脏数据拦在外面比事后翻日志效率高太多。4. 数据量上来以后JDBC 就不够看了改走 Stream Load4.1 JDBC 批量写入 Doris 为什么慢Kettle 的表输出本质上是 JDBC 驱动发 insert 语句给 Doris。一次 insert 在 Doris 里就是一个导入任务即使你开了批量提交相对于 Doris 本身的列式存储导入通道来说仍然是一条条行协议处理效率不高。具体表现是几十万行以内还能接受到了几百万行同步耗时成倍上升而且 Doris 后台会出现特别多的小版本文件。查询的时候 Doris 需要把文件合并起来于是慢查询就来了业务可能反过来投诉你同步影响了查询。这里要理解一个核心概念Doris 的写入不是简单的 insert它真正高效的是导入机制。Stream Load、Broker Load 这些导入方式走的是 HTTP 协议加批量文件数据直接面向存储层效率高得多也方便通过 label 做幂等。4.2 Stream Load 的导入思路Stream Load 是 Doris 提供的一种同步导入方式通过 HTTP 提交一个本地数据文件给 DorisDoris 解析后直接写入对应表。数据格式通常是 CSV 或者 JSON。一个最朴素的 curl 导入命令长这样curl --location-trusted -u root: \ -H label:orders_20240604_001 \ -H column_separator:, \ -H format:csv \ -T /tmp/orders.csv \ http://127.0.0.1:8030/api/demo/orders/_stream_load这里面几个点要解释清楚。label 是导入作业的唯一标识同一个 label 重复提交是幂等的失败重试不会产生重复数据。column_separator 用来指定列分隔符CSV 默认是逗号。端口 8030 是 FE 的 HTTP 端口也可以直接用 BE 的端口。URL 里最后面_stream_load是固定的路径节点。Stream Load 返回的是一段 JSON里面有 Status 字段Success表示成功Fail表示失败。失败时还会给出 ErrorURL这个 URL 里能找到失败的具体行信息是我们排查脏数据的重要依据。4.3 在 Kettle 里复用 curl 调 Stream Load 的完整做法Kettle 本身没有内置的 Stream Load 步骤所以我们用“组合拳”实现一个转换负责把数据导出成 CSV 文件另一个转换里的“执行 Shell”步骤负责调 curl 上传。第一个转换的链路是表输入 → 字段选择 → 文本文件输出。文本文件输出时要特别注意几个设置分隔符选逗号、编码选 UTF-8、文件路径要固定好、扩展名写 csv。另外一定要勾上“不写头部”不然第一行会被当成表头写进 Doris直接造成脏数据。第二个转换就简单了画布上放一个“执行 Shell”步骤在命令里调用上面那条 curl。这里有个必须注意的点Shell 步骤不是等前面数据全部生成完再执行的它是随流里的每一行数据都执行一次。如果你把两个转换连在同一个流程里会重复执行很多次 curl这是新手最容易踩的坑。正确的做法是在一个 Job 里做编排先执行“导出 CSV”转换再执行“调用 curl”转换。Job 是串行执行作业项的前一个成功后才执行下一个。Shell 步骤里的命令只写一次比如curl --location-trusted -u root: -H label:orders_${DATE} -H column_separator:, -H format:csv -T /data/orders.csv http://127.0.0.1:8030/api/demo/orders/_stream_loadWindows 下的路径和 curl 命令要按实际情况调整Win10 自带了 curl.exe不用额外装。4.4 两种方式的取舍我用一个表格把两套方案的差异放出来方便你选型对比项JDBC 直写表输出Stream LoadCSV curl适用数据量百万行以内、增量小批百万行以上、全量重刷性能一般大批量明显变慢高效面向列式存储导入配置复杂度低一行连接配置搞定中需要维护CSV文件和脚本错误排查错误直接显示在Kettle日志需看返回JSON和ErrorURL幂等控制弱重跑可能产生重复强label可控制幂等适合人群刚上手Kettle、表不大的场景对Doris导入机制熟悉、数据量大的团队我的建议是日常增量同步数据量不大用 JDBC 就行首次全量同步、补数、重刷维表优先安排 Stream Load。两条链路可以放在同一个 Job 里根据参数判断走哪个分支前期麻烦一点后面一劳永逸。5. 多表合并、定时同步和时间参数一次说清楚5.1 时间参数到底在哪设置“Kettle 转换里的时间参数在哪里”这个问题经常有人在群里问。答案在转换属性里。打开转换菜单栏找到“转换”-“设置”会看到“参数”页签。在这里添加参数名和默认值比如refresh_date2024-06-04。然后在表输入的 SQL 文本里引用${refresh_date}比如select * from orders where biz_date ${refresh_date}这里有一个非常容易忽略的关键选项表输入步骤里有一个“替换 SQL 中的变量”的勾选项必须勾上参数才能被解析。如果不勾Kettle 会把${refresh_date}当成普通字符串发给数据库然后告诉你语法错误。还有另一种方式是直接在 SQL 里用?占位符配表输入下面的“插入数据步骤”传值。这种方式适合单表简单过滤但可读性不如参数变量我还是推荐参数方案。5.2 多张表合并抽到一张表热搜里有“多表合并抽到一个表”这个需求在数仓里太常见了。比如三个库三张月表结构一样要合并到 Doris 的一张历史表里。如果表结构完全一致最简单的方式是在表输入的 SQL 里直接写 UNION ALLselect id, name, amount, biz_date from db1.orders union all select id, name, amount, biz_date from db2.orders union all select id, name, amount, biz_date from db3.ordersKettle 会把结果集当多行流出后面的表输出只需要一份配置效率很高。如果表结构不一样就需要分别读出来然后通过“追加流”步骤把多路数据拼在一起。注意“追加流”是顺序追加不会去重。如果业务上要求去重要在这之前用“排序记录”加“去除重复记录”或者目标表用 Unique 模型让 Doris 在合并时按主键去重。我的建议是合并逻辑尽量在 SQL 里完成。SQL 能表达的合并比在 Kettle 里拖步骤更直观也更容易调优。5.3 定时同步Spoon 里的 Job 和正式调度的 KitchenSpoon 里可以创建 Job然后把转换拖进 Job。Job 里有一个“开始”步骤双击可以设置定时执行比如每天早上 6 点跑一次。在开发调试阶段这样用没问题但生产环境我不建议直接在 Spoon 里挂着界面等定时。正规做法是用 Kitchen 命令行工具调度。Kitchen 是 Kettle 自带的批处理命令Windows 下是Kitchen.batLinux 下是kitchen.sh。把它和系统定时任务结合就能实现真正可靠的无人值守跑批。Windows 任务计划程序为例新建一个计划任务执行命令填写C:\pdi\data-integration\Kitchen.bat -fileC:\jobs\order_sync.kjb -param:refresh_date2024-06-04Linux 的 cron 写法类似0 6 * * * /opt/pdi/data-integration/kitchen.sh -file/opt/jobs/order_sync.kjb -param:refresh_date$(date \%F) /var/log/kitchen/order_sync.log 21Kitchen 相比 Spoon 的好处是不依赖图形界面、启动后后会自动运行、出错时通过退出码反馈调用方方便外部系统做告警。日志里会有每一次执行的起止时间和处理行数我在团队内部要求所有定时任务都必须用 Kitchen 起Spoon 只负责开发调试。6. 数据进 Doris 之后的功课合并、慢查询和类型坑6.1 关于“Doris 手动触发对表的合并”很多做同步的人写过“Doris 手动触发合并”的关键词。我的结论先说在前面Doris 的 Compaction 是后台自动完成的官方并没有像 Hive 那样面向用户提供一条MANUAL COMPACT命令。你不需要、也不应该手动去触发它。为什么会有手动合并这种需求因为频繁的小批量导入会产生大量小版本导致查询变慢。这时候真正该做的是减少导入频率、增大每次导入的数据量、尽量用 Stream Load 合并提交Doris 后台的合并线程才能跟得上。网上有些文章提到的 Be 内部 HTTP 接口可以用来调试 compaction这属于内部调试手段而不是公共能力生产环境依赖它有风险。正确的思路是把功夫下在写入端批量大一点、批次少一点、别把 Doris 当成 MySQL 那种小事务穿插写入的库。写入健康合并自然就健康。6.2 写入方导致的慢查询怎么在 Doris 侧定位如果发现同步任务跑完之后Doris 上的查询变慢了先不要急着调 Doris 参数先看是不是写入方式不当导致的。Doris 的查询日志里能找到执行计划配合explain可以看到单表扫描行数、谓词下推情况、join 方式这些关键信息。同步任务刚结束时如果写入产生的小版本太多查询需要在合并后的版本上做扫描时间自然长。这种慢查询通常会在后台合并完成后逐渐缓解。另一个慢查询源头是 SQL 本身写得太烂。Kettle 表输入如果是把 Doris 表整个 select 出来再在本地处理那再怎么调 Doris 都没用。正确做法是把过滤和聚合尽量下推到 Doris 执行让 Doris 只回传精炼结果给 Kettle。Doris 常见调优手段包括合理选择分桶键、利用分区裁剪、对高基数字段建 Bitmap 索引、避免大范围扫描等。但这些都是后话第一步先把写入习惯改好慢查询至少少一半。6.3 Variant 字段、大小写和其他容易漏的细节热搜里还有一个“doris variant java”。Variant 是 Doris 支持 JSON 风格半结构化数据的字段类型Java 应用可以通过 JDBC 写入 JSON 字符串Doris 内部会自动解析。如果你用 Kettle 往 Variant 列写数据要格外注意Kettle 里 Variant 字段本质上就是个字符串你写入的必须是合法 JSON比如{name:test,age:18}不能是普通文本。否则 Doris 解析报错而且报错信息比较隐晦。稳妥的做法是在 Kettle 里用字符串拼接好 JSON 内容再做一次 JSON 格式校验确认没问题再写入。另外几个容易踩的细节我写在下面问题现象应对列名大小写Kettle 写入时报 Unknown columnDoris 列名默认转小写建表时用反引号明确指定源库 number 类型写入时变成 double精度丢失建表时按精度建 DECIMAL并在字段选择里显式转换中文字符乱码查询结果乱码连接 URL 加 characterEncodingutf8mb4Stream Load 重复导入出现重复行每次导入用唯一 label失败重试时复用该 label主键模型更新数据没按预期覆盖确认表是 Unique 模型且 Doris 版本支持主键模型自动覆盖这些细节单拎出来都不难但如果在链路搭建阶段没注意后期排查会非常耗时。我在实际项目中吃过列名大小写的亏花了半天时间才发现是 Doris 把大写列名自动转成了小写Kettle 一直按大写映射找不到列。7. 最后一点我自己总结的落地经验写到最后分享几条从项目里趟出来的经验。第一不要一上来就追求高效导入。先用表输出把链路跑通确认字段、类型、编码、增量逻辑都没问题再考虑切 Stream Load。链路没通就研究性能优化很容易被一堆问题绕进去。第二Job 里一定要记录处理行数。Kettle 的转换本身有统计功能但在 Job 的日志里不显眼。我会在转换末尾加一个“写日志”步骤把行数、时间、耗时打出来调度系统收集日志后可以判断这次任务完成了多少数据异常时能快速定位是读的问题还是写的问题。第三Doris 的日志是最后的防线。Kettle 报错信息有时候不够具体真正的原因在 FE 的fe.log和 BE 的be.out里。遇到链路上层排查不出来的写入问题直接去服务器看日志比盯着 Spoon 报错猜高效得多。第四Windows 上用 Docker 跑 Doris 做验证没问题但生产环境老老实实用 Linux别为了省事把业务架在容器洞开的本地环境上。端口、内存、磁盘这些宿主机资源一样都得提前规划好。这套 Kettle 写 Doris 的操作说难不难说简单也不简单。理解了 Doris 的导入机制和 Kettle 的执行原理之后剩下的事情就是不断积攒各种边界情况的处理经验。希望这篇文章能帮你少走一些弯路。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进