简介:在数据同步场景中,原生DataX并不支持瀚高数据库,这份插件包正是为破解该痛点而设计。它面向使用DataX进行数据集成、需要将瀚高数据库作为同步目标的开发人员,提供了开箱即用的writer插件实现,能够弥补官方生态缺口,适用于各类ETL任务中的瀚高数据入库环节。插件包共17个文件,包含15个jar包和2个json文件,整体大小8.15MB。jar包中既有插件主程序及运行依赖,也有数据库驱动与通用工具组件,json文件则提供插件参数配置模板和作业模板,方便使用者快速理解调用方式。资源已有625人学习下载,虽体积小巧但结构清晰,可帮助开发者免去从零编写插件的重复劳动,直接部署到DataX插件目录即可使用,同时便于参照模板进行参数调优,显著提升瀚高数据库同步任务的搭建效率。
1. 从传统数仓到国产数据库:highgowriter 插件怎么成了刚需
因为项目合规要求,我最近接手了一个把业务系统从 Oracle 迁移到国产数据库 HGDB(基于 PostgreSQL 内核)的活。本来以为用 DataX 默认的 postgresqlwriter 改改连接串就行,结果一跑就报错——要么驱动不认,要么方言不兼容,几千万行数据卡死在那。后来找到了这个专门适配 HGDB 的 datax 插件 highgowriter.zip,算是“救火队员”。这篇文章我拆给你看,它到底解决了什么问题,哪些地方有坑,以及我是怎么把它接到生产同步任务里的。适合正在做数据库国产化替换、或者需要在 DataX 里对接 HGDB 的同步开发同学。
2. 插件包解构:安装部署与初始化逻辑
2.1 直面目录结构:下载后第一步不是解压,是看说明书
先找个干净目录把 zip 解压。我是放在 DataX 的安装根目录下的plugin/writer/highgowriter/里。解压完先别急着写任务,先看一遍目录和插件描述文件。
unzip datax-plugin-瀚高数据库支持插件-highgowriter.zip -d /data/datax/plugin/writer/highgowriter/ ls -la /data/datax/plugin/writer/highgowriter/ cat /data/datax/plugin/writer/highgowriter/plugin.json解压完一定要先看plugin.json,这个文件里的"name"和"class"是 DataX 框架加载插件的唯一凭据。如果名字对不上,后面跑任务时会报“writer 插件找不到”的错。这一点很像 Spring 里的@Service注解,名称必须唯一且与配置里的"name": "highgowriter"完全一致。目录结构里通常还有一个libs文件夹,里面是该插件依赖的 JDBC 驱动和其他 jar 包。这里有个坑:如果 DataX 安装目录下已经存在同名但不同版本的驱动 jar,可能会因为类加载顺序问题导致冲突,后边我会讲怎么排查。
2.2 底层原理:它和 postgresqlwriter 到底差在哪
通常我们以为 HGDB 兼容 PG,直接用 PG 插件就行。但真实情况是,HGDB 的驱动类名、连接协议、部分方言是有差异的。这个插件的价值在于,它把 JDBC 驱动替换成了 HGDB 官方驱动,并在Connection初始化时做了特殊处理,避免了把 HGDB 当成标准 PG 来连时的各种诡异报错。
我翻了一下它的源码结构,核心类主要是继承自 DataX 的BaseWriter,然后重写了init()和writeToDatabase()方法。下面是关键逻辑的伪代码:
// 关键伪代码:初始化连接池 public void init() { // 加载 HGDB 专用的 JDBC 驱动,注意类名和 PG 驱动不一样 Class.forName("com.hgdb.Driver"); // 通过连接池获取连接,设置连接参数 dataSource = new DruidDataSource(); dataSource.setUrl(jdbcUrl); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setInitialSize(1); dataSource.setMaxActive(2); }逻辑不复杂,但边界细节多。比如 HGDB 的驱动 jar 版本必须足够新,否则不支持rewriteBatchedInserts参数。用 PG 的驱动连 HGDB,大概率会在批量插入时抛PSQLException: ERROR: invalid byte sequence for encoding之类的编码错误。所以如果你在同步过程中遇到这类报错,第一反应不应该是去改数据库编码,而应该检查是不是驱动用错了。
2.3 参数体检:安装完先改这三个地方
很多人装完插件直接就跑,结果报错后一脸懵。我习惯是先做一次“参数体检”,重点检查三个地方:
jdbcUrl中的 IP 和端口要改成 HGDB 实际的地址。这里要特别注意,如果 HGDB 部署在容器里,外边通过映射端口访问,地址看起来通但实际握手的还是容器内网 IP,很容易超时。libs目录下的驱动版本如果太老,建议换成 HGDB 官方最新的兼容版本。判断标准是日志里有没有出现UnsupportedFeatureException。- 确认写权限。HGDB 对表空间和用户权限卡得很严,没有
INSERT或DELETE权限会直接报错。这种错误在日志里通常会被误认为是连接问题,其实只要用数据库客户端手动执行一条insert就能定位。
3. 手把手配置一个同步任务:JSON 细节决定成败
3.1 先跑通一个最小化配置
这是一个最简的 writer 配置块,配合任意的 reader(比如 oracle reader)就能跑通。我的习惯是第一次跑通链路时把 channel 设为 1,避免因为并发问题干扰判断。
{ "job": { "setting": { "speed": { "channel": 1 } }, "content": [ { "reader": { "name": "oraclereader", "parameter": { "username": "scott", "password": "tiger", "column": ["empno", "ename", "sal"], "connection": [ { "jdbcUrl": ["jdbc:oracle:thin:@//192.168.1.10:1521/ORCL"], "table": ["emp"] } ] } }, "writer": { "name": "highgowriter", "parameter": { "username": "hgdb_user", "password": "hgdb_pwd", "column": ["empno", "ename", "sal"], "preSql": ["delete from emp_target"], "batchSize": 2048, "connection": [ { "jdbcUrl": "jdbc:highgo://192.168.1.20:5866/hgdb", "table": ["emp_target"] } ] } } } ] } }我先说一下这个preSql。很多新手会把truncate table写在这里,但如果目标表是维表且有外键引用,truncate会锁表甚至报错。我这里用delete是为了保证可重入。channel设为 1 是为了先验证链路通不通,这时候不要管性能,能跑完 10 万行就说明连接、驱动、字段映射都没问题。如果这里报了网络错误,先别急着调批量参数,那不是性能问题,是根本连不上。
3.2 批量提交与事务边界:避免丢数据的必经之路
跑通之后,再考虑速度和事务。batchSize是 writer 每次提交给数据库的行数。这个值太小时,网络往返次数多,性能差;太大时,如果中途失败,回滚的代价也大。实践下来,2000-5000 之间是比较稳的区间。但要注意,这个参数不是越大越好,我试过设置成 10000,结果 HGDB 端因为 WAL 日志写入瓶颈导致整体卡顿。
HGDB 的 JDBC 驱动对批处理的支持和 PG 类似。如果不开启rewriteBatchedInserts,驱动会一条条地发 SQL,所谓的批处理形同虚设,速度可能只有几千行每秒。在jdbcUrl后面加上?rewriteBatchedInserts=true能极大提升性能。注意,useServerPrepStmts=false是为了避免和 HGDB 某些版本的 prepared statement 缓存冲突。这个参数组合我测过很多次,在 HGDB 上表现最稳定。
3.3 字段映射与类型转换:这里最容易翻车
HGDB 兼容 Oracle 特性,但底层毕竟是 PG,数据类型映射不是 100% 一致。下面是几个我踩过的大坑:
- NUMBER 类型:Oracle 的
NUMBER(10)映射到 HGDB 建议用NUMERIC(10)或INT。如果你 source 是NUMBER,writer 这边建表用了VARCHAR,DataX 会把数字转成字符串,查询时索引就失效了,后续报表查询会特别慢。 - DATE 类型:Oracle 的
DATE包含时分秒,PG 的timestamp也包含,但如果目标列是date(只精确到天),同步后时分秒会被截断。对比数据时只有用TO_CHAR才能发现差异,这种差异很隐蔽。 - 空字符串 vs NULL:Oracle 查出来是空字符串的字段,到了 PG/HGDB 里会变成
NULL。如果目标表又建了NOT NULL约束,任务直接中断。解法是先在 reader 端用NVL(col, '')做一层兜底,别指望 DataX 帮你自动转换。
4. 避坑排查:实战中靠这几条“血泪经验”续命
4.1 现象:连接超时或连接被拒绝
日志里报Caused by: java.net.SocketTimeoutException: Read timed out。原因:DataX 所在服务器到 HGDB 防火墙策略限制,或者 HGDB 的max_connections被占满了。之前某现场做安全加固,防火墙只允许应用端口通过,结果 DataX 的随机端口全被拦了。
解决:先telnet 192.168.1.20 5866测连通性,不通就找网络管理员开策略;然后把 HGDB 的max_connections调大,默认 100 肯定不够,我一般设到 300-500;最后检查驱动连接串是否显式设置了connectTimeout=0和socketTimeout=0,不让数据库那边因为空闲被中间设备断开。
4.2 现象:主键冲突导致任务中断
日志里报ERROR: duplicate key value violates unique constraint "xxx_pkey"。原因:源表数据本身存在重复,或者上一次同步由于异常中断,没有做清理,重跑时和旧数据撞了。
解决:如果源表数据不规范,在 reader 的 SQL 里去重,比如加row_number()窗口函数取最新一条;如果是任务重跑导致,preSql必须写成delete from table而不是truncate。我遇到过用truncate把自增序列重置了,结果后续并发插入时主键撞车,反而引发更麻烦的问题。
4.3 现象:任务卡在 100% 然后内存溢出
日志里报java.lang.OutOfMemoryError: Java heap space。原因:DataX 默认 JVM 堆内存只有 1G,channel 开到 8 或 16 时,内存里积压的Record太多,GC 也回天乏术。
解决:修改bin/datax.py里的JVM_PARAM,我一般会设成-Xms2g -Xmx2g -XX:+UseG1GC。如果数据超过几亿行,建议在 reader 端加where条件拆分成多个任务,别指望单个任务打天下,否则你会在监控面板上看到频繁的 Full GC,把磁盘 IO 都耗在写日志上。
4.4 现象:同步速度比 Oracle 还慢
日志里没报错,但速度只有 1-2 万行/秒。原因:多半是没开启批量重写,或者目标表上有多个索引和触发器。HGDB 在插入时维护索引的成本很高,尤其是如果有多个二级索引,每次插入都要更新 B+ 树。
解决:严格检查连接串是否带了reWriteBatchedInserts=true参数;迁移前和 DBA 沟通,先降索引或改成异步更新。有一次我把目标表上的三个索引全部先 drop 掉,同步完成后再重建,整体耗时反而缩短了 40%。对于大表迁移,这种做法相当实用。如果你有触发器在做审计记录,插入性能会进一步恶化,建议先在会话级关闭。
5. 压榨性能:把同步效率从 2 万提到 20 万行/秒
5.1 参数组合拳:JVM 内存与 channel 数
性能调优不是单点问题。我把channel数从 1 加到 4,发现并行度上去了,但 HGDB 的 CPU 没跑满,瓶颈反而在 JVM GC。这说明内存和并发必须匹配。给大家一个参考配置:
export JAVA_HOME=/path/to/jdk export DATAX_HOME=/data/datax python3 $DATAX_HOME/bin/datax.py -j "-Xms4g -Xmx4g" /path/to/job.json这里的-j参数用于覆盖默认的 JVM 设置。当 channel 数增加时,堆内存也要跟上,否则你会在日志里看到频繁的 Full GC。我一般是 channel 每增加 1,堆内存就增加 512M,这个比例比较稳。
5.2 杀手锏:开启池化预处理与批量重写
reWriteBatchedInserts参数在 HGDB 上效果明显。它的原理是把INSERT INTO t VALUES (1),(2),(3)这种多条合并成一条 SQL,减少 SQL 解析次数。另外,如果你的连接串没有开启useServerPrepStmts=false,HGDB 驱动可能会在每次插入时都做一次预编译,导致性能退化。推荐的连接串参数如下:
jdbc:highgo://host:5866/dbname?reWriteBatchedInserts=true&useServerPrepStmts=false&allowMultiQueries=true注意,allowMultiQueries=true增加了 SQL 注入风险,只建议在内网同步工具上使用。如果你们安全扫描比较严格,这个参数可能会被标记为高危,需要走审批流。但不开它,大批量场景下速度会差 10 倍以上。在这个参数上我吃过亏,一开始怕麻烦没开,被业务方催着要数据,后来开了之后秒变“真香”。
5.3 大表拆分的多任务并行策略
当单表超过 5000 万行时,即便单任务调到最优,也很难再快了。由于 DataX 的 channel 是在 JVM 内通过线程模拟并行,碰上 CPU 密集型转换逻辑时会互相争抢资源,瓶颈很明显。我的做法是,在主键 ID 上按mod(id, 8)拆成 8 个分片,分别提交给 DataX。
# 分片示例 SQL(在 reader 中配置) SELECT * FROM source_table WHERE mod(id, 8) = 0; SELECT * FROM source_table WHERE mod(id, 8) = 1; # ... 以此类推这样每个任务只跑 625 万行,多个任务同时写入时,对 HGDB 表的锁竞争会减轻不少。还需要注意,如果源表是 Oracle,分片字段最好用rowid或pk字段,否则全表扫描的代价太大。另外,每开一个 DataX 进程,它都会新建一个 JVM,8 个任务就是 8 个 JVM,物理机内存如果只有 8G,建议分批跑,否则 OOM 直接把机器搞挂。
6. 数据一致性校验:上线前千万别跳过这一步
6.1 行数级校验与断点续跑
同步完先别急着交接,我一般用count比对来兜底。虽然 Count 在大数据量下会慢,但准确性高,是判断同步是否完整最直接的证据。
# 在源 Oracle 执行 select count(1) from source_table; # 在目标 HGDB 执行 select count(1) from target_table;如果两端行数对不上,先查丢数据还是多数据。DataX 没有强事务保证,如果同步过程被 kill -9,必然出现部分提交。我的“后悔药”是在任务执行前记录一个watermark时间戳,重跑时只捞这个时间戳之后的数据,这样可以有效避免全量重跑带来的时间和资源浪费。
6.2 业务字段去重与抽样核对
Count 一致并不能证明数据没同步错。某次项目我碰到两边行数都是 100 万,但实际有 3 万行主键冲突被数据库“静默忽略”(因为目标表没建唯一约束),对账时才发现数据对不上。现在我会多做一个步骤:用group by去重核对关键业务字段。
-- 在 HGDB 中执行,查看可疑重复 SELECT key_col, COUNT(1) FROM target_table GROUP BY key_col HAVING COUNT(1) > 1;最后做一次抽样checksum:用MD5或DBMS_SQLHASH对比源端和目标端的某行数据。这个方法能发现很多隐性不一致,比如字符集转换异常产生的乱码,或者数字精度丢失的问题。从那以后,我每次上线同步任务,都会强制把“行数比对 -> 去重检查 -> 抽样 checksum”走完一遍。虽然麻烦点,但总比半夜被叫起来处理数据问题强。希望我踩过的这些坑,能帮你在做 HGDB 数据同步时少走弯路。
本文还有配套的精品资源,点击获取