☰
从一次刷卡数据采集说起:DataX 到底是怎么把数据「搬」到 Hive 的
2026/10/15 15:40:53 网站建设 项目流程

一、我们要干什么

学数仓的时候,很容易陷入一个误区:一上来就研究分层、建模、拉链表。但真把项目跑起来才发现,最容易卡住的其实是第一步——数据怎么进来。

我这次要做的链路很朴素:

业务数据库(MySQL) --DataX--> HDFS --Hive外部表--> 数仓ODS层

大体分三步走:

  1. 构建数仓:规划 ODS / DWD / DWS / ADS 分层,确定每层放什么
  2. 导入数据到业务数据库:准备模拟源数据(刷卡记录表、用户信息表)
  3. 数据采集:装 DataX,写配置,把两张表同步到 Hive

前两步是体力活,真正有技术含量的是第三步。所以这篇博客的重点放在数据采集上,把 DataX 的原理拆开讲清楚。


二、为什么是 DataX

离线同步工具其实不少:Sqoop、Flume、Kettle、DataX……选 DataX 的理由很实际:

对比项SqoopFlumeDataX
定位关系库 ↔ Hadoop日志流式采集异构数据源离线同步
数据源以 RDBMS 为主以文件/日志为主插件化,几十种
同步粒度任务级事件级单机多线程,任务级
学习成本中中低(一个 JSON 搞定)

DataX 最舒服的一点是:换数据源只需要换插件、改 JSON,不用改代码。业务库里是 MySQL、Oracle、SQLServer,目标端是 HDFS、Hive、HBase、ES,写法几乎一样。


三、原理:DataX 的架构到底是什么样

这是本文最想讲清楚的部分。很多教程直接甩一个 JSON 让你抄,抄完了也不知道为什么长这样。

3.1 一句话概括

DataX 是一个Framework + Plugin的架构。Framework 负责调度、缓冲、流控、错误处理;Plugin 负责「读」和「写」。

[图1:DataX 整体架构图,标注 Reader 插件 / Writer 插件 / Framework]

┌──────────────── Framework ────────────────┐ │ 调度 · 缓冲 · 流控 · 脏数据 · 统计 │ └───────────────────────────────────────────┘ ▲ │ ┌──────────┴──────────┐ ┌────────┴────────┐ │ Reader Plugin │ │ Writer Plugin │ │ (mysqlreader…) │ │ (hdfswriter…) │ └─────────────────────┘ └─────────────────┘ ▲ │ 业务数据库 MySQL HDFS / Hive

一个 Job 里,Reader 和 Writer 是一一对应的:一个 mysqlreader 配一个 hdfswriter。中间的所有搬运、排队、限速工作,全由 Framework 兜住。

3.2 关键:Job → Task → TaskGroup 三层拆分

这是 DataX 并发的核心,也是「channel 数配错了会出事」的根源。

层级含义数量由谁决定
Job一次同步作业,对应一个 JSON 配置文件你写几个 JSON 就有几个 Job
TaskJob 按分片规则切出来的最小执行单元由splitPk和切分策略决定
TaskGroupTask 的容器,直接对应并发度由setting.speed.channel决定

数据流向是这样的:

Reader 切分出的 Task ──► Channel(内存队列) ──► Writer Task (N 个) (限速/背压) (N 个)

Channel 是理解 DataX 性能的钥匙:它本质是一个有界阻塞队列。

  • 队列满了 → Reader 线程阻塞 → 不会把内存撑爆(这就是「背压」)
  • 队列空了 → Writer 线程等待 → 不会空转浪费 CPU
  • channel配几个,就有几个并发的「流水线」

所以调channel不是无脑调大就行:channel 数 ≈ 并发线程数 ≈ 需要的内存和数据库连接数。

3.3 线程模型:单进程,多线程

DataX 是单机单进程多线程模型,不是分布式的。一个 DataX 进程内部:

JobContainer(主线程) ├── 切分 Task、调度、统计、收尾 └── TaskGroupContainer × N(每个一个线程) └── Task × M(每个一个线程,内含 Reader + Writer 两个执行体)

这就是为什么 DataX单机跑得快、但上限取决于机器资源。想要更高吞吐,标准做法是「多机部署 + 用调度器切分任务」,而不是把 channel 无限调大。

3.4 一次执行的完整流程

你敲下python bin/datax.py job.json之后,背后发生了这些事:

  1. 解析:读取 JSON,校验job.setting和job.content结构是否合法
  2. 加载插件:根据reader.name/writer.name去plugin/reader/、plugin/writer/下找对应插件,装载 jar
  3. preHandler(重要):Reader 根据splitPk或切分策略,把一次大读取切成多个 Task
  4. schedule:按channel数把 Task 分配到 TaskGroup,启动线程开始搬运
  5. 流控:Channel 队列 + RateLimiter 双重控速
  6. postHandler:收尾、flush、统计并打印报告
  7. 输出报告:总记录数、读写速度、耗时、脏数据量

[图2:datax.py 执行完成后终端打印的统计报告]

那个报告非常有用,尤其最后一行任务启动时刻 / 结束时刻 / 总计耗时,是后面调优的唯一依据。

3.5 限速与脏数据:两个容易被忽略的机制

限速:在setting.speed里可以配byte(字节/秒)和record(记录/秒)。底层是 RateLimiter,通过让线程睡眠来削峰。为什么需要它?——因为线上库经不起你把 IO 打满,采集任务必须「让路给业务」。

脏数据:指 Reader 读到了、但 Writer 写不进去的记录(比如字段超长、类型不匹配、编码错误)。默认行为是直接丢弃,只在统计里计个数。调试阶段建议打开errorLimit并把脏数据落到文件里,否则你会遇到「任务成功但数据少了」这种最折磨人的问题。


四、动手:刷卡主数据采集

4.1 编写 DataX 配置文件

DataX 的配置就是一个 JSON,结构永远是三段:

{"job":{"setting":{"speed":{...},"errorLimit":{...}},"content":[{"reader":{...},"writer":{...}}]}}

setting管全局行为,content管「谁读、谁写」。核心是content里的 reader/writer。

[图3:编写好的刷卡主数据 JSON 配置文件]

几个必踩的配置点:

  • splitPk:必须选一个数值型且分布均匀的字段(比如自增 id),DataX 靠它把大查询切片,并发才有效。选字符串主键或者分布倾斜的字段,切片会失效,等于白配并发。
  • column:显式列出字段,不要图省事用*。显式列字段能减少网络传输、避免源表加字段后同步错位。
  • writeMode:append(追加)/nonConflict(重名报错)/truncate(先清空)。首次全量用 truncate,日常增量用 append,选错就是数据翻倍。
  • fieldDelimiter:HDFS 文本文件的分隔符。别用逗号——业务字段里出现逗号会让 Hive 直接串列,推荐\u0001这类不可见字符。

4.2 在 Hive 中创建表

这里有个新手最容易懵的顺序问题:

DataX 的 hdfswriter 是往 HDFS 写文件,它不知道 Hive 表的存在。

所以真实顺序是:

  1. 先执行 DataX 任务 → 数据以文件形式落在 HDFS 某个目录
  2. 再在 Hive 建外部表,LOCATION指向那个目录
CREATEEXTERNALTABLEods_swipe_master(idBIGINT,card_no STRING,...)ROWFORMAT DELIMITEDFIELDSTERMINATEDBY'\u0001'LOCATION'/warehouse/ods/swipe_master';

建外部表的好处是:删表不删数据,重跑任务不会因为 DROP TABLE 把辛苦采集的数据一起干掉。

4.3 执行脚本

python bin/datax.py job/swipe_master.json

看到退出码 0 且报告里「读出记录总数 == 写出记录总数 == 脏数据 0」,这一步才算真的过。

[图4:执行脚本后 DataX 打印的成功报告]


五、用户信息表:换汤不换药

用户信息表的流程完全一样,只是reader 的 SQL 和 writer 的路径/表名不同。

编写 DataX 脚本 → 在 Hive 建表 → 执行脚本

这里真正值得记录的差异是:如果源表没有合适的数值型主键怎么办?

比如用户表主键是user_id这种 UUID 字符串,splitPk就没法用,只能退化成单 Task 串行读,速度会慢一个数量级。对策有两个:

  • 找表里其它数值型且有索引的字段当splitPk(比如create_time的时间戳)
  • 接受单通道,靠调大batchSize和压缩来换吞吐

六、优化:脚本跑通之后才是开始

跑通只是能出数,优化才是让链路能上生产。前两节脚本的第 3、4 条优化,本质是同一类思路。

6.1 吞吐上不去

现象:任务能跑完,但速度只有几 MB/s,数据库 CPU 却很闲。

原因:默认channel = 1,等于单线程在搬;或者splitPk没配/配置无效,Reader 根本切不出多个 Task。

对策:

  • channel提到 4~8(不要超过数据库承受能力)
  • 确认splitPk是数值型且分布均匀
  • 增大batchSize(MySQL 建议 1024~2048),减少往返次数
  • 调大 JVM 内存:bin/datax.py里的-Xms/-Xmx,默认值偏小,大表容易 OOM

6.2 全量太重,想改成增量

现象:每天整表重刷,源库压力大,目标端文件越滚越多。

原因:配置里没有 where 条件,每次都是select全表。

对策:在 reader 的 SQL 上加时间窗口,配合调度器传参:

select...fromt_swipewherecreate_time>='${bizdate}'andcreate_time<'${bizdate_next}'

再配合 Hive 端按天分区,做到「只读当天、只写当天分区」。

6.3 HDFS 上一堆小文件

现象:一次任务结束后,目标目录里散落大量几十 KB 的小文件。

原因:文件数 ≈ Task 数 × 每个 Task 产生的文件数。channel 调大了、切片变多了,小文件自然就多。小文件一多,Hive 查询时元数据开销和 NameNode 压力都会爆炸。

对策:

  • 控制channel数,别为了速度把切片切得太碎
  • 写 ORC/Parquet 时开启小文件合并
  • 加一层「小文件合并」的定时任务(数仓里几乎是标配)

6.4 数据对不上

现象:任务显示成功,但 Hive 里查出来条数比源表少,或者字段串位了。

原因:八成是脏数据被默认丢弃,或者分隔符和业务字段内容冲突。

对策:

  • 打开errorLimit,把脏数据输出到文件排查
  • 确认 Hive 建表的分隔符与 DataX 的fieldDelimiter完全一致(这是最常见的一处失误)
  • 检查空值处理:源端null与 Hive 端\N的映射

七、小结

回头再看整条链路,其实就三件事:

  1. 构建数仓——先想清楚分层,别急着写代码
  2. 准备业务数据——模拟源数据,方便反复测试
  3. 数据采集——用 DataX 把数据搬进来

而 DataX 的原理,浓缩成三句话:

  • 架构是 Framework + Plugin,读写的差异被插件吃掉,剩下的调度和流控由框架兜底;
  • 并发靠 Task 切分,splitPk决定能不能并行,channel决定并行多少个;
  • 性能的本质是取舍,速度、源库压力、目标端文件数三者互相牵制,调优就是在这三者之间找平衡点。

理解了这三点,再回头看那个 JSON,就不是「抄来的配置」了,而是「我知道每一行为什么这么写」。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询