4、离线链路基石:HDFS与Hive数仓分层设计(ODS/DWD/DWS/ADS)
聊完了实时链路,咱们得回头看看离线这边。说实话,离线数仓才是大多数公司的“压舱石”。实时再快,最终对账、报表、BI分析,还是得靠离线。而离线链路的基石,就是HDFS和Hive。
我个人习惯把数仓分层比作“厨房流水线”。原始食材(ODS)进来,经过清洗切配(DWD),做成半成品(DWS),最后端上桌(ADS)。每一层都有它的职责,乱不得。
核心原则:分层不是为了炫技,而是为了解耦和复用。你想想看,如果所有逻辑都堆在一张表里,改一个指标得重跑全量数据,那得多痛苦?
4.1 存储基石:HDFS 的选型与避坑
HDFS 是离线数仓的根。根不稳,上面全白搭。我在项目中遇到过几次因为 HDFS 小文件问题导致 NameNode 内存爆掉的故障,那叫一个惨烈。
这里有几个关键点,你得记住:
- 块大小:默认 128MB,别乱改。除非你确定你的数据平均文件大小远超这个值,否则别动。
- 副本数:默认 3 副本。如果集群机器紧张,可以降为 2,但别低于 2。我曾经为了省存储把副本改成 1,结果一台机器磁盘坏了,数据直接丢了一整天。
- 小文件治理:这是个大坑。HDFS 不适合存大量小文件。每个小文件都会在 NameNode 内存里占一条记录(约 150 字节)。1000 万个文件,光元数据就得 1.5GB 内存。
我的小技巧:在 Hive 建表时,尽量用 ORC 或 Parquet 格式,配合 Snappy 压缩。既能省空间,又能提升查询速度。别再用 TextFile 了,除非你只是为了临时看数据。
4.2 Hive 数仓分层:ODS / DWD / DWS / ADS
好了,存储层搞定,咱们聊聊分层。这是数仓设计的灵魂。我见过很多团队上来就建表,完全不考虑分层,最后数据血缘乱成一锅粥。
标准的四层架构,每一层都有明确的定位:
| 分层 | 全称 | 核心职责 | 数据粒度 | 存储策略 |
|---|---|---|---|---|
| ODS | 操作数据层 | 原封不动接入业务数据 | 原始明细 | 增量/全量,保留原始格式 |
| DWD | 明细数据层 | 清洗、去重、标准化、维度退化 | 明细 | ORC + Snappy,分区表 |
| DWS | 汇总数据层 | 按主题轻度汇总(天/小时) | 轻度聚合 | ORC + Snappy,分区表 |
| ADS | 应用数据层 | 面向业务报表、即席查询 | 高度聚合 | ORC + Snappy,小表可存 MySQL |
4.2.1 ODS 层:原样接入,不做任何加工
ODS 层说白了就是“垃圾桶”。业务库的数据怎么来,你就怎么存。别做任何清洗、转换。为什么?因为一旦你改了原始数据,出了问题你根本没法回溯。
我记得有一次,业务方说某个字段值不对。我直接去 ODS 层查原始日志,发现是业务系统本身写错了。如果我在 ODS 层做了清洗,这个锅就得我来背。
-- ODS 层建表示例
CREATE TABLE ods_user_event (
user_id STRING,
event_type STRING,
event_time STRING,
event_data STRING, -- 原始 JSON 字符串
dt STRING -- 分区字段
)
PARTITIONED BY (dt STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
STORED AS TEXTFILE; -- 保留原始格式,方便排查
注意:ODS 层表结构必须与源系统严格一致。字段名、类型、分隔符都不能改。改了就是给自己挖坑。
4.2.2 DWD 层:清洗、标准化、维度退化
DWD 层是数仓最核心的一层。这里要做三件事:
- 清洗:去掉空值、脏数据、格式不对的记录。
- 标准化:统一时间格式、枚举值、编码。
- 维度退化:把常用的维度字段(如城市、渠道)直接拉平到事实表中,避免每次查询都去 Join 维表。
我见过最坑的情况是,DWD 层没做维度退化,结果下游每个报表都要 Join 十几张维表,跑一次要半小时。后来我把常用的几个维度直接退化到事实表里,查询时间直接降到 2 分钟。
-- DWD 层建表示例(ORC 格式 + 分区)
CREATE TABLE dwd_user_event (
user_id STRING,
event_type STRING,
event_time TIMESTAMP,
city_id STRING, -- 退化维度
channel STRING, -- 退化维度
event_data MAP<STRING,STRING> -- 解析后的结构化数据
)
PARTITIONED BY (dt STRING)
STORED AS ORC
TBLPROPERTIES ('orc.compress'='SNAPPY');
4.2.3 DWS 层:按主题轻度汇总
DWS 层是“承上启下”的一层。它不做全量明细,而是按主题做轻度汇总。比如按天、按小时统计每个用户的 PV、UV、下单数。
为什么要做这一层?因为下游的报表、BI 工具,很少需要看单条明细。它们要的是聚合后的指标。如果每次都从 DWD 层全量扫描聚合,性能扛不住。
-- DWS 层建表示例(按天汇总用户行为)
CREATE TABLE dws_user_daily (
user_id STRING,
pv_count BIGINT,
uv_count BIGINT,
order_count BIGINT,
total_amount DECIMAL(18,2),
dt STRING
)
PARTITIONED BY (dt STRING)
STORED AS ORC
TBLPROPERTIES ('orc.compress'='SNAPPY');
经验之谈:DWS 层的粒度不要太小。按天、按小时就够了。别搞什么按分钟汇总,那跟 DWD 层没区别,还浪费存储。
4.2.4 ADS 层:面向业务,按需定制
ADS 层是给业务方直接用的。它可以是宽表,也可以是报表。这一层的数据量通常很小,但查询频率很高。
我建议 ADS 层的表尽量做成“大宽表”。把业务方需要的所有字段都放在一张表里。别让他们自己去 Join。业务方不是数据工程师,他们只想看到结果。
-- ADS 层建表示例(业务宽表)
CREATE TABLE ads_user_portrait (
user_id STRING,
total_orders BIGINT,
total_amount DECIMAL(18,2),
last_active_date STRING,
favorite_category STRING,
risk_level STRING,
dt STRING
)
PARTITIONED BY (dt STRING)
STORED AS ORC
TBLPROPERTIES ('orc.compress'='SNAPPY');
4.3 数仓分层架构图
说了这么多,不如一张图来得直观。下面这张图展示了从 ODS 到 ADS 的完整数据流转过程。
4.4 分层设计的最佳实践与避坑
最后,分享几个我在实战中踩过的坑,希望能帮你少走弯路。
- 不要跨层访问:ADS 层只能读 DWS 层,DWS 层只能读 DWD 层。ODS 层的数据不能直接给报表用。一旦破了这个规矩,数据血缘就乱了。
- 分区策略要统一:所有层都按
dt字段分区。这样回溯数据时,只需要改一个分区条件,不用到处改 SQL。 - 命名规范要严格:表名必须带分层前缀(ods_、dwd_、dws_、ads_)。字段名统一用下划线命名法。别搞什么大小写混用,那是在给自己找麻烦。
- 数据质量监控:每一层的数据产出后,都要做校验。比如 ODS 层的数据量不能为 0,DWD 层的去重率不能超过 5%。我曾经因为没做监控,导致 DWS 层的数据重复了三天才发现。
血的教训:千万别在 DWD 层做复杂的业务逻辑计算。DWD 层只做清洗和标准化。业务逻辑应该放在 DWS 层或 ADS 层。否则,一旦业务规则变了,你得重跑整个 DWD 层,那代价太大了。
嗯,离线数仓的分层设计,说白了就是“分而治之”。每一层做好自己的事,层与层之间通过分区和命名规范解耦。这样,无论业务怎么变,你都能快速响应。