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层数据接入流程 源系统 MySQL 业务库 HBase 日志库 Kafka 消息流 接入工具 SQOOP(批量) DataX(批量) Canal(实时流) ODS 操作数据存储层 Hive / HDFS / Kafka ods_<源>_<域>_<表> 说明: 小表(百万级以下)→ 全量同步,使用SQOOP或DataX;大表(千万级以上)→ 增量同步,使用DataX或Canal实时流。

从这张图可以看得很清楚:源系统 → 接入工具 → ODS层,这是一个标准的三段式架构。不同的源系统,选择不同的接入工具,最终汇聚到ODS层统一存储。

3.6 小结

ODS层是整个数据仓库的基石。它的设计质量,直接决定了后续所有数据加工的稳定性和可追溯性。总结几个关键点:

  • 定位:缓冲、还原、隔离,不做业务转换
  • 同步策略:小表全量,大表增量,首次全量
  • 工具选型:批量用DataX,实时用Canal,SQOOP作为备选
  • 表设计:命名规范、技术字段、日期分区,缺一不可

嗯,ODS层的内容就讲到这里。记住一句话:ODS层越"原始",数仓越"可靠"。别在ODS层耍聪明做转换,那是后面几层的事。