0、前言
场景:最近有求职者在面试中被问到数仓设计可不可以不分层?当求职者回答不可以的时候,面试官似乎对这一回答并不满意。
分析:针对该问题其实面试官并不是不懂数仓分层的意义,而是想要求职者讲明白什么样情况下需要分层,该如何分层,看看求职者是否真正对分层掌握,能在不同情形和环境下做出对策。数仓的面试其实就是一场思辨的过程,更像是哲学上的讨论,没有绝对的好坏,只有在某种场景、条件下的合不合适。
数据仓库是否分层取决于具体的业务场景、团队规模、数据复杂度以及维护需求。虽然分层设计有其显著优势,但在某些情况下,不分层的简化设计也可能是可行的。以下是更系统的分析
一、数仓分层目的
分层(如ODS、DWD、DWS、ADS)的核心目标是:
-
解耦数据流:分离原始数据、清洗整合后的数据、聚合数据和应用数据。
-
提升复用性:中间层数据可被多个下游应用复用,减少重复计算。
-
优化性能:通过分层聚合减少复杂查询的计算量。
-
保障一致性:统一数据清洗和业务规则,避免口径混乱。
-
降低维护成本:问题定位和迭代更高效。
二、不分层的适用场景
适用场景 | 说明 |
小型团队或简单业务 | ●场景:数据量小(如日增量<GB级)、业务逻辑简单(如仅需几张报表)。 ●优势:省去分层设计的开发和管理成本,快速交付。 ●风险:业务扩展后可能面临重构压力。 |
实时数据流处理 | ●场景:需要实时响应的场景(如监控、风控),数据直接从消息队列(如Kafka)写入OLAP引擎(如Doris)。 ●方案:使用流式处理(Flink)+ 实时数仓(如ClickHouse),跳过传统分层。 |
探索性分析或临时需求 | ●场景:临时数据探查、PoC验证,可直接在原始数据层(ODS)或数据湖中操作。 ●工具:通过Trino、Spark SQL直接查询原始数据,快速输出结果。 |
现代架构的演进 | ●Data Lakehouse:结合数据湖的灵活性和数仓的管理能力(如Delta Lake、Iceberg),部分场景可替代分层。 ●Serverless查询引擎:如BigQuery、Snowflake,通过虚拟化层自动优化查询,降低对分层的依赖。 |
2.1 不分层潜在风险
风险 | 说明 |
数据冗余与一致性 | ●不同业务可能重复加工相同逻辑,导致资源浪费和结果不一致。 ●案例:用户画像的“活跃用户”定义在A报表和B看板中不一致。 |
维护复杂度 | ●业务逻辑分散在多个ETL任务或SQL脚本中,变更时需全网排查。 ●统计:某电商未分层时,一个字段变更需修改12个任务,耗时3天;分层后仅需修改1个DWD表。 |
性能瓶颈 | ●复杂查询直接扫描原始数据,可能导致计算时间过长。 ●测试数据:某未分层日志分析系统,TOP10用户查询耗时120秒;分层后通过聚合层降至3秒。 |
数据质量风险 | ●缺乏统一的清洗和稽核层,错误数据可能直接影响业务。 ●案例:未清洗的订单数据中包含测试账号,导致GMV统计偏高。 |
三、数仓分层建设指南
数仓分层 | 说明 |
ODS | ODS(Operation Data Store)操作数据层,即原始数据层,又叫贴源层,与业务系统基本同构(可能会增加管理字段),目的是保留历史,解耦业务数据库,这样整个数据平台只需要访问一次业务数据库即可。 所以ODS层存在的意义是尽可能减少对业务数据库的访问压力。ODS层有些时候会细分为两层,一个STG数据缓冲层,存原始数据,一个ODS,存简单清洗的数据。 |
DWD | DWD(Data Warehouse Detail)层是数据仓库体系中的明细数据层,位于ODS(Operational Data Store,原始数据层)之上,DIM(维度层)和DWS(数据服务层)之下。它是数据仓库的核心加工区域,承担着将原始数据转换为可用分析数据的重要职责。 ▶主要特点: ●面向主题:按业务领域进行数据组织 ●粒度统一:保持相同业务过程的数据粒度一致 ●结构规范:字段命名和定义遵循统一标准 ●历史完整:保留历史变更数据,确保可追溯性 |
DWS | DWS(Data Warehouse Service)层是数据仓库中的服务数据层,它基于DWD层的明细数据,按照业务主题对数据进行轻度汇总,形成主题宽表,主要包含了1d(最近1日)、nd(最近n日)、td(历史至今)等时间维度的指标,是数据仓库中承上启下的关键环节,既保证了数据的可复用性,又为上层应用提供了标准化的数据服务。 ▶业务价值: ●降低计算成本:预计算常用指标,避免重复计算 ●统一指标口径:确保全公司指标计算规则统一 ●提升分析效率:提供主题化的宽表,方便业务分析 ●支持多维分析:保留完整维度,支持灵活查询 |
DIM | DIM层,即dimension,维度,是连接数据仓库ODS原始数据与后续分析层的关键桥梁。 DIM层作为数据仓库的关键组成部分,其设计质量直接影响整个数据仓库的可用性和分析效率。 通过正确选择维度表类型、设计合理的更新策略,以及实施有效的优化措施,可以构建出高质量的维度层,为上层分析提供坚实基础。 |
标签层 | 标签工厂生产的各种标签,用户标签、商品标签等等,一般在DWD层之上,互联网业务特有,现在也慢慢扩展到其他领域。 |
ADS层 | ADS(Application Data Store)层是数据仓库的最顶层,直接面向业务应用,为数据分析、报表展示和业务决策提供数据支持。它是数据仓库与业务系统之间的桥梁,将经过清洗、转换和汇总的数据以业务友好的方式呈现给最终用户。 ▶应用场景: ●业务报表:为管理层提供决策支持的各类报表。 ●运营分析:支持运营人员进行用户行为分析和营销效果评估。 ●风险控制:提供风险监控和预警数据。 ●客户服务:支持客服人员进行客户画像分析和精准服务。 ●产品优化:为产品团队提供用户反馈和使用数据,指导产品迭代。 |
DM层 | DM层,又叫主题层,与主题域不一样,这是在企业级数仓之上,对某个单独业务或者部门专门设立的小型数据集市。DM层又可以根据业务需求再次拆分。 |
3.1 DWD层建设指南
3.1.1 建设基本思路
1.业务分域设计
DWD层首先应该按照业务域(Domain)进行划分,常见的业务域包括:用户域:用户基础信息、注册、登录、地址等
-
商品域:商品、类目、品牌等基础数据
-
交易域:订单、支付、退款等交易过程数据
-
流量域:点击、曝光、跳出等用户行为数据
-
营销域:活动、优惠券、秒杀等营销数据
-
库存域:库存、仓储等供应链数据
-
互动域:评论、收藏、分享等社交数据
这种分域方式让数据架构更清晰,方便不同业务部门使用自己关心的数据。
2.明确数据模型类型
DWD层主要包含两类数据模型:
-
事实表:记录业务事件,通常包含度量值和外键
-
维度表:描述业务对象的属性,如用户、商品、时间等
根据数据更新方式,DWD表可以分为:
-
全量表(Full):每次加载会覆盖所有历史数据
-
增量表(Inc):只加载新增或变化的数据
-
拉链表:记录数据的历史变更,保留所有版本信息
3.数据加工规范
DWD层的数据加工遵循以下规范:字段规范:统一字段命名和数据类型,如id、create_time等
-
数据清洗:处理空值、异常值、重复值等
-
数据转换:类型转换、编码转换、格式标准化
-
数据整合:关联多个来源的数据,丰富信息维度
-
指标计算:生成基础派生指标
4.分区与性能优化
为提高查询效率,DWD层通常采用分区策略:
-
按时间分区:最常见的方式,如按天分区(k1字段)
-
按业务分区:某些场景下按业务线或地区分区
-
复合分区:时间+业务的组合分区策略
3.1.2 建设案例
下面以dwd_user_address_full(用户地址表)表为例,详细说明DWD层表的设计和实现过程。
1.需求分析
用户地址信息是电商系统的重要基础数据,需要支持:
-
用户历史地址查询
-
配送范围分析
-
区域销售分布统计
2.数据源分析
从ODS层,我们有两个相关表:
-
ods_user_address_full:用户地址基本信息
-
ods_base_province_full:省份信息
通过分析表结构,发现:
-
用户地址表包含用户ID、省份ID等基本信息
-
省份表包含省份名称、地区编码等信息
-
缺少专门的城市和区县表
3.表结构设计
CREATE TABLE dwd.dwd_user_address_full
(
`id` VARCHAR(255) COMMENT '地址ID',
`k1` DATE COMMENT '数据日期',
`user_id` STRING COMMENT '用户ID',
`province_id` STRING COMMENT '省份ID',
`province_name` STRING COMMENT '省份名称',
`city_id` STRING COMMENT '城市ID',
`city_name` STRING COMMENT '城市名称',
`district_id` STRING COMMENT '区县ID',
`district_name` STRING COMMENT '区县名称',
`detail_address` STRING COMMENT '详细地址',
`consignee` STRING COMMENT '收货人',
`phone_num` STRING COMMENT '联系电话',
`is_default` STRING COMMENT '是否默认地址',
`create_time` DATETIME COMMENT '创建时间',
`operate_time` DATETIME COMMENT '操作时间',
`postal_code` STRING COMMENT '邮政编码',
`full_address` STRING COMMENT '完整地址'
)
ENGINE=OLAP
UNIQUE KEY(`id`, `k1`)
DISTRIBUTED BY HASH(`id`);
4.ETL实现
-- 用户域用户地址全量表
INSERT INTO dwd.dwd_user_address_full(id, k1, user_id, province_id, province_name,
city_id, city_name, district_id, district_name, detail_address, consignee,
phone_num, is_default, create_time, operate_time, postal_code, full_address)
select
ua.id,
date('${pdate}') as k1, -- 使用参数日期作为k1值
ua.user_id,
ua.province_id,
bp.name as province_name,
ua.city_id,
bp.area_code as city_name, -- 这里假设使用area_code作为城市名称,实际应根据实际情况调整
ua.district_id,
bp.iso_code as district_name, -- 这里假设使用iso_code作为区域名称,实际应根据实际情况调整
ua.user_address as detail_address, -- 将user_address字段映射为detail_address
ua.consignee,
ua.phone_num,
ua.is_default,
now() as create_time, -- 使用当前时间作为create_time
now() as operate_time, -- 使用当前时间作为operate_time
null as postal_code, -- 暂无此数据,可根据实际情况调整
concat(bp.name, ' ', bp.area_code, ' ', bp.iso_code, ' ', ua.user_address) as full_address -- 完整地址拼接
from
(
select
id,
user_id,
province_id,
province_id as city_id, -- 暂用province_id代替city_id
province_id as district_id, -- 暂用province_id代替district_id
user_address,
consignee,
phone_num,
is_default
from ods.ods_user_address_full
) ua
left join
(
select
id,
name,
area_code,
iso_code
from ods.ods_base_province_full
) bp
on ua.province_id = bp.id;
5.建设经验总结:
-
统一规范先行:在开始建设前,制定统一的命名规范和数据标准
-
分阶段建设:先搭建核心业务域,后扩展其他业务域
-
灵活处理数据缺失:面对不完美的数据源,使用合理的替代方案
-
重视文档和注释:详细记录表结构、字段含义和处理逻辑
-
持续优化:随着业务发展,不断完善DWD层数据模型
3.2 DIM层建设指南
3.2.1 DIM层的基本概念与作用
基本概念 | 维度层是数据仓库架构中专门用于存储和管理维度数据的层次,位于ODS层之上,为DWD、DWS和ADS层提供标准化的维度信息。 维度数据描述业务实体的属性特征,如商品、用户、时间、地理位置等,它们与事实表(如订单、支付)结合,构成完整的业务分析视图。 |
核心作用 | 提供统一维度视图 ●标准化企业维度定义,解决源系统维度不一致问题数据质量保障 ●对维度数据进行清洗、去重和一致性校验提升查询效率 ●预先整合维度信息,减少复杂分析时的表连接操作支持历史追溯 ●记录维度变化历史,支持不同时间点的历史分析降低冗余度 ●避免在各层重复维护维度属性信息 |
典型表类型 | DIM层表主要分为两大类:常规维度表 如商品维度表、品牌维度表等,一般采用全量更新模式缓慢变化维度(SCD) 记录随时间变化的维度属性,主要有三种类型: ●SCD Type 1:直接覆盖更新,不保留历史 ●SCD Type 2:保留历史版本,通过有效期标记区分 ●SCD Type 3:保留有限历史,通过额外字段存储 |
3.2.2 设计原则与策略
1.表设计原则
用户地址信息是电商系统的重要基础数据,需要支持:
-
包含维度的所有关键属性,满足分析需求一致性
-
提供统一标准的维度定义,消除歧义稳定性
-
维度设计应具备稳定性,避免频繁变更可用性
-
优化查询效率,支持高并发访问可追溯性
-
对于关键维度,保留历史变更记录
2.更新策略选择
根据维度变化特性选择合适的更新策略:
-
适用于变化频率低、数据量小的维度,如商品类目增量更新
-
适用于持续新增的维度,如新商品拉链表设计
-
适用于需要保留历史版本的维度,如用户属性变化
3.2.3 案例分析
3.2.3.1 商品维度表实现
-- 以Doris表来构建商品维度表(dim_sku_full)
CREATE TABLE dim.dim_sku_full
(
`id` VARCHAR(255) COMMENT 'SKU ID,商品唯一标识',
`k1` DATE NOT NULL COMMENT '分区字段,数据日期',
`price` DECIMAL(16, 2) COMMENT '商品价格,单位元',
`sku_name` STRING COMMENT '商品名称,展示用',
-- 其他商品属性字段
`category3_id` STRING COMMENT '三级分类ID,最细粒度的商品分类',
`category3_name` STRING COMMENT '三级分类名称,如"休闲男鞋"',
`category2_id` STRING COMMENT '二级分类ID,中间层级商品分类',
`category2_name` STRING COMMENT '二级分类名称,如"男鞋"',
`category1_id` STRING COMMENT '一级分类ID,顶层商品分类',
`category1_name` STRING COMMENT '一级分类名称,如"鞋靴"',
`tm_id` STRING COMMENT '品牌ID,品牌唯一标识',
`tm_name` STRING COMMENT '品牌名称,如"Nike"、"Adidas"',
-- 扩展属性字段
`attr_ids` ARRAY<int(11)> COMMENT '平台属性ID集合',
`sale_attr_ids` ARRAY<int(11)> COMMENT '销售属性ID集合',
`create_time` STRING COMMENT '创建时间,商品首次录入时间'
)
ENGINE=OLAP
UNIQUE KEY(`id`,`k1`) -- 使用商品ID和日期作为联合主键
PARTITION BY RANGE(`k1`) () -- 按日期范围分区
DISTRIBUTED BY HASH(`id`) -- 按商品ID哈希分布
设计特点分析
-
宽表设计:整合了商品所有相关维度信息(SKU基本信息、分类信息、品牌信息)
-
多级分类:保留一、二、三级分类的ID和名称,便于不同粒度的分析
-
分区策略:按日期分区,支持历史版本管理和数据生命周期管理
-
复合主键:通过商品ID和日期组合,确保每天每个商品只有一个状态
3.2.3.2 用户维度拉链表实现
1.表结构设计
对于变化较为频繁且需要历史追溯的维度,如用户信息,我们采用了拉链表设计。
CREATE TABLE dim.dim_user_zip
(
`id` VARCHAR(64) COMMENT '用户ID,用户唯一标识',
`k1` DATE NOT NULL COMMENT '分区字段,数据日期',
`login_name` STRING COMMENT '用户登录名,账号名称',
`nick_name` STRING COMMENT '用户昵称,用户自定义展示名',
`name` STRING COMMENT '用户真实姓名,已加密',
-- 其他用户属性字段
`start_date` STRING COMMENT '开始日期,当前版本生效开始日期',
`end_date` STRING COMMENT '结束日期,当前版本失效日期,9999-12-31表示当前有效版本'
)
2.拉链表处理逻辑
拉链表的核心在于ETL处理逻辑,需要处理两种情况:初始化和增量更新。
2.1 初始化逻辑
-- 用户维度拉链表初始化插入(设置历史起点)
insert into dim.dim_user_zip(id, k1, login_name, ... , start_date, end_date)
select
id, -- 用户ID
k1, -- 分区日期
login_name, -- 用户登录名
-- 其他字段
md5(name), -- 用户真实姓名(MD5加密处理)
md5(phone_num), -- 手机号码(MD5加密处理)
md5(email), -- 电子邮箱(MD5加密处理)
-- 其他字段
'2020-06-14' start_date, -- 拉链起始日期(业务起始日期)
'9999-12-31' end_date -- 拉链结束日期(当前有效版本标记)
from ods.ods_user_info_full;
2.2 增量更新逻辑
拉链表的增量更新是其核心价值所在,我们来看实际项目中的逻辑代码:
insert into dim.dim_user_zip(id, k1, login_name, ... , start_date, end_date)
with
tmp as
(
select
-- 当前拉链表最新有效数据
old.id old_id, -- 原用户ID
-- 其他old字段
old.end_date old_end_date, -- 原版本结束日期
-- 新增数据(当日变化)
new.id new_id, -- 新用户ID
-- 其他new字段
new.end_date new_end_date -- 新版本结束日期
from
-- 查询当前拉链表中最新有效记录
(
select * from dim.dim_user_zip
where end_date = '9999-12-31' -- 筛选当前有效版本
) old
-- 使用FULL OUTER JOIN合并新旧数据
full outer join
-- 当日增量数据处理
(
select
cast(t1.id as VARCHAR(64)) as id, -- 转换用户ID类型
-- 其他字段处理
md5(t1.name) as name, -- 敏感信息加密
-- 其他字段
'2024-06-15' as start_date, -- 新版本开始日期
'9999-12-31' as end_date -- 新版本结束日期
from
(-- 取每个用户最新记录
select id, k1, ... , row_number() over (partition by id order by create_time desc) rn
from ods.ods_user_info_full
) t1
where rn=1 -- 只取每个用户的最新记录
) new
on old.id=new.id -- 按用户ID关联
)
-- 查询1:处理有变更的用户数据,生成新版本记录
select
if(new_id is not null, new_id, old_id), -- 用户ID
-- 其他字段选择逻辑
if(new_id is not null, new_start_date, old_start_date), -- 版本开始日期
if(new_id is not null, new_end_date, old_end_date) -- 版本结束日期
from tmp
where k2 is not NULL -- 只处理有新分区数据的记录
union all
-- 查询2:处理原记录的历史版本化,更新结束日期
select
old_id, -- 用户ID
-- 其他原字段
old_start_date, -- 版本开始日期
cast(date_add(date('${pdate}'), -1) as string) old_end_date -- 更新版本结束日期为当前日期前一天
from tmp
where k1 is not NULL -- 有原分区数据
and old_id is not null -- 有原用户ID
and new_id is not null; -- 同时有新用户ID,说明是变更记录
3.拉链表更新原理解析
拉链表的核心在于ETL拉链表更新的核心原理在于:保留当前记录
-
将原有记录的结束日期修改为变更前一天插入新版本
-
创建新记录,开始日期为变更当天,结束日期为'9999-12-31'新增用户处理
-
对于新增的用户,直接插入记录,无需处理历史版本使用FULL JOIN
-
确保既能处理变更用户,也能处理新增用户
3.3 DWS层建设指南
3.3.1 设计思路
3.3.1.1 主题划分
主题是DWS层最重要的设计要素,需要从以下几个方面考虑:
1.业务维度划分
-
交易域:订单、支付、退款等
-
用户域:注册、登录、画像等
-
流量域:访问、浏览、跳转等
-
商品域:商品、类目、品牌等
2.分析维度考虑
-
时间维度:日、周、月、年
-
地理维度:省份、城市、区域
-
用户维度:设备、渠道、等级
-
业务维度:品类、品牌、店铺
3.3.1.2 粒度设计
1.基础粒度
-
用户粒度:用户行为分析
-
商品粒度:商品销售分析
-
店铺粒度:店铺运营分析
-
订单粒度:交易过程分析
2.时间粒度
-
1d:最近1日汇总,日常监控
-
nd:最近n日汇总,趋势分析
-
td:历史至今汇总,累计分析
3.3.1.3 指标体系
1.指标类型
-
统计指标:数量、金额等
-
比率指标:占比、转化率等
-
环比指标:增长率、变化率等
-
复合指标:加权分数、综合评分等
2.计算方式
-
累计值:SUM、COUNT等
-
去重值:COUNT DISTINCT等
-
最新值:MAX、LAST_VALUE等
-
平均值:AVG、MEDIAN等
3.3.2 实战案例
1.业务场景
分析用户购物行为,包括:
-
商品购买情况
-
用户消费习惯
-
品类偏好分析
-
支付方式分析
2.表设计方案
-- 用户商品交易行为汇总表(最近N日)
CREATE TABLE dws.dws_trade_user_sku_order_nd (
user_id STRING COMMENT '用户ID',
sku_id STRING COMMENT '商品ID',
k1 DATE COMMENT '数据日期',
-- 商品维度冗余
sku_name STRING COMMENT '商品名称',
category1_id STRING COMMENT '一级品类ID',
category1_name STRING COMMENT '一级品类名称',
tm_id STRING COMMENT '品牌ID',
tm_name STRING COMMENT '品牌名称',
-- 7日汇总指标
order_count_7d BIGINT COMMENT '7日下单次数',
order_num_7d BIGINT COMMENT '7日购买件数',
order_amount_7d DECIMAL(16,2) COMMENT '7日下单金额',
-- 30日汇总指标
order_count_30d BIGINT COMMENT '30日下单次数',
order_num_30d BIGINT COMMENT '30日购买件数',
order_amount_30d DECIMAL(16,2) COMMENT '30日下单金额'
) COMMENT '交易域用户商品粒度订单最近N日汇总表'
PARTITION BY k1;
3.实现方案
1.维度关联
-- 1. 关联商品维度信息
SELECT
od.user_id, od.sku_id, od.k1,
-- 2. 冗余维度属性
COALESCE(sku.sku_name, '未知商品') as sku_name,
COALESCE(sku.category1_id, '-1') as category1_id,
COALESCE(sku.category1_name, '未知品类') as category1_name,
COALESCE(sku.tm_id, '-1') as tm_id,
COALESCE(sku.tm_name, '未知品牌') as tm_name,
-- 3. 统计指标
od.order_count_1d,
od.order_num_1d,
od.order_amount_1d
FROM order_detail od
LEFT JOIN dim.dim_sku_full sku
ON od.sku_id = sku.id
AND sku.k1 = (
SELECT MAX(k1)
FROM dim.dim_sku_full
WHERE k1 <= DATE('${pdate}')
);
2.指标计算
-- 1. 计算汇总指标
SELECT
user_id, sku_id, k1,
sku_name, category1_id, category1_name,
tm_id, tm_name,
-- 2. 计算7日累计
SUM(IF(k1 >= DATE_ADD(DATE('${pdate}'), -6),
order_count_1d, 0)) AS order_count_7d,
SUM(IF(k1 >= DATE_ADD(DATE('${pdate}'), -6),
order_num_1d, 0)) AS order_num_7d,
SUM(IF(k1 >= DATE_ADD(DATE('${pdate}'), -6),
order_amount_1d, 0)) AS order_amount_7d,
-- 3. 计算30日累计
SUM(order_count_1d) AS order_count_30d,
SUM(order_num_1d) AS order_num_30d,
SUM(order_amount_1d) AS order_amount_30d
FROM dws.dws_trade_user_sku_order_1d
WHERE k1 >= DATE_ADD(DATE('${pdate}'), -29)
GROUP BY
user_id, sku_id, k1,
sku_name, category1_id, category1_name,
tm_id, tm_name;
3.4 ADS层建设指南
3.4.1 建设思路
1.设计原则
-
业务导向:ADS层设计应以业务需求为核心,确保数据能够直接支持业务决策和运营分析。
-
性能优化:针对高频查询场景进行优化,包括合理的分区策略、索引设计和物化视图。
-
数据一致性:确保数据口径统一,避免不同报表之间的数据不一致。
-
可扩展性:设计时考虑未来业务扩展需求,预留足够的扩展空间。
-
易用性:提供简单直观的数据结构,降低业务人员使用门槛。
2.数据模型设计
ADS层通常采用星型模型或雪花模型,主要包含以下几类表:
-
汇总事实表:按不同维度(时间、地区、产品等)汇总的事实数据。
-
维度表:包含业务实体的属性信息,如客户、产品、地区等。
-
指标表:存储预计算的业务指标,如转化率、留存率等。
-
报表表:直接面向报表展示的宽表,包含多个维度的指标。
3.数据更新策略
-
增量更新:对于大表,采用增量更新策略,只处理新增或变更的数据。
-
全量刷新:对于小表或需要保证数据一致性的场景,采用全量刷新策略。
-
定时调度:根据业务需求设置合理的调度周期,如每日、每周或每月。
3.4.2 实战案例
1.业务背景
电商平台需要对用户进行价值分层,以便进行精准营销和个性化服务。基于RFM模型(Recency、Frequency、Monetary)对用户进行价值评估,并计算用户的生命周期价值,为运营决策提供数据支持。
2.数据来源
-
DWS层:用户交易宽表(dws_trade_user_order_td)和用户登录宽表(dws_user_user_login_td)
-
ADS层:历史用户价值分析数据(用于计算价值发展趋势)
3.实现方案
3.1 建表(ads.ads_user_value_analysis)
-- 用户价值分析表
CREATE TABLE IF NOT EXISTS ads.ads_user_value_analysis
(
dt DATE COMMENT '统计日期',
user_id BIGINT COMMENT '用户ID',
order_count_td BIGINT COMMENT '累计下单次数',
order_amount_td DECIMAL(20,2) COMMENT '累计下单金额',
order_last_date DATE COMMENT '最近下单日期',
order_first_date DATE COMMENT '首次下单日期',
login_count_td BIGINT COMMENT '累计登录次数',
login_last_date DATE COMMENT '最近登录日期',
average_order_amount DECIMAL(20,2) COMMENT '平均客单价',
purchase_cycle_days INT COMMENT '平均购买周期(天)',
account_days INT COMMENT '账号存续天数',
life_time_value DECIMAL(20,2) COMMENT '生命周期价值(LTV)',
recency_score INT COMMENT '最近活跃度评分(R)',
frequency_score INT COMMENT '活动频次评分(F)',
monetary_score INT COMMENT '消费金额评分(M)',
rfm_score INT COMMENT 'RFM总分',
user_value_level STRING COMMENT '用户价值分层',
active_status STRING COMMENT '活跃状态',
life_cycle_status STRING COMMENT '生命周期状态',
shopping_preference STRING COMMENT '购物偏好',
growth_trend STRING COMMENT '价值发展趋势'
)
COMMENT '用户价值分层分析报表,基于RFM模型计算用户价值'
PARTITIONED BY (dt STRING)
STORED AS ORC
TBLPROPERTIES ('orc.compress'='SNAPPY');
3.2 核心指标计算
RFM模型评分
-
Recency(最近购买时间):根据最近一次购买距今的天数评分(1-5分)
-
Frequency(购买频率):根据累计购买次数评分(1-5分)
-
Monetary(消费金额):根据累计消费金额评分(1-5分)
用户价值分层
-
高价值:RFM总分≥13分
-
中高价值:RFM总分10-12分
-
中价值:RFM总分7-9分
-
低价值:RFM总分4-6分
-
流失风险:RFM总分≤3分
生命周期价值(LTV)
-
计算公式:平均客单价 × 年购买频率 × 预期客户生命周期(年)
-
年购买频率:总购买次数×365/账号存续天数
用户状态分类
-
活跃状态:基于最近交易和登录时间
-
生命周期状态:基于交易行为和订单历史
-
购物偏好:基于购买频率和金额
-
价值发展趋势:比较当前RFM评分与30天前的评分
3.3 具体逻辑实现
INSERT INTO ads.ads_user_value_analysis
(dt, user_id, order_count_td, order_amount_td, order_last_date, order_first_date,
login_count_td, login_last_date, average_order_amount, purchase_cycle_days, account_days,
life_time_value, recency_score, frequency_score, monetary_score, rfm_score,
user_value_level, active_status, life_cycle_status, shopping_preference, growth_trend)
SELECT
-- 基础日期统计
date('${pdate}') AS dt, -- 统计日期,使用调度日期参数
t1.user_id AS user_id, -- 用户ID
t1.order_count_td, -- 累计下单次数,来自交易宽表
t1.total_amount_td, -- 累计下单金额,来自交易宽表
-- 格式化日期为yyyy-MM-dd格式
date_format(t1.order_last_date, '%Y-%m-%d') AS order_last_date, -- 最近下单日期
date_format(t1.order_first_date, '%Y-%m-%d') AS order_first_date, -- 首次下单日期
t2.login_count_td, -- 累计登录次数,来自用户登录宽表
date_format(t2.login_last_date, '%Y-%m-%d') AS login_last_date, -- 最近登录日期
-- 计算衍生指标
-- 计算平均客单价 = 总金额/订单数
CASE WHEN t1.order_count_td > 0 THEN t1.total_amount_td/t1.order_count_td ELSE 0 END AS average_order_amount,
-- 计算平均购买周期(天) = (最后订单日期-首次订单日期)/(订单数-1)
CASE WHEN t1.order_count_td > 1
THEN datediff(t1.order_last_date, t1.order_first_date)/(t1.order_count_td-1)
ELSE NULL END AS purchase_cycle_days,
-- 计算账号存续天数 = 当前日期-注册日期
datediff(current_date(), t2.register_date) AS account_days,
-- 计算生命周期价值(LTV) = 平均客单价 * 年购买频率 * 预期客户生命周期(年)
-- 年购买频率计算方式: 总购买次数*365/账号存续天数,即年化购买频率
-- 预期客户生命周期取3年作为默认预估
CASE WHEN t1.order_count_td > 0 AND datediff(current_date(), t2.register_date) > 0
THEN (t1.total_amount_td/t1.order_count_td) * (t1.order_count_td*365/datediff(current_date(), t2.register_date)) * 3
ELSE 0 END AS life_time_value,
-- RFM模型计算 - 为每个维度打分(1-5分)
-- Recency(最近购买时间)评分: 越近分数越高
CASE
WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5 -- 30天内
WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4 -- 31-60天
WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3 -- 61-90天
WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2 -- 91-180天
ELSE 1 -- 180天以上
END AS recency_score,
-- Frequency(购买频率)评分: 购买次数越多分数越高
CASE
WHEN t1.order_count_td >= 20 THEN 5 -- 20次及以上
WHEN t1.order_count_td >= 10 THEN 4 -- 10-19次
WHEN t1.order_count_td >= 5 THEN 3 -- 5-9次
WHEN t1.order_count_td >= 2 THEN 2 -- 2-4次
ELSE 1 -- 1次
END AS frequency_score,
-- Monetary(消费金额)评分: 总消费金额越高分数越高
CASE
WHEN t1.total_amount_td >= 10000 THEN 5 -- 1万元及以上
WHEN t1.total_amount_td >= 5000 THEN 4 -- 5千-1万元
WHEN t1.total_amount_td >= 2000 THEN 3 -- 2千-5千元
WHEN t1.total_amount_td >= 500 THEN 2 -- 500-2千元
ELSE 1 -- 500元以下
END AS monetary_score,
-- 计算RFM总分 = R分 + F分 + M分
(CASE
WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
ELSE 1
END) +
(CASE
WHEN t1.order_count_td >= 20 THEN 5
WHEN t1.order_count_td >= 10 THEN 4
WHEN t1.order_count_td >= 5 THEN 3
WHEN t1.order_count_td >= 2 THEN 2
ELSE 1
END) +
(CASE
WHEN t1.total_amount_td >= 10000 THEN 5
WHEN t1.total_amount_td >= 5000 THEN 4
WHEN t1.total_amount_td >= 2000 THEN 3
WHEN t1.total_amount_td >= 500 THEN 2
ELSE 1
END) AS rfm_score,
-- 用户价值分层: 根据RFM总分(3-15分)进行分层
CASE
WHEN (CASE
WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
ELSE 1
END +
CASE
WHEN t1.order_count_td >= 20 THEN 5
WHEN t1.order_count_td >= 10 THEN 4
WHEN t1.order_count_td >= 5 THEN 3
WHEN t1.order_count_td >= 2 THEN 2
ELSE 1
END +
CASE
WHEN t1.total_amount_td >= 10000 THEN 5
WHEN t1.total_amount_td >= 5000 THEN 4
WHEN t1.total_amount_td >= 2000 THEN 3
WHEN t1.total_amount_td >= 500 THEN 2
ELSE 1
END) >= 13 THEN '高价值' -- 13-15分
WHEN (CASE
WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
ELSE 1
END +
CASE
WHEN t1.order_count_td >= 20 THEN 5
WHEN t1.order_count_td >= 10 THEN 4
WHEN t1.order_count_td >= 5 THEN 3
WHEN t1.order_count_td >= 2 THEN 2
ELSE 1
END +
CASE
WHEN t1.total_amount_td >= 10000 THEN 5
WHEN t1.total_amount_td >= 5000 THEN 4
WHEN t1.total_amount_td >= 2000 THEN 3
WHEN t1.total_amount_td >= 500 THEN 2
ELSE 1
END) >= 10 THEN '中高价值' -- 10-12分
WHEN (CASE
WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
ELSE 1
END +
CASE
WHEN t1.order_count_td >= 20 THEN 5
WHEN t1.order_count_td >= 10 THEN 4
WHEN t1.order_count_td >= 5 THEN 3
WHEN t1.order_count_td >= 2 THEN 2
ELSE 1
END +
CASE
WHEN t1.total_amount_td >= 10000 THEN 5
WHEN t1.total_amount_td >= 5000 THEN 4
WHEN t1.total_amount_td >= 2000 THEN 3
WHEN t1.total_amount_td >= 500 THEN 2
ELSE 1
END) >= 7 THEN '中价值' -- 7-9分
WHEN (CASE
WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
ELSE 1
END +
CASE
WHEN t1.order_count_td >= 20 THEN 5
WHEN t1.order_count_td >= 10 THEN 4
WHEN t1.order_count_td >= 5 THEN 3
WHEN t1.order_count_td >= 2 THEN 2
ELSE 1
END +
CASE
WHEN t1.total_amount_td >= 10000 THEN 5
WHEN t1.total_amount_td >= 5000 THEN 4
WHEN t1.total_amount_td >= 2000 THEN 3
WHEN t1.total_amount_td >= 500 THEN 2
ELSE 1
END) >= 4 THEN '低价值' -- 4-6分
ELSE '流失风险' -- 3分
END AS user_value_level,
-- 活跃状态: 基于最近交易和登录时间
CASE
WHEN datediff(current_date(), t1.order_last_date) <= 30 OR datediff(current_date(), t2.login_last_date) <= 7 THEN '活跃' -- 30天内有交易或7天内有登录
WHEN datediff(current_date(), t1.order_last_date) <= 90 OR datediff(current_date(), t2.login_last_date) <= 30 THEN '沉默' -- 90天内有交易或30天内有登录
ELSE '流失' -- 超过90天未交易且超过30天未登录
END AS active_status,
-- 生命周期状态: 基于交易行为和订单历史
CASE
WHEN datediff(current_date(), t1.order_first_date) <= 30 AND t1.order_count_td <= 2 THEN '新用户' -- 30天内首次购买且购买次数<=2次
WHEN t1.order_count_td >= 3 AND (CASE
WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
ELSE 1
END) >= 4 THEN '成长期' -- 购买>=3次且近期活跃(60天内)
WHEN t1.order_count_td >= 5 AND (CASE
WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
ELSE 1
END) >= 3 THEN '成熟期' -- 购买>=5次且90天内有购买
WHEN (CASE
WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
ELSE 1
END) <= 2 AND (CASE
WHEN t1.order_count_td >= 20 THEN 5
WHEN t1.order_count_td >= 10 THEN 4
WHEN t1.order_count_td >= 5 THEN 3
WHEN t1.order_count_td >= 2 THEN 2
ELSE 1
END) >= 3 THEN '衰退期' -- 购买次数>=5但超过90天未购买
WHEN (CASE
WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
ELSE 1
END) >= 3 AND datediff(t1.order_last_date, t0.lag_order_date) > 90 THEN '回流' -- 最近90天内有购买但之前超过90天未购买
ELSE '新用户' -- 默认为新用户
END AS life_cycle_status,
-- 购物偏好: 基于购买频率和金额
CASE
WHEN (CASE
WHEN t1.order_count_td >= 20 THEN 5
WHEN t1.order_count_td >= 10 THEN 4
WHEN t1.order_count_td >= 5 THEN 3
WHEN t1.order_count_td >= 2 THEN 2
ELSE 1
END) >= 4 AND (CASE
WHEN t1.total_amount_td >= 10000 THEN 5
WHEN t1.total_amount_td >= 5000 THEN 4
WHEN t1.total_amount_td >= 2000 THEN 3
WHEN t1.total_amount_td >= 500 THEN 2
ELSE 1
END) <= 3 THEN '高频低额' -- 高频率低金额: 购买频繁但单价较低
WHEN (CASE
WHEN t1.order_count_td >= 20 THEN 5
WHEN t1.order_count_td >= 10 THEN 4
WHEN t1.order_count_td >= 5 THEN 3
WHEN t1.order_count_td >= 2 THEN 2
ELSE 1
END) <= 3 AND (CASE
WHEN t1.total_amount_td >= 10000 THEN 5
WHEN t1.total_amount_td >= 5000 THEN 4
WHEN t1.total_amount_td >= 2000 THEN 3
WHEN t1.total_amount_td >= 500 THEN 2
ELSE 1
END) >= 4 THEN '低频高额' -- 低频率高金额: 购买较少但大额消费
WHEN (CASE
WHEN t1.order_count_td >= 20 THEN 5
WHEN t1.order_count_td >= 10 THEN 4
WHEN t1.order_count_td >= 5 THEN 3
WHEN t1.order_count_td >= 2 THEN 2
ELSE 1
END) >= 4 AND (CASE
WHEN t1.total_amount_td >= 10000 THEN 5
WHEN t1.total_amount_td >= 5000 THEN 4
WHEN t1.total_amount_td >= 2000 THEN 3
WHEN t1.total_amount_td >= 500 THEN 2
ELSE 1
END) >= 4 THEN '高频高额' -- 高频率高金额: 高价值客户,频繁且大额
ELSE '低频低额' -- 低频率低金额: 低价值客户
END AS shopping_preference,
-- 价值发展趋势: 比较当前RFM评分与30天前的评分
CASE
WHEN (CASE
WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
ELSE 1
END +
CASE
WHEN t1.order_count_td >= 20 THEN 5
WHEN t1.order_count_td >= 10 THEN 4
WHEN t1.order_count_td >= 5 THEN 3
WHEN t1.order_count_td >= 2 THEN 2
ELSE 1
END +
CASE
WHEN t1.total_amount_td >= 10000 THEN 5
WHEN t1.total_amount_td >= 5000 THEN 4
WHEN t1.total_amount_td >= 2000 THEN 3
WHEN t1.total_amount_td >= 500 THEN 2
ELSE 1
END) > COALESCE(t3.previous_rfm_score, 0) THEN '上升' -- 当前分数高于30天前,趋势上升
WHEN (CASE
WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
ELSE 1
END +
CASE
WHEN t1.order_count_td >= 20 THEN 5
WHEN t1.order_count_td >= 10 THEN 4
WHEN t1.order_count_td >= 5 THEN 3
WHEN t1.order_count_td >= 2 THEN 2
ELSE 1
END +
CASE
WHEN t1.total_amount_td >= 10000 THEN 5
WHEN t1.total_amount_td >= 5000 THEN 4
WHEN t1.total_amount_td >= 2000 THEN 3
WHEN t1.total_amount_td >= 500 THEN 2
ELSE 1
END) < COALESCE(t3.previous_rfm_score, 0) THEN '下降' -- 当前分数低于30天前,趋势下降
ELSE '稳定' -- 分数相等,趋势稳定
END AS growth_trend
FROM
(
-- 订单数据: 获取用户交易相关信息
SELECT
user_id,
k1,
order_date_last,
LAG(order_date_last, 1, NULL) OVER(PARTITION BY user_id ORDER BY k1) AS lag_order_date -- 获取上一次最近下单日期,用于计算回流状态
FROM dws.dws_trade_user_order_td
WHERE k1 = date('${pdate}') -- 取当天分区数据
) t0
JOIN
(
-- 订单数据: 获取用户交易相关信息
SELECT
user_id,
SUM(order_count_td) AS order_count_td, -- 累计下单次数
SUM(total_amount_td) AS total_amount_td, -- 累计下单金额
MAX(order_date_last) AS order_last_date, -- 最近下单日期
MIN(order_date_first) AS order_first_date -- 首次下单日期
FROM dws.dws_trade_user_order_td
WHERE k1 = date('${pdate}') -- 取当天分区数据
GROUP BY user_id
) t1
ON t0.user_id = t1.user_id
JOIN
(
-- 登录数据: 获取用户登录相关信息
SELECT
user_id,
SUM(login_count_td) AS login_count_td, -- 累计登录次数
MAX(login_date_last) AS login_last_date, -- 最近登录日期
date('2020-01-01') AS register_date -- 注册日期,使用默认值
FROM dws.dws_user_user_login_td
WHERE k1 = date('${pdate}') -- 取当天分区数据
GROUP BY user_id
) t2
ON t1.user_id = t2.user_id
LEFT JOIN
(
-- 上月RFM评分数据: 用于计算价值发展趋势
SELECT
user_id,
recency_score + frequency_score + monetary_score AS previous_rfm_score -- 30天前的RFM总分
FROM ads.ads_user_value_analysis
WHERE dt = date_sub(date('${pdate}'), 30) -- 取30天前的数据
) t3
ON t1.user_id = t3.user_id;
总结:ADS(Application Data Store)层是数据仓库的最顶层,直接面向业务应用。其建设核心是以业务需求为导向,将经过清洗、转换和汇总的数据以业务友好的方式呈现。ADS层设计应遵循业务导向、性能优化、数据一致性、可扩展性和易用性五大原则。主要包含汇总事实表、维度表、指标表和报表表等数据模型。数据更新策略包括增量更新和全量刷新,需根据业务场景选择。ADS层实现需要高性能存储引擎(如Doris)和计算框架,并建立完善的数据质量控制机制。通过ADS层,企业可以实现精准营销、会员运营、风险控制、客户服务和产品优化等业务目标,为决策提供数据支持。
四、决策建议
通过以下问题判断是否需要分层:
-
数据规模:是否超过单机处理能力?
-
团队规模:是否有专职数据工程师负责模型设计?
-
业务复杂度:是否需要支持多个部门或业务线的多样化需求?
-
长期规划:是否预期未来3年数据量或业务复杂度会显著增长?
结论:
-
短期/简单场景:可不分层,但需预留扩展性。
-
中长期/复杂场景:分层是必选项,可从小规模开始逐步迭代。
最终,是否分层是一个权衡ROI(投资回报率)的问题。建议从最小化可行方案出发,随着业务发展逐步演进架构。
4.1 不同情况建设建议
不同情况特点 | 说明 |
时间紧任务重 | 若时间紧任务重,一周就要看结果,那就别说了,直接连业务数据库是最合适的。要啥分层?没那个时间。 |
业务简单+数据源少且简单 | 如果说公司业务简单,且相对比较固定,数据来源不多,结构也很清晰,需求也不多,可以ODS+DWD+DWS,三层足矣。 ODS起到解耦业务数据库+异构数据源的问题,DWD解决数据脏乱差的问题,DWS直接面向前台业务需求。够了。 |
业务一般复杂+公司战略变化不大 | 如果说公司业务一般复杂,每年都要跟着战略变,那就中规中矩的设计4层,多一层DWB层做汇总,多一层解耦,业务变化的时候,我们只改DWS层就好了,最多穿透到DWB层。每年按照战略调整一次,工作量也不会太大,最重要的是能保证底层结构的稳定和数据分析的可持续性。 |
业务多非常复杂 | 如果说公司业务非常复杂,业务线众多,那就在4层基础上加一层DM,每条业务线一个单独的DM。如果是集团型的,DM还可以设置在汇总层下面。放在那里,取决于组织结构。 |
业务变化频繁 | 如果说公司业务变化非常频繁,仨月变一次业务方向,后台数据每天都改数据库,说实话,没啥好办法。要么用人力堆,要么提取相对比较固定的内容去建设数仓,变化太快的直接做固定报表吧。前后都变,中间的没法干活,怎么解耦都没用。谁有好办法,可以告诉我,我去学习一下。 |
其他注意: ●至于DIM层,不管哪一种方式,都需要。 ●互联网模式加一层标签层。 ●大数据环境可以用宽表层替换DWS层(其实都一样)。 |
五、数仓分层示例参考
5.1 三层数仓架构(方案一)
3层数据仓库建设的架构一般指的是ODS、DW(数仓)、DM(数据集市),其中DW和DM又可以再拆成N层。不过我没有找到大厂的这种架构分享,大抵是因为这么各大厂不屑于画这么简单的分层架构图吧。
5.2 三层数仓架构(方案二)
另外一种3层数仓是ODS+DWD+DWS,这样就能满足解耦业务数据库、数据标准化+统一化和面向应用等基础功能。同样也没有找到大厂的3层分享案例。
5.3 美团大交通4层实时数仓架构
特意放上实时数仓的架构图,就是想说明一下无论是实时数仓还是离线数仓,架构都是一样的,该分几层分几层。只不过实时数仓用的是Kafka等MQ作为实时存储介质。
5.4 搜狐5层数据仓库架构
这是搜狐的5层数据仓库架构。之所以放搜狐的案例,是因为这里有一个STG层。这边把ODS细分为STG和ODS。STG是数据缓冲层,相当于贴源层,就是跟业务系统保持一致的结构,而这个架构中的ODS是经过简单清洗的明细数据。
5.5 美团酒旅6层数仓架构
这是美团酒旅业务的数仓架构,业务足够复杂,所以分成6层了。以第3代为例,ODS、数据整合层、多维明细层、汇总层、主题层、应用层。每一层的目的非常清晰。
-
ODS:汇聚原始数据;
-
数据整合层:对数据进行清洗、筛选、整合等操作;
-
多维明细层:进行维度建模;
-
汇总层:进行各级汇总;
-
主题层:按照业务领域,切分主题域,提供面向业务主题的数据集;
-
应用层:面向前端应用汇聚数据。
另外,美团的这张图,也体现了架构的另外一个重点:架构是需要不断的优化调整的,不能超前太多,也不能脱离业务。按照Inmon和Kimball吵了十几年的经验上看,建议架构设计时,按超越当前实际情况1~1.5年的设计是比较合适的。超越太长会导致建设期过长或者条件不成熟而失败,太短则修改太过频繁。
六、总结
数据仓库分层的核心逻辑是解耦。需要在有限时间、资源等条件下满足业务需求,同时又要兼顾业务的快速变化。所以我们作为数据架构师,需要兼顾业务的复杂变化,以及开发的复杂度和可维护性,在两者之间做一个平衡和取舍。
至于分几层,建议按照目前的业务和建设现状,进行合理解构和分层设计,一般刚开始做,建议3、4层。规划1-1.5年的架构,然后不断的建设、优化、再优化。不断逼近满足所有需求。