3、ODS层(操作数据存储层):数据进入数仓的第一道门
ODS层,全称是Operational Data Store。说白了,它就是数据仓库的"接待大厅"。所有来自业务系统的原始数据,第一站都要到这里报到。
我刚开始做数仓时,有个误区——觉得ODS层就是简单地把数据搬过来,没啥技术含量。后来被现实狠狠教育了一顿。有一次,源系统的某个字段类型变了,我们的同步脚本直接崩溃,整整丢了半天的数据。从那以后,我再也不敢小看ODS层的设计。
3.1 ODS层的定位与作用
ODS层到底扮演什么角色?我习惯用三个词来概括:缓冲、还原、隔离。
- 缓冲层:源系统和数仓之间,需要一个缓冲区。业务系统不能直接暴露给下游ETL,否则一个慢查询就能把线上库拖垮。
- 还原点:ODS层保存的是最原始的数据快照。万一后面哪层算错了,可以从ODS重新跑,不用再去打扰业务库。
- 隔离带:源系统的表结构、命名规范、数据类型千奇百怪。ODS层先把这些差异"抹平",给下游提供一个相对干净的起点。
核心原则:ODS层的数据,在结构上尽量保持与源系统一致,不做任何业务逻辑转换。只做两件事——原样接入和增量追加。
你想想看,如果ODS层就开始做数据清洗、维度退化,那出了问题到底是源系统的问题还是ETL的问题?根本说不清楚。所以,保持原始,是ODS的第一纪律。
3.2 数据同步策略:全量与增量
数据怎么从业务库到ODS?两种策略:全量同步和增量同步。选哪个,取决于数据量和业务特性。
| 策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 全量同步 | 小表(百万级以下)、字典表、配置表 | 实现简单,数据完整 | 数据量大时效率低,浪费存储 |
| 增量同步 | 大表(千万级以上)、流水表、日志表 | 效率高,节省资源 | 需要可靠的增量标识,实现复杂 |
| 全量+增量混合 | 首次加载用全量,后续用增量 | 兼顾效率和完整性 | 需要维护同步状态 |
我个人习惯的做法是:小表全量,大表增量,首次全量。举个例子,一张用户维度表只有几十万条,每天全量拉一遍,几分钟搞定,简单又可靠。但一张订单流水表每天新增几百万条,全量同步就是灾难——我见过有人全量跑订单表,跑了6个小时还没跑完,直接把业务库的IO打满了。
避坑指南:我曾经遇到过一个坑——增量同步依赖时间戳字段,但业务系统的时间戳精度不够(只到秒),导致同一秒内有多条数据更新时,部分数据漏掉了。后来我改用自增ID+时间戳双重校验,才彻底解决。所以,永远不要只依赖一个字段做增量标识。
3.3 数据接入方式:SQOOP / DataX / Canal
工具选型,是ODS层设计的另一个关键点。市面上主流的有三种:SQOOP、DataX、Canal。它们各有各的脾气。
3.3.1 SQOOP:老牌选手,适合批量
SQOOP是Hadoop生态的老将,主要做Hadoop和关系型数据库之间的数据搬运。它基于MapReduce,适合离线批量同步。
# 全量导入示例
sqoop import \
--connect jdbc:mysql://host:3306/db \
--username root \
--password 123456 \
--table orders \
--target-dir /user/hive/ods/orders \
--fields-terminated-by '\001' \
--num-mappers 4
# 增量导入示例
sqoop import \
--connect jdbc:mysql://host:3306/db \
--table orders \
--incremental append \
--check-column id \
--last-value 1000000 \
--target-dir /user/hive/ods/orders
SQOOP的优点是对Hive/HDFS集成好,缺点也很明显——不支持实时,而且MapReduce的启动开销大,小表同步反而慢。
3.3.2 DataX:阿里出品,异构数据源利器
DataX是阿里巴巴开源的异构数据源同步框架。它的设计理念是"一次编写,到处运行"——只要定义好reader和writer插件,就能在任意数据源之间同步。
{
"job": {
"content": [
{
"reader": {
"name": "mysqlreader",
"parameter": {
"connection": [{"jdbcUrl": ["jdbc:mysql://host:3306/db"], "table": ["orders"]}],
"username": "root",
"password": "123456",
"column": ["*"],
"splitPk": "id"
}
},
"writer": {
"name": "hdfswriter",
"parameter": {
"defaultFS": "hdfs://namenode:8020",
"path": "/user/hive/ods/orders",
"fileName": "orders",
"writeMode": "append",
"fieldDelimiter": "\001"
}
}
}
],
"setting": {
"speed": {"channel": 4}
}
}
}
DataX的优点是性能好、监控完善,支持限流和断点续传。我曾在项目里用DataX同步每天上亿条的用户行为日志,稳定跑了两年没出过问题。
3.3.3 Canal:实时同步,监听Binlog
Canal是阿里巴巴的MySQL Binlog解析工具。它伪装成MySQL的从库,实时接收主库的Binlog变更,然后推送给下游。
# Canal 客户端示例(Java)
CanalConnector connector = CanalConnectors.newSingleConnector(
new InetSocketAddress("127.0.0.1", 11111),
"example", "", "");
connector.connect();
connector.subscribe("db\\\\.orders");
while (true) {
Message message = connector.getWithoutAck(100);
long batchId = message.getId();
if (batchId == -1 || message.getEntries().isEmpty()) {
Thread.sleep(1000);
continue;
}
// 处理Binlog事件
for (CanalEntry.Entry entry : message.getEntries()) {
// 解析INSERT/UPDATE/DELETE
}
connector.ack(batchId);
}
Canal适合实时同步场景,比如把MySQL的订单变更实时推送到Kafka,再写入ODS。但要注意,Binlog解析对源库有一定性能影响,生产环境建议用从库的Binlog,别直接连主库。
| 工具 | 同步模式 | 延迟 | 适用场景 | 我的评价 |
|---|---|---|---|---|
| SQOOP | 批量 | 分钟~小时级 | 离线大表全量/增量 | 老牌稳定,但不够灵活 |
| DataX | 批量 | 分钟级 | 异构数据源、高吞吐 | 我的首选,性能强悍 |
| Canal | 实时流 | 秒级 | 实时同步、Binlog订阅 | 实时场景必备,注意性能 |
3.4 ODS层表设计规范
ODS层的表设计,核心就一句话:贴近源,但高于源。贴近源是指字段、类型尽量保持一致;高于源是指要加上数仓自己的管理字段。
3.4.1 表命名规范
我习惯的命名规则是:ods_<数据源>_<业务域>_<表名>。举个例子:
ods_mysql_trade_orders—— MySQL交易库的订单表ods_hbase_log_user_action—— HBase的用户行为日志ods_kafka_click_stream—— Kafka的点击流数据
这样命名,一看就知道数据从哪来、属于哪个业务域,排查问题时省了不少时间。
3.4.2 字段设计规范
除了源系统的业务字段,ODS表必须包含以下技术字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
etl_id |
bigint | ETL批次ID,用于追溯 |
etl_time |
timestamp | 数据写入ODS的时间 |
etl_source |
string | 数据来源标识(如mysql/hbase) |
etl_op_type |
string | 操作类型(I/U/D,用于CDC场景) |
etl_version |
int | 数据版本号,用于处理重复数据 |
注意:技术字段不要用源系统可能出现的字段名,比如create_time、update_time。我见过有人用update_time做ETL时间戳,结果源系统也有同名字段,数据加载时直接覆盖了——排查了整整一天才发现。所以,技术字段统一加etl_前缀,避免冲突。
3.4.3 分区策略
ODS层的数据量通常很大,分区是必须的。我推荐按日期分区,每天一个分区。这样既方便管理,也方便回溯。
-- Hive ODS表DDL示例
CREATE TABLE ods_mysql_trade_orders (
order_id BIGINT,
user_id BIGINT,
product_id BIGINT,
amount DECIMAL(10,2),
order_status STRING,
create_time TIMESTAMP,
update_time TIMESTAMP,
-- 技术字段
etl_id BIGINT,
etl_time TIMESTAMP,
etl_source STRING,
etl_op_type STRING,
etl_version INT
)
PARTITIONED BY (dt STRING) -- 按日期分区,格式 yyyyMMdd
STORED AS ORC
TBLPROPERTIES ('orc.compress'='SNAPPY');
嗯,这里要注意:分区字段不要出现在业务字段中。有些新人会把dt也作为业务字段存储,这样既浪费存储,又容易导致数据不一致。
3.5 ODS层核心流程总览
为了让你更直观地理解ODS层的整体架构,我画了一张流程图。它展示了数据从源系统到ODS层的完整路径,以及不同工具在其中的位置。
从这张图可以看得很清楚:源系统 → 接入工具 → ODS层,这是一个标准的三段式架构。不同的源系统,选择不同的接入工具,最终汇聚到ODS层统一存储。
3.6 小结
ODS层是整个数据仓库的基石。它的设计质量,直接决定了后续所有数据加工的稳定性和可追溯性。总结几个关键点:
- 定位:缓冲、还原、隔离,不做业务转换
- 同步策略:小表全量,大表增量,首次全量
- 工具选型:批量用DataX,实时用Canal,SQOOP作为备选
- 表设计:命名规范、技术字段、日期分区,缺一不可
嗯,ODS层的内容就讲到这里。记住一句话:ODS层越"原始",数仓越"可靠"。别在ODS层耍聪明做转换,那是后面几层的事。