ODS层设计原则:数据源接入、全量/增量同步策略、数据格式统一

ODS层,说白了就是实时数仓的"第一道门"。所有业务系统的数据,都得先经过这里,才能进入后面的分层。我做了这么多年实时数仓,见过太多因为ODS层设计不合理导致整个项目返工的情况。今天咱们就聊聊ODS层的三个核心设计原则。

数据源接入:先把"水龙头"接好

数据源接入,是ODS层最基础也最容易出问题的地方。你想想看,如果源头的数据都接不进来,或者接进来是乱的,后面再怎么做都是白搭。

我个人习惯把数据源分成三类:

数据源类型 典型代表 接入方式 注意事项
关系型数据库 MySQL、Oracle、PostgreSQL CDC(Canal/Debezium)+ JDBC 注意主键、索引、大字段
消息队列 Kafka、RocketMQ、Pulsar 直接订阅Topic 关注分区数、消息顺序、offset管理
日志文件 Nginx日志、业务日志 Filebeat/Flume + Kafka 注意日志格式变化、滚动策略

我在项目中遇到过最头疼的事,就是业务方突然改了数据库字段类型,结果CDC任务直接挂了。嗯,这里要注意:接入层一定要做Schema兼容性校验。我建议在接入时加一个"Schema Registry"组件,每次数据变更都先校验,不兼容的直接告警,而不是让任务默默失败。

避坑指南:我曾经接手过一个项目,ODS层直接用了业务库的原始表名和字段名。结果业务方做了一次数据库迁移,表名全改了,整个ODS层重建了三天。所以,我建议在ODS层建立数据源映射表,把物理表名和逻辑表名解耦。

全量/增量同步策略:什么时候拉全量,什么时候追增量?

这个问题,几乎每个做实时数仓的人都会被问到。说白了,没有银弹,得看场景。

增量同步:日常的主力

大部分时候,我们只需要同步变化的数据。CDC(Change Data Capture)是主流方案。我个人比较推荐Debezium,它支持多种数据库,而且社区活跃。

增量同步的核心是保证Exactly-Once语义。怎么做?我一般用"事务ID + 偏移量"双重校验:

-- 伪代码示例:增量数据去重逻辑
INSERT INTO ods.order_info
SELECT 
  order_id,
  user_id,
  amount,
  status,
  update_time,
  -- 用事务ID和偏移量做幂等
  CONCAT(binlog_file, '_', binlog_pos) AS event_id
FROM kafka_source
WHERE event_id NOT IN (SELECT event_id FROM ods.order_info_processed);
小技巧:如果数据量特别大,别用NOT IN做去重,性能太差。我习惯用Flink的KeyedProcessFunction,在状态里维护最近N分钟的event_id,既保证去重,又控制状态大小。

全量同步:什么时候用?

全量同步不是天天做,但有些场景绕不开:

  • 首次接入:新接入一个数据源,必须先拉一次全量
  • 数据修复:发现历史数据有问题,需要重新同步
  • Schema大变更:字段结构变化太大,增量追不上了
  • 回溯补数:比如要算去年同期的数据,增量只保留最近30天

全量同步怎么做?我建议用分页+多线程的方式。别一把梭哈,会把源库搞垮的。我曾经见过一个团队,全量同步时直接SELECT * FROM 亿级大表,结果业务库挂了半小时。

-- 推荐的全量同步方式:分页+游标
-- 每次只取5000条,用主键排序
SELECT * FROM order_info 
WHERE id > :last_id 
ORDER BY id 
LIMIT 5000;

全量+增量的衔接

这里有个坑:全量同步期间,增量数据还在产生。怎么保证数据不丢不重?

我常用的方案是"双轨并行":

  1. 先启动增量同步,记录当前偏移量
  2. 再启动全量同步,拉取历史数据
  3. 全量完成后,用增量数据覆盖全量中已更新的记录
  4. 最终以增量数据为准
核心原则:全量是"基线",增量是"补丁"。最终状态由增量数据决定,全量只负责把历史数据"填"进来。

数据格式统一:别让下游猜你的数据长什么样

这一点,我感触特别深。早期做数仓时,ODS层的数据格式五花八门,有的用JSON,有的用Avro,有的直接用CSV。下游的同事天天跑来问我:"这个字段是string还是long?""这个时间戳是秒还是毫秒?"

后来我定了个规矩:ODS层统一使用Avro格式。为什么选Avro?

  • Schema自带:每个数据文件都带着Schema定义,下游不用猜
  • 压缩率高:比JSON小30%-50%,节省存储和带宽
  • 支持Schema演化:加字段、删字段都兼容,不会影响下游
  • 序列化/反序列化快:比JSON快一个数量级

当然,不是所有场景都适合Avro。如果你对接的是前端埋点数据,JSON更灵活。我的建议是:ODS层内部统一一种格式,对外提供多种格式的视图。

我的经验:在ODS层加一个"元数据管理表",记录每个表的字段名、类型、描述、来源、更新时间。这样不管是开发还是运维,查一下就知道这个字段是干嘛的。别让知识只存在某个人的脑子里。

ODS层设计全景图

说了这么多,咱们用一张图把ODS层的设计思路串起来:

ODS层设计全景图 MySQL Kafka 日志文件 数据接入层:CDC / 订阅 / 采集 同步策略 全量同步(首次/修复/回溯) 增量同步(CDC/实时流) 数据格式 统一格式:Avro(推荐) Schema管理 + 兼容性校验 ODS存储层:Kafka Topic / HDFS / HBase 输出:统一、可回溯、Schema自描述 核心原则:源端解耦、格式统一、同步可追溯、Schema可演化

这张图把ODS层的核心流程串起来了。从数据源接入,到同步策略选择,再到格式统一,最后落到存储层。每一步都有讲究,每一步都可能踩坑。

总结一下:ODS层设计的三个原则,说白了就是"接得进来、同步得对、格式不乱"。做到这三点,后面的DWD、DWS层才能站得稳。我见过太多项目,ODS层草草了事,结果后面天天补数据、修数据,得不偿失。