4、数据交换实战:基于DataX实现MySQL到Hive的全量/增量同步
数据交换这件事,说白了就是让数据在不同系统之间「搬家」。我做了这么多年数据架构,见过太多团队在搬家过程中翻车——要么丢数据,要么跑不动,要么同步完才发现对不上。
今天咱们聊的,是数据交换里最经典的一个场景:从MySQL同步到Hive。MySQL存业务数据,Hive做离线分析,这几乎是每家公司都会遇到的组合。工具我选DataX,阿里开源的,稳定、灵活、社区活跃。我个人习惯用它来处理这类结构化数据的批量同步。
核心要点:全量同步解决「历史数据一次性搬迁」,增量同步解决「新数据持续追加」。两者结合,才能构建一个完整的数据同步链路。
4.1 场景分析:为什么是MySQL到Hive?
先说说这个场景的典型特征。MySQL是OLTP系统,存的是订单、用户、商品这类业务数据。Hive是OLAP系统,跑的是离线报表、数据挖掘。两者的数据模型、存储引擎、查询方式完全不同。
我在项目中遇到过这样一个情况:业务方每天要跑一份全量报表,但MySQL里几千万条数据,直接查会把数据库拖死。解决方案就是——每天凌晨把MySQL数据同步到Hive,然后在Hive上跑分析。这样既不影响线上业务,又能拿到完整数据。
| 对比维度 | MySQL | Hive |
|---|---|---|
| 存储引擎 | InnoDB/MyISAM | HDFS + MapReduce/Tez |
| 数据量级 | GB ~ TB | TB ~ PB |
| 查询延迟 | 毫秒级 | 分钟级 |
| 适用场景 | 在线事务处理 | 离线批量分析 |
| 同步方式 | 源端 | 目标端 |
你想想看,这两个系统天然就不适合直连查询。所以数据交换就成了必经之路。DataX在这条路上,扮演的就是「搬运工」的角色。
4.2 DataX核心原理速览
DataX的架构其实不复杂。它把数据同步抽象成三个角色:Reader、Channel、Writer。Reader从源端读数据,Writer往目标端写数据,Channel在中间做缓冲和传输。
嗯,这里要注意:DataX不是简单的「读一条写一条」。它内部有并发控制、断点续传、脏数据管理这些机制。我刚开始用的时候,以为它就是封装了个JDBC连接池,后来看了源码才发现,它的Channel设计挺讲究的——支持限流、支持内存控制、支持数据转换。
小提示:DataX的Channel数量直接影响同步速度。我个人习惯先设成CPU核数的2倍,然后根据实际跑出来的吞吐量再调。别一上来就设100个,容易把源库打挂。
下面这张图,是我画的一个简化版DataX同步流程,你可以直观感受一下:
4.3 全量同步实战
全量同步,就是一次性把MySQL里的所有数据搬到Hive。听起来简单,但坑不少。我见过有人直接SELECT * FROM table然后往HDFS写文件,结果源库内存直接爆了。
正确的做法是:分页读取 + 批量写入。DataX天然支持这个机制,你只需要配好JSON就行。
4.3.1 全量同步配置示例
下面这个配置,是我在项目里用过的简化版。它从MySQL的orders表读取数据,写入Hive的ods_orders表。
{
"job": {
"content": [
{
"reader": {
"name": "mysqlreader",
"parameter": {
"username": "root",
"password": "your_password",
"connection": [
{
"jdbcUrl": ["jdbc:mysql://localhost:3306/biz_db"],
"table": ["orders"]
}
],
"splitPk": "id",
"where": "create_time >= '2024-01-01'"
}
},
"writer": {
"name": "hdfswriter",
"parameter": {
"defaultFS": "hdfs://namenode:8020",
"fileType": "text",
"path": "/user/hive/warehouse/ods_orders",
"fileName": "orders",
"writeMode": "overwrite",
"column": [
{"name": "id", "type": "bigint"},
{"name": "order_no", "type": "string"},
{"name": "amount", "type": "double"},
{"name": "create_time", "type": "string"}
]
}
}
}
],
"setting": {
"speed": {
"channel": 4
}
}
}
}
注意:splitPk这个参数很关键。它告诉DataX用哪个字段做分片,实现并行读取。如果不设,DataX就只能单线程读,大表会慢得让你怀疑人生。我曾经踩过这个坑,一张2亿条的表跑了6个小时,加上splitPk后缩到40分钟。
4.3.2 全量同步的执行与验证
配置写好后,执行就一行命令:
python datax.py job/orders_full.json
跑完之后,我建议你做三件事验证:
- 行数对比:MySQL里
SELECT COUNT(*),Hive里也跑一下,看是否一致。 - 抽样对比:随机抽几条记录,逐字段比对,特别是时间、金额这种容易出问题的字段。
- 边界检查:看看NULL值、空字符串、特殊字符是否被正确处理。
我记得有一次,全量同步跑完行数对上了,但业务方反馈数据不对。查了半天,发现是MySQL里有个字段存的是JSON类型,Hive那边当字符串存了,但引号被转义了。从那以后,我每次都会加一个「字段类型映射检查」的步骤。
4.4 增量同步实战
全量同步搞定后,接下来就是增量同步。增量同步的核心是:只同步新增或变更的数据,而不是每次都全量搬一遍。
增量同步的方案,我总结下来主要有三种:
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 时间戳增量 | 根据update_time字段筛选 |
实现简单,对源库影响小 | 依赖业务字段,删除数据无法感知 |
| Binlog监听 | 解析MySQL的binlog日志 | 实时性高,能捕获所有变更 | 需要开启binlog,运维成本高 |
| 全量对比 | 全量拉取后在Hive侧做diff | 数据最准确 | 资源消耗大,不适合大表 |
我个人最常用的是时间戳增量。原因很简单——大部分业务表都有create_time和update_time字段,而且DataX天然支持where条件过滤。你只需要记录上次同步的时间点,然后每次只拉取这个时间点之后的数据。
4.4.1 增量同步配置示例
增量同步的配置和全量很像,区别在于:
writeMode从overwrite改成appendwhere条件里加上时间过滤
{
"job": {
"content": [
{
"reader": {
"name": "mysqlreader",
"parameter": {
"username": "root",
"password": "your_password",
"connection": [
{
"jdbcUrl": ["jdbc:mysql://localhost:3306/biz_db"],
"table": ["orders"]
}
],
"splitPk": "id",
"where": "update_time >= '2024-06-01 00:00:00'"
}
},
"writer": {
"name": "hdfswriter",
"parameter": {
"defaultFS": "hdfs://namenode:8020",
"fileType": "text",
"path": "/user/hive/warehouse/ods_orders",
"fileName": "orders_incr",
"writeMode": "append",
"column": [
{"name": "id", "type": "bigint"},
{"name": "order_no", "type": "string"},
{"name": "amount", "type": "double"},
{"name": "create_time", "type": "string"},
{"name": "update_time", "type": "string"}
]
}
}
}
],
"setting": {
"speed": {
"channel": 2
}
}
}
}
经验之谈:增量同步的Channel数我一般设得比全量小。因为增量数据量小,并发太高反而浪费资源。另外,时间戳字段一定要建索引,否则每次同步都会全表扫描,源库扛不住。
4.4.2 增量同步的调度与状态管理
增量同步不是跑一次就完事的,它需要持续运行。我通常的做法是:
- 用一个状态表记录每次同步的
max(update_time) - 每次调度时,从状态表里读出上次同步的时间点
- 把这个时间点作为
where条件的起始值 - 同步完成后,更新状态表
状态表的结构很简单:
CREATE TABLE sync_status (
table_name VARCHAR(100) PRIMARY KEY,
last_sync_time DATETIME,
sync_type VARCHAR(10) -- 'full' 或 'incr'
);
调度脚本里,我会先查这个表,然后动态生成DataX的JSON配置。嗯,这里要注意:时间边界要处理好。比如上次同步到2024-06-01 00:00:00,这次同步的where条件应该是update_time >= '2024-06-01 00:00:00',而不是>。否则刚好在边界上的数据会漏掉。
4.5 全量+增量组合策略
在实际项目中,全量和增量不是二选一,而是组合使用。我常用的策略是:
- 首次运行:全量同步,把历史数据一次性搬过来
- 日常运行:增量同步,每天拉取新增/变更数据
- 定期全量:比如每周或每月跑一次全量,用来修正增量同步可能产生的数据偏差
为什么会需要定期全量?因为增量同步有个天然缺陷——删除操作无法感知。如果MySQL里删了一条记录,增量同步不会主动去Hive里删对应的数据。时间一长,两边数据就对不上了。定期全量相当于一次「数据校准」。
最佳实践:我建议每天增量 + 每周全量。增量保证时效性,全量保证准确性。如果数据量特别大,全量可以放在周末凌晨跑,避开业务高峰期。
4.6 避坑指南
最后,分享几个我在实战中踩过的坑,希望能帮你少走弯路。
- 字符集问题:MySQL用UTF-8,Hive默认也是UTF-8,但中间经过DataX传输时,如果配置不对,中文会变成乱码。解决方案:在JDBC URL里加上
characterEncoding=utf-8。 - 大字段处理:MySQL里的
TEXT、BLOB字段,同步到Hive后可能变成空值。这是因为DataX默认的byteBuffer大小不够。调大byteBufferSize参数即可。 - 时间精度丢失:MySQL的
datetime(3)存的是毫秒级,Hive的string类型存成字符串后,毫秒部分可能被截断。我建议统一用yyyy-MM-dd HH:mm:ss.SSS格式。 - 内存溢出:如果一张表有几百个字段,DataX默认的堆内存可能不够。启动时加上
-Xmx4g参数,给足内存。
我曾经在一个项目里,因为没注意字符集问题,导致同步到Hive的中文数据全是???。业务方第二天跑报表,发现所有中文用户名都变成了问号,那叫一个尴尬。从那以后,字符集检查成了我每次同步前的必做项。