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"组件,每次数据变更都先校验,不兼容的直接告警,而不是让任务默默失败。
全量/增量同步策略:什么时候拉全量,什么时候追增量?
这个问题,几乎每个做实时数仓的人都会被问到。说白了,没有银弹,得看场景。
增量同步:日常的主力
大部分时候,我们只需要同步变化的数据。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);
全量同步:什么时候用?
全量同步不是天天做,但有些场景绕不开:
- 首次接入:新接入一个数据源,必须先拉一次全量
- 数据修复:发现历史数据有问题,需要重新同步
- Schema大变更:字段结构变化太大,增量追不上了
- 回溯补数:比如要算去年同期的数据,增量只保留最近30天
全量同步怎么做?我建议用分页+多线程的方式。别一把梭哈,会把源库搞垮的。我曾经见过一个团队,全量同步时直接SELECT * FROM 亿级大表,结果业务库挂了半小时。
-- 推荐的全量同步方式:分页+游标
-- 每次只取5000条,用主键排序
SELECT * FROM order_info
WHERE id > :last_id
ORDER BY id
LIMIT 5000;
全量+增量的衔接
这里有个坑:全量同步期间,增量数据还在产生。怎么保证数据不丢不重?
我常用的方案是"双轨并行":
- 先启动增量同步,记录当前偏移量
- 再启动全量同步,拉取历史数据
- 全量完成后,用增量数据覆盖全量中已更新的记录
- 最终以增量数据为准
数据格式统一:别让下游猜你的数据长什么样
这一点,我感触特别深。早期做数仓时,ODS层的数据格式五花八门,有的用JSON,有的用Avro,有的直接用CSV。下游的同事天天跑来问我:"这个字段是string还是long?""这个时间戳是秒还是毫秒?"
后来我定了个规矩:ODS层统一使用Avro格式。为什么选Avro?
- Schema自带:每个数据文件都带着Schema定义,下游不用猜
- 压缩率高:比JSON小30%-50%,节省存储和带宽
- 支持Schema演化:加字段、删字段都兼容,不会影响下游
- 序列化/反序列化快:比JSON快一个数量级
当然,不是所有场景都适合Avro。如果你对接的是前端埋点数据,JSON更灵活。我的建议是:ODS层内部统一一种格式,对外提供多种格式的视图。
ODS层设计全景图
说了这么多,咱们用一张图把ODS层的设计思路串起来:
这张图把ODS层的核心流程串起来了。从数据源接入,到同步策略选择,再到格式统一,最后落到存储层。每一步都有讲究,每一步都可能踩坑。