生而为人

程序员的自我修养

0%

离线数仓

[toc]

简历项目描述

美团外卖离线数仓核心域重构与性能优化项目

项目周期:2024.06-2025.03

技术栈:Spark 3.3(离线批计算)、Hive 3.1(离线数仓存储)、DolphinScheduler(任务调度)、Superset(可视化)、ZSTD(数据压缩)

项目背景:美团外卖日均订单量超 6000 万,用户行为日志日均增量超 10 亿条。原有离线数仓经过多年迭代,存在三大核心痛点:

  1. 架构混乱,用户、订单两大核心域表设计冗余,数据链路长达 7 层,数据复用率不足 30%,维护成本极高
  2. 指标口径严重不统一,80 + 个核心业务指标存在多版本定义,业务部门数据信任度低,数据团队日均需花费 30% 时间排查口径差异
  3. 核心报表产出延迟高、稳定性差,核心订单日报表产出时间长达 T+2 小时,大促期间经常延迟至 T+4 小时,核心链路任务失败率达 12%,无法支撑次日运营复盘

核心职责

  1. 独立负责用户与订单两大核心域离线数仓重构:重新设计星型数据模型,完成订单明细事实表、用户行为事实表及 12 张核心维度表的全量重构,精简冗余链路,实现数据复用最大化
  2. 牵头统一全业务指标口径:梳理订单、用户域全量业务指标,制定统一的指标定义、计算逻辑与命名规范,完成 80 + 个核心业务指标的口径对齐与落地
  3. 全链路性能与稳定性优化:通过头部商家数据倾斜治理、动态分区裁剪、低效算子重写、资源精细化调度等手段,深度优化核心订单报表的计算链路
  4. 建立核心域数据质量与运维体系:设计覆盖完整性、准确性、一致性的多层数据质量校验规则,完善任务告警与故障恢复流程,实现数据异常自动检测与快速定位

核心成果

  1. 数仓架构全面升级:完成用户、订单核心域重构,数据链路从 7 层精简至 4 层,数据复用率从 30% 提升至 75%,核心域维护成本降低 50%
  2. 指标口径彻底统一:完成 80 + 个核心业务指标的口径统一,彻底解决了多版本数据不一致的问题,数据团队口径排查工作量下降 90%
  3. 性能与稳定性大幅提升:核心订单日报表产出时间从T+2 小时缩短至 T+45 分钟,大促期间产出稳定在 T+1 小时以内;核心链路任务失败率从 12% 下降至4.2%,降幅达 65%
  4. 业务价值突出:核心报表产出效率提升 62.5%,运营团队次日复盘时间提前 2 小时以上;统一可靠的数据支撑助力运营活动转化率提升 7%,为后续业务快速迭代奠定了坚实的数据基础

项目总结

细化项目列表

  1. 美团数据指标平台,统一数据口径
  2. 美团实时仓库、监控平台
  3. 美团模型实时指标平台
  4. 美团数仓升级
  5. 微软通用化数据处理架构
  6. 微软数据引擎迁移
  7. 微软透传schema方案设计

细化点:业务描述,解决的问题,设计方案,遇到的难点

项目描述

设计细节

难点

美团数据指标平台建设

项目描述

设计细节

难点

美团数仓

外卖数仓 2.0

美团外卖数仓 2.0 五层架构详解

ODS/IDL/CDL/MDL/ADL 是美团外卖数仓 2.0 时代(2016-2019 年)确立的标准分层架构,遵循 “数据从原始到加工、从通用到专用” 的演进逻辑,每一层都有明确的职责边界和数据流转方向。这套分层解决了 1.0 时代烟囱式开发、数据口径混乱的问题,但也为后续 3.0 升级埋下了痛点。

一、ODS 层(Operational Data Store,操作数据存储层)

核心定义

数仓的最底层、数据入口层,直接对接业务系统的原始数据,是所有数据加工的源头。

核心作用
  • 完整保留业务系统的原始数据,不做任何业务逻辑加工
  • 作为数据的 “备份库”,支持数据回溯和问题排查
  • 屏蔽底层业务系统的技术差异,为上层提供统一的数据接入接口
数据来源
  • 业务数据库 Binlog:订单库、用户库、商家库、支付库等 MySQL 数据库的增量变更日志
  • 用户行为日志:App 点击、浏览、下单、支付等用户操作日志
  • 第三方接口数据:地图数据、支付渠道数据、第三方配送数据等
  • 其他数据:爬虫数据、人工录入数据、系统监控数据等
数据特点
  • 结构完全一致:和源系统的表结构、字段类型、数据格式完全相同
  • 数据量最大:保留全量历史数据,是数仓中存储量最大的一层
  • 包含脏数据:未经过清洗,包含空值、异常值、重复数据和冗余数据
  • 按时间分区:通常按天分区存储,部分日志数据按小时分区
典型表示例
  • ods_order_binlog_di:订单表的 Binlog 增量数据(di=day incremental)
  • ods_user_click_log_hh:用户点击日志(hh=hourly)
  • ods_merchant_info_df:商家信息表全量快照(df=day full)
  • ods_payment_record_di:支付记录增量数据
2.0 时代技术实现与痛点
  • 技术实现:Sqoop 全量同步 + 增量同步业务数据库,Flume 采集日志到 HDFS,Canal 采集 Binlog 到 Kafka 再落地 Hive
  • 核心痛点:
    • 全量同步效率极低,大表(如订单表)同步需要 2-3 小时
    • 数据延迟高,T+1 才能拿到前一天的完整数据
    • 没有统一的接入标准,不同业务系统的数据格式混乱
3.0 时代改造
  • 引入流式数据集成架构,Flink 实时消费 Kafka 数据并写入 Hive
  • 统一数据接入标准,自动生成 ODS 表结构和同步任务
  • 基于 Hudi/Beluga 引擎实现增量数据的实时合并,替代全量快照

二、IDL 层(Integration Data Layer,数据集成层)

核心定义

也叫数据清洗层 / 标准化层,是对 ODS 层数据进行清洗、过滤、标准化后的统一数据层。

核心作用
  • 屏蔽底层业务系统的差异,统一数据标准和字段命名
  • 清洗脏数据,提高数据质量
  • 整合多个源系统的同一实体数据,形成统一的实体视图
  • 对敏感数据进行脱敏处理,保障数据安全
主要加工逻辑
  1. 数据清洗:过滤空值、异常值、重复数据和测试数据
  2. 数据标准化:
    • 统一字段命名:如所有用户 ID 统一为user_id,订单 ID 统一为order_id
    • 统一数据格式:时间统一为yyyy-MM-dd HH:mm:ss,金额统一为分
    • 统一编码标准:如性别编码(1 = 男,2 = 女,0 = 未知)、订单状态编码
  3. 数据整合:将多个源系统的同一实体数据合并(如用户信息可能来自用户中心、订单系统、支付系统)
  4. 数据脱敏:对手机号、身份证号、银行卡号等敏感数据进行脱敏处理
数据特点
  • 结构标准化:字段含义明确,命名规范统一
  • 无业务逻辑:只做数据清洗和标准化,不加入任何业务计算逻辑
  • 保留全量历史:按天分区存储,保留所有历史数据
  • 可追溯性:每条数据都能追溯到 ODS 层的原始数据
典型表示例
  • idl_order_di:标准化后的订单增量表
  • idl_user_df:标准化后的用户全量表
  • idl_merchant_df:标准化后的商家全量表
  • idl_delivery_record_di:标准化后的配送记录表
2.0 时代技术实现与痛点
  • 技术实现:主要用 Hive SQL 编写 ETL 任务,按天分区存储
  • 核心痛点:
    • 加工逻辑重复,不同业务线可能各自做一遍数据清洗
    • 没有统一的实体识别和关联标准,导致同一实体在不同表中标识不一致
    • 数据血缘不完整,问题排查困难
3.0 时代改造
  • 建立统一的实体识别和关联标准,实现全局唯一 ID
  • 基于元数据自动生成 IDL 层的加工逻辑
  • 引入自动化数据质量监控,确保数据清洗的准确性

三、CDL 层(Common Data Layer,公共数据层)

核心定义

也叫数据仓库层 / 主题层,是数仓的核心层,按照业务主题对 IDL 层数据进行维度建模和轻度汇总。

核心作用
  • 构建企业级的公共数据资产,实现数据的共享和复用
  • 统一原子指标定义,解决数据口径不一致的问题
  • 为上层数据集市和应用提供统一的基础数据
建模方法

采用维度建模(星型模型为主,雪花模型为辅),按照业务过程划分主题域。美团外卖的核心主题域包括:

  • 用户主题域
  • 商家主题域
  • 交易主题域
  • 支付主题域
  • 配送主题域
  • 商品主题域
主要内容
  1. 维度表:描述业务实体的属性信息,通常是缓慢变化维度(SCD)
    • 例:用户维度表(包含用户 ID、性别、年龄、注册时间、地域等)
    • 例:时间维度表(包含年、月、日、周、季度、节假日等)
  2. 事实表:记录业务过程的度量信息,通常是事务型事实表
    • 例:订单事实表(包含订单 ID、用户 ID、商家 ID、订单金额、下单时间等)
  3. 轻度汇总表:按照常用维度进行轻度汇总,提高查询性能
    • 例:商家日交易汇总表(按商家 ID 和日期汇总订单量、交易额等)
数据特点
  • 面向主题:按照业务主题组织数据,而不是按照业务系统组织
  • 粒度较细:通常保留最细粒度的事实数据(如单条订单、单条配送记录)
  • 指标原子化:只包含原子指标(如订单量、交易额),不包含派生指标
  • 复用性高:是所有上层数据的基础,被多个业务线共享
典型表示例
  • cdl_order_fact_di:订单事实表
  • cdl_user_dim_df:用户维度表
  • cdl_merchant_dim_df:商家维度表
  • cdl_merchant_trade_sum_di:商家交易轻度汇总表
2.0 时代技术实现与痛点
  • 技术实现:Hive SQL 进行维度建模和汇总计算,部分高频查询用 Kylin 预计算
  • 核心痛点:
    • 主题划分不清晰,部分表跨多个主题域
    • 原子指标定义不统一,导致不同汇总表的指标口径不一致
    • 轻度汇总表过多过杂,维护困难
3.0 时代改造
  • 重新划分主题域,明确每个主题的边界和职责
  • 建立统一的原子指标管理体系,实现指标的全局唯一
  • 将轻度汇总表改造为数据组件,提高复用性和灵活性

四、MDL 层(Market Data Layer,集市数据层)

核心定义

也叫数据集市层,是面向特定业务线或业务部门的专用数据层。

核心作用
  • 根据不同业务线的个性化需求,对 CDL 层数据进行进一步加工和汇总
  • 加入业务线特有的计算逻辑,满足业务线的定制化分析需求
  • 构建业务线专用的宽表和汇总表,提高查询效率
主要加工逻辑
  1. 业务逻辑加工:加入业务线特有的计算规则(如不同业务线的补贴计算、佣金计算)
  2. 多维度汇总:按照业务线常用的维度进行深度汇总(如按城市、商圈、品类汇总)
  3. 宽表构建:将多个事实表和维度表关联成大宽表,避免业务查询时的多表关联
  4. 派生指标计算:基于原子指标计算派生指标(如客单价 = 交易额 / 订单量)
数据特点
  • 面向特定业务:只服务于某一个业务线或部门,复用性低
  • 粒度较粗:通常是日、周、月级别的汇总数据
  • 包含业务逻辑:加入了业务线特有的计算逻辑
  • 数据口径多样:不同业务线可能有不同的指标口径
典型表示例
  • mdl_waimai_merchant_daily:外卖商家日汇总表(仅服务于外卖业务线)
  • mdl_waimai_user_behavior_daily:外卖用户行为日汇总表
  • mdl_waimai_delivery_city_daily:外卖配送城市日汇总表
  • mdl_waimai_category_trade_weekly:外卖品类交易周汇总表
2.0 时代技术实现与痛点
  • 技术实现:各业务线独立用 Hive SQL 开发,按天分区存储

  • 核心痛点

    (这是 3.0 升级最主要的驱动力):

    • 过度膨胀:库表数量呈指数级增长,高峰期超过 10 万张表
    • 重复建设:不同业务线建设大量相似的表,60% 以上的开发资源消耗在重复劳动上
    • 口径混乱:同一个指标(如交易额)在不同业务线有多个不同版本
    • 维护困难:表之间的依赖关系复杂,问题排查和变更影响分析极其困难
3.0 时代改造
  • 取消独立的 MDL 层,将其功能拆分到数据组件层和应用层
  • 通用的业务逻辑和汇总指标下沉到数据组件层
  • 个性化的应用需求通过数据组件的拼接在应用层实现

五、ADL 层(Application Data Layer,应用数据层)

核心定义

数仓的最上层,直接面向数据应用和最终用户。

核心作用
  • 为具体的数据应用提供数据支撑
  • 满足最终用户的查询、报表、分析和导出需求
  • 提供统一的数据服务接口
主要内容
  1. 报表数据:为各种业务报表(日报、周报、月报)提供数据
  2. 大屏数据:为实时监控大屏、指挥中心提供数据
  3. 接口数据:为业务系统(如商家后台、骑手 App)提供数据接口
  4. 导出数据:为用户提供 Excel、CSV 等格式的数据导出服务
  5. 即席查询数据:支持用户的临时查询和分析需求
数据特点
  • 面向具体应用:数据格式和粒度完全匹配应用需求
  • 数据量最小:只包含应用需要的字段和数据
  • 更新频率高:部分应用需要小时级甚至实时更新
  • 查询性能要求高:需要支持高并发、低延迟的查询
典型表示例
  • adl_waimai_daily_report:外卖日报表数据
  • adl_waimai_real_time_screen:外卖实时大屏数据
  • adl_merchant_ranking_daily:商家排行榜日数据
  • adl_user_portrait_daily:用户画像日数据
2.0 时代技术实现与痛点
  • 技术实现:Hive SQL、Spark SQL 加工数据,Kylin 提供预计算查询,Redis 提供缓存,MySQL 存储报表数据
  • 核心痛点:
    • 开发效率低,每个应用都需要单独开发数据加工逻辑
    • 数据更新不及时,无法满足实时需求
    • 缺乏统一的数据服务接口,应用对接成本高
3.0 时代改造
  • 建立统一数据服务层,提供标准的 RESTful API 接口
  • 引入 Apache Doris 作为实时 OLAP 引擎,支持实时数据查询
  • 提供自助查询工具,用户可以通过拖拽方式生成报表和查询

六、五层架构对比与 3.0 时代的整体变化

五层架构核心对比表
层级 中文名称 核心职责 数据粒度 复用性 开发主体
ODS 操作数据存储层 原始数据接入 最细(单条记录) 高 数据基建组
IDL 数据集成层 数据清洗与标准化 最细(单条记录) 高 数据基建组
CDL 公共数据层 维度建模与轻度汇总 细(原子粒度) 高 数据基建组
MDL 集市数据层 业务线定制化加工 较粗(日 / 周汇总) 低 各业务线
ADL 应用数据层 面向应用的数据支撑 最粗(应用粒度) 低 各业务线
3.0 时代的架构简化

数仓 3.0 将原来的五层架构简化为三层架构,解决了 2.0 时代中间层冗余、重复建设的问题:

  • 基础层:对应原来的 ODS+IDL 层,负责数据接入和标准化
  • 数据组件层:对应原来的 CDL 层 + 部分 MDL 层,是 3.0 的核心创新,将原子指标和基础维度封装成可复用的数据组件
  • 应用层:对应原来的部分 MDL 层 + ADL 层,通过数据组件的拼接快速构建应用

外卖数仓 3.0

美团外卖数仓 3.0 数据组件层构建逻辑与实战详解

数据组件层(Data Component Layer, DCL)是美团外卖数仓 3.0 最核心的创新,它彻底颠覆了 2.0 时代 “按业务过程建模、业务线重复开发” 的模式,将数据按照 “分析对象” 进行标准化封装,实现了 “一次建模、全公司复用”。它替代了原来的 CDL 公共数据层和 80% 以上的 MDL 集市层,从根源上解决了数据口径混乱、重复建设严重、开发效率低下的痛点。

一、数据组件层的核心定位与设计理念

1. 核心定位

数据组件层是连接基础层和应用层的中间桥梁,是企业级公共数据资产的载体。它将原子指标、基础维度和多维明细数据封装成可复用的 “数据积木”,上层应用只需通过 “拼接积木” 的方式就能快速构建数据产品,无需从原始数据开始开发。

2. 与 2.0 时代 CDL/MDL 的本质区别
维度 2.0 CDL+MDL 模式 3.0 数据组件模式
建模中心 以 “业务过程” 为中心(如订单过程、支付过程) 以 “分析对象” 为中心(如商家、用户、订单)
指标管理 原子指标和派生指标混合,各业务线自行定义 组件内只存原子指标,派生指标统一在应用层计算
复用性 低,不同业务线重复建设相似表 高,一个组件被所有业务线共享
开发模式 人工编写 SQL,全流程手动 配置化拼接组件,工具自动生成 SQL
口径一致性 差,同一个指标有多个版本 好,所有应用使用同一套原子指标
3. 核心设计理念
(1)分析对象唯一原则

一个组件对应一个且仅一个分析对象,这是数据组件层最核心的原则。

  • 分析对象是业务中可独立分析的实体,如商家、用户、订单、骑手、商品
  • 禁止一个组件包含多个分析对象的数据,避免边界模糊
  • 例如:不能有 “商家和用户交易组件”,必须拆分为 “商家交易组件” 和 “用户交易组件”
(2)原子指标标准化原则
  • 组件内只存储原子指标(不可再拆分的指标,如订单量、交易额)
  • 所有派生指标(如客单价 = 交易额 / 订单量)统一在应用层计算
  • 原子指标必须在全局指标中心注册,确保口径唯一
(3)高内聚低耦合原则
  • 高内聚:组件内部包含该分析对象的所有相关维度和指标
  • 低耦合:组件之间通过主键关联,没有直接的依赖关系
  • 一个组件的修改不会影响其他组件,提高系统的可维护性
(4)流批一体原则
  • 同一个组件同时支持离线和实时两种计算模式
  • 离线和实时使用完全相同的计算逻辑和字段定义
  • 上层应用可以根据需求选择使用离线数据还是实时数据
(5)数据驱动优化原则
  • 收集所有应用对组件的查询行为
  • 分析高频查询的维度和指标组合
  • 根据查询热度自动优化组件的结构和预计算内容

二、数据组件层的整体构建逻辑

数据组件层采用 “两层架构 + 五步构建法” 的模式进行建设,确保组件的标准化、可复用性和可维护性。

1. 内部两层架构

数据组件层内部又分为两个子层,分别对应不同的查询需求:

1
2
3
4
5
6
7
┌─────────────────────────────────────────────────────────┐
│ 轻度汇总组件层(Summary Component) │
│ 按"分析对象+常用维度"预计算原子指标,支持快速聚合查询 │
├─────────────────────────────────────────────────────────┤
│ 多维明细组件层(Detail Component) │
│ 保留最细粒度的明细数据,支持任意维度的钻取和分析 │
└─────────────────────────────────────────────────────────┘
(1)多维明细组件层
  • 定位:保留分析对象的最细粒度明细数据,是所有汇总数据的源头
  • 粒度:与原始数据的粒度一致(如单条订单、单条行为记录)
  • 内容:包含该分析对象的所有维度属性和原子指标
  • 更新频率:日级(离线)或分钟级(实时)
  • 适用场景:即席查询、自定义分析、异常排查
(2)轻度汇总组件层
  • 定位:按常用维度组合预计算原子指标,提高查询性能
  • 粒度:“分析对象 + 常用维度”(如商家 + 日期、用户 + 日期)
  • 内容:只包含原子指标,不包含派生指标
  • 更新频率:日级(离线)或分钟级(实时)
  • 适用场景:固定报表、仪表盘、业务监控
2. 五步构建法

数据组件的构建是一个标准化的流程,遵循以下五个步骤:

第一步:分析对象识别与边界划分

这是最关键的一步,决定了组件的合理性和复用性。

  1. 业务调研:与所有业务线沟通,收集他们的分析需求
  2. 实体抽象:从需求中抽象出共同的分析对象
  3. 边界划分:明确每个分析对象的边界,避免重叠
  4. 优先级排序:根据业务需求的紧急程度和复用价值排序

美团外卖核心分析对象:

  • 用户、商家、骑手、商品、订单、支付、配送、营销
第二步:实体关系与依赖梳理

梳理分析对象之间的关联关系,为后续的组件关联和维度冗余做准备。

  • 一对一关系:用户 ID ↔ 用户基本信息
  • 一对多关系:商家 ID ↔ 订单 ID
  • 多对多关系:订单 ID ↔ 商品 ID

美团外卖核心实体关系图(简化版):

1
2
3
4
5
用户 1───n 订单 n───1 商家
│
├───n 支付记录
├───n 配送记录
└───n 商品
第三步:组件内容设计

为每个分析对象设计对应的多维明细组件和轻度汇总组件,明确组件的字段、粒度和更新频率。

组件设计模板:

项 内容
组件名称 dcl_[分析对象][类型][更新频率]
组件描述 清晰描述组件的用途和包含的数据范围
分析对象 对应的业务实体
粒度 数据的最细粒度
分区字段 dt(日期)、hh(小时)等
分桶字段 通常是分析对象的主键(如 merchant_id)
维度字段 所有与该分析对象相关的维度属性
原子指标 所有不可再拆分的度量指标
更新频率 di(日增量)、hh(小时级)、mi(分钟级)
存储引擎 Hudi(离线)、Doris(实时)
第四步:组件开发与流批实现

基于数仓 3.0 的流批一体架构,开发组件的离线和实时计算逻辑。

  1. 离线计算:使用 Spark 读取基础层数据,计算组件的全量和增量数据,写入 Hudi
  2. 实时计算:使用 Flink 读取 Kafka 数据流,计算组件的实时增量数据,写入 Doris
  3. 逻辑统一:将公共计算逻辑封装成 UDF,离线和实时任务共享
  4. 数据校验:开发数据质量校验规则,确保组件数据的准确性
第五步:组件注册、治理与迭代
  1. 组件注册:将组件注册到元数据中心,填写组件的描述、字段、负责人等信息
  2. 权限管理:设置组件的访问权限,控制不同业务线的访问范围
  3. 血缘管理:自动采集组件的上下游血缘关系,支持影响分析
  4. 质量监控:配置组件的数据质量监控规则,实时监控数据质量
  5. 迭代优化:根据业务需求和查询行为,持续优化组件的结构和内容

三、核心技术实现细节

1. 基于 Hudi/Beluga 的存储设计

数据组件层主要使用美团基于 Hudi 改造的 Beluga 引擎作为存储引擎,针对组件的特点做了以下优化:

  • 分桶策略:所有组件都按分析对象的主键进行分桶(如 merchant_id),提高查询和关联性能

  • 表类型选择:

    • 多维明细组件:使用 MOR(Merge On Read)表,支持高效的增量更新
    • 轻度汇总组件:使用 COW(Copy On Write)表,提供更好的查询性能
  • 分区策略:按日期分区,部分高频查询的组件按小时分区

  • 索引选择:使用 Bucket Index,将 Upsert 性能提升 10 倍以上

2. 流批一体的计算逻辑

实现 “一套逻辑、两种运行模式” 是数据组件层的核心技术挑战。美团的解决方案是:

  1. 统一 SQL 语法:使用 Flink SQL 作为统一的 SQL 语法,离线和实时任务使用相同的 SQL
  2. 公共 UDF 库:将所有业务逻辑封装成公共 UDF,离线和实时任务共享
  3. 计算引擎适配:开发引擎适配层,将统一的 SQL 转换为 Spark 或 Flink 可执行的代码
  4. 结果校准:每天用离线计算结果校准实时计算结果,保证数据一致性
3. 元数据驱动的自动生成

基于统一元数据中心,实现了组件开发的自动化:

  • 自动生成 DDL:根据组件设计文档自动生成 Hudi 和 Doris 表的 DDL 语句
  • 自动生成 DML:根据组件的字段和计算逻辑自动生成 Spark 和 Flink 的计算代码
  • 自动生成调度:自动生成 Azkaban 调度任务,配置任务的依赖关系和运行时间
  • 自动生成监控:自动生成数据质量监控规则和告警配置
4. 组件间关联机制

组件之间通过主键进行关联,应用层建模工具可以自动完成关联和维度冗余:

  1. 元数据关联:在元数据中心记录组件之间的关联关系(如商家组件和订单组件通过 merchant_id 关联)
  2. 自动关联:当用户在应用层拼接多个组件时,工具会根据元数据自动关联组件
  3. 维度冗余:工具会自动将维度组件的属性冗余到事实组件中,避免查询时的多表关联
  4. 关联优化:工具会自动选择最优的关联方式(如 Broadcast Join),提高查询性能

四、实战举例:美团外卖核心数据组件

下面以美团外卖最常用的三个核心组件为例,详细说明组件的设计和使用方法。

例 1:商家交易汇总组件(dcl_merchant_trade_summary_di)

这是使用频率最高的组件之一,被营销、商家运营、产品等多个业务线共享。

(1)组件基本信息
项 内容
组件名称 dcl_merchant_trade_summary_di
组件描述 按商家和日期汇总的交易数据,包含所有商家交易相关的原子指标
分析对象 商家
粒度 商家 + 日期
分区字段 dt
分桶字段 merchant_id
更新频率 di(日增量)
存储引擎 Hudi MOR 表
(2)核心字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
CREATE TABLE dcl_merchant_trade_summary_di (
merchant_id STRING COMMENT '商家ID',
city_id STRING COMMENT '城市ID',
category_id STRING COMMENT '品类ID',
dt STRING COMMENT '日期',
-- 原子指标
order_count BIGINT COMMENT '订单量',
valid_order_count BIGINT COMMENT '有效订单量',
cancel_order_count BIGINT COMMENT '取消订单量',
refund_order_count BIGINT COMMENT '退款订单量',
total_amount DECIMAL(18,2) COMMENT '总交易额',
pay_amount DECIMAL(18,2) COMMENT '实际支付金额',
subsidy_amount DECIMAL(18,2) COMMENT '平台补贴金额',
merchant_subsidy_amount DECIMAL(18,2) COMMENT '商家补贴金额',
commission_amount DECIMAL(18,2) COMMENT '平台佣金',
user_count BIGINT COMMENT '下单用户数',
new_user_count BIGINT COMMENT '新下单用户数',
avg_order_amount DECIMAL(18,2) COMMENT '平均客单价' -- 注意:这是原子指标,因为它是单订单金额的平均值
) COMMENT '商家交易汇总组件'
PARTITIONED BY (dt STRING)
CLUSTERED BY (merchant_id) INTO 128 BUCKETS
STORED AS HUDI
TBLPROPERTIES (
'hoodie.table.type' = 'MERGE_ON_READ',
'hoodie.bucket.index.num.buckets' = '128'
);
(3)计算逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- 离线计算逻辑(Spark SQL)
INSERT OVERWRITE TABLE dcl_merchant_trade_summary_di PARTITION (dt='${dt}')
SELECT
merchant_id,
city_id,
category_id,
'${dt}' AS dt,
COUNT(order_id) AS order_count,
COUNT(CASE WHEN order_status = 'VALID' THEN order_id END) AS valid_order_count,
COUNT(CASE WHEN order_status = 'CANCELLED' THEN order_id END) AS cancel_order_count,
COUNT(CASE WHEN order_status = 'REFUNDED' THEN order_id END) AS refund_order_count,
SUM(total_amount) AS total_amount,
SUM(pay_amount) AS pay_amount,
SUM(subsidy_amount) AS subsidy_amount,
SUM(merchant_subsidy_amount) AS merchant_subsidy_amount,
SUM(commission_amount) AS commission_amount,
COUNT(DISTINCT user_id) AS user_count,
COUNT(DISTINCT CASE WHEN is_new_user = 1 THEN user_id END) AS new_user_count,
AVG(total_amount) AS avg_order_amount
FROM dcl_order_full_detail_di
WHERE dt = '${dt}'
GROUP BY merchant_id, city_id, category_id;
(4)使用场景与示例

场景:生成外卖商家日报表,包含商家的基本信息和交易指标。

传统 2.0 写法(需要关联 5 张表,100 + 行 SQL):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
SELECT
m.merchant_id,
m.merchant_name,
m.city_name,
m.category_name,
o.order_count,
o.valid_order_count,
o.total_amount,
o.pay_amount,
o.user_count,
o.avg_order_amount
FROM (
-- 自己计算商家交易汇总
SELECT
merchant_id,
COUNT(order_id) AS order_count,
COUNT(CASE WHEN order_status = 'VALID' THEN order_id END) AS valid_order_count,
SUM(total_amount) AS total_amount,
SUM(pay_amount) AS pay_amount,
COUNT(DISTINCT user_id) AS user_count,
AVG(total_amount) AS avg_order_amount
FROM idl_order_di
WHERE dt = '${dt}'
GROUP BY merchant_id
) o
JOIN (
-- 关联商家维度表
SELECT
merchant_id,
merchant_name,
city_name,
category_name
FROM idl_merchant_df
WHERE dt = '${dt}'
) m ON o.merchant_id = m.merchant_id;

3.0 组件写法(只需关联 2 个组件,10 行 SQL):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SELECT
m.merchant_id,
m.merchant_name,
m.city_name,
m.category_name,
t.order_count,
t.valid_order_count,
t.total_amount,
t.pay_amount,
t.user_count,
t.avg_order_amount
FROM dcl_merchant_trade_summary_di t
JOIN dcl_merchant_dim_df m ON t.merchant_id = m.merchant_id
WHERE t.dt = '${dt}';

优势对比:

  • 代码量减少 90%
  • 开发时间从 1 天缩短到 10 分钟
  • 所有业务线使用同一套交易指标,口径完全一致
例 2:用户行为明细组件(dcl_user_behavior_detail_hh)

用于分析用户的 App 行为,支持用户行为路径分析、转化漏斗分析等。

(1)组件基本信息
项 内容
组件名称 dcl_user_behavior_detail_hh
组件描述 用户在 App 内的所有行为明细数据
分析对象 用户行为
粒度 单条行为记录
分区字段 dt, hh
分桶字段 user_id
更新频率 hh(小时级)
存储引擎 Hudi MOR 表
(2)核心字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CREATE TABLE dcl_user_behavior_detail_hh (
user_id STRING COMMENT '用户ID',
session_id STRING COMMENT '会话ID',
event_id STRING COMMENT '事件ID',
event_name STRING COMMENT '事件名称',
page STRING COMMENT '页面',
element STRING COMMENT '元素',
target_id STRING COMMENT '目标ID(如商家ID、商品ID)',
timestamp BIGINT COMMENT '事件时间戳',
referrer STRING COMMENT '来源页面',
device_id STRING COMMENT '设备ID',
app_version STRING COMMENT 'App版本'
) COMMENT '用户行为明细组件'
PARTITIONED BY (dt STRING, hh STRING)
CLUSTERED BY (user_id) INTO 256 BUCKETS
STORED AS HUDI;
(3)使用示例:计算首页到下单的转化率
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
WITH home_view AS (
SELECT DISTINCT user_id
FROM dcl_user_behavior_detail_hh
WHERE dt = '${dt}'
AND event_name = 'page_view'
AND page = 'home'
),
order_click AS (
SELECT DISTINCT user_id
FROM dcl_user_behavior_detail_hh
WHERE dt = '${dt}'
AND event_name = 'click'
AND element = 'order_button'
)
SELECT
COUNT(o.user_id) / COUNT(h.user_id) AS conversion_rate
FROM home_view h
LEFT JOIN order_click o ON h.user_id = o.user_id;
例 3:订单全链路明细组件(dcl_order_full_detail_di)

整合了订单、支付、配送、营销等全链路数据,是最复杂也是最核心的组件之一。

(1)组件基本信息
项 内容
组件名称 dcl_order_full_detail_di
组件描述 订单全链路明细数据,包含订单、支付、配送、营销等所有相关信息
分析对象 订单
粒度 单条订单
分区字段 dt
分桶字段 order_id
更新频率 di(日增量)
存储引擎 Hudi MOR 表
(2)核心字段
  • 订单基本信息:order_id, user_id, merchant_id, order_time, order_status
  • 支付信息:pay_time, pay_amount, pay_method
  • 配送信息:rider_id, pick_up_time, delivery_time, delivery_duration, is_timeout
  • 营销信息:coupon_id, coupon_amount, activity_id
  • 商品信息:商品列表(数组类型)
(3)使用示例:计算不同配送时长的订单占比
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SELECT
CASE
WHEN delivery_duration < 30 THEN '30分钟以内'
WHEN delivery_duration < 45 THEN '30-45分钟'
WHEN delivery_duration < 60 THEN '45-60分钟'
ELSE '60分钟以上'
END AS delivery_duration_range,
COUNT(order_id) AS order_count,
COUNT(order_id) / total_order_count AS order_ratio
FROM dcl_order_full_detail_di
CROSS JOIN (
SELECT COUNT(*) AS total_order_count
FROM dcl_order_full_detail_di
WHERE dt = '${dt}'
) t
WHERE dt = '${dt}'
GROUP BY
CASE
WHEN delivery_duration < 30 THEN '30分钟以内'
WHEN delivery_duration < 45 THEN '30-45分钟'
WHEN delivery_duration < 60 THEN '45-60分钟'
ELSE '60分钟以上'
END;

五、最佳实践与避坑指南

1. 组件粒度把握
  • 不要过粗:一个组件包含太多分析对象,导致复用性差
  • 不要过细:拆分出太多小组件,导致应用层需要关联大量组件
  • 黄金法则:如果一个组件的字段被 80% 以上的应用使用,那么这个组件的粒度是合适的
2. 避免组件膨胀
  • 定期清理:每季度清理一次没有被任何应用使用的组件
  • 严格审批:新组件的创建必须经过严格的审批流程,确保其复用价值
  • 合并相似组件:如果发现多个组件功能相似,及时合并成一个通用组件
3. 组件依赖管理
  • 单向依赖:组件之间只能单向依赖,禁止循环依赖
  • 依赖最小化:一个组件的依赖越少越好,最好只依赖基础层
  • 依赖版本管理:当依赖的组件发生变化时,及时通知所有下游应用
4. 性能优化
  • 合理分桶:按查询频率最高的字段进行分桶,通常是分析对象的主键
  • 分区裁剪:所有查询都必须使用分区字段,避免全表扫描
  • 预计算:将高频查询的维度组合预计算成轻度汇总组件
  • 数据压缩:使用 ZSTD 压缩算法,减少存储成本和 IO 开销
5. 质量保障
  • SLA 承诺:每个组件都必须有明确的 SLA 承诺,如每天早上 8 点前产出
  • 数据质量监控:对组件的核心指标进行监控,如数据量、空值率、异常值
  • 数据对比:每天将组件数据与原始数据进行对比,确保数据一致性
  • 故障应急预案:制定故障应急预案,当组件数据出现问题时能快速恢复

六、总结

数据组件层是美团外卖数仓 3.0 的灵魂,它通过 “以分析对象为中心” 的建模思想和 “组件化复用” 的设计理念,从根源上解决了传统数仓重复建设和口径不一致的问题。

通过数据组件层的建设,美团外卖实现了:

  • 开发效率提升 50% 以上:新需求响应周期从 3-5 天缩短到 1 天以内
  • 数据口径不一致问题减少 80%:所有应用使用同一套原子指标
  • 计算资源节省 30%:避免了重复计算,提高了资源利用率
  • 维护成本大幅降低:只需要维护一套公共组件,而不是成千上万的业务表

对于正在进行数仓升级的企业来说,数据组件层是一个非常值得借鉴的设计思路。它不需要推翻现有的数仓架构,可以在现有 CDL 层的基础上逐步改造,先从最核心、复用性最高的分析对象开始,逐步扩展到所有业务领域。

美团数仓 3.0 完整技术栈与标准化开发流程(2026 最新)

美团数仓 3.0 不是单一技术的升级,而是 **“平台化 + 工具化 + 智能化” 的完整技术体系 **。它以 “达芬奇智能构建平台” 为核心,整合了流批一体计算、增量存储、统一数据服务等能力,实现了从 “人工写 SQL” 到 “配置化生成任务” 的范式转变。

一、数仓 3.0 全栈技术图谱

1. 整体技术架构图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
┌─────────────────────────────────────────────────────────┐
│ 应用层 │
│ ├─ 统一BI平台 ├─ 实时大屏 ├─ 数据服务API ├─ 自助分析 │
│ └─ 算法特征平台 ├─ 营销平台 ├─ 商家后台 │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ 工具平台层(核心) │
│ ├─ 达芬奇智能构建平台 ├─ 统一指标平台 ├─ 元数据中心 │
│ ├─ 数据质量平台 ├─ 统一调度平台 ├─ 数据安全平台 │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ 计算引擎层 │
│ ├─ 离线计算:Spark 3.5(深度定制) │
│ ├─ 实时计算:Flink 1.18(深度定制) │
│ └─ OLAP计算:Apache Doris 2.1(深度定制)+ Kylin 4.0 │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ 存储引擎层 │
│ ├─ 增量存储:Beluga 3.0(基于Hudi 0.14深度改造) │
│ ├─ 分布式文件系统:HDFS 3.3 │
│ ├─ 消息队列:Kafka 3.6 │
│ ├─ 缓存:Redis 7.0(集群版) │
│ └─ 元数据存储:TiDB 6.5 │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ 数据接入层 │
│ ├─ Binlog采集:Canal 1.1(美团定制版) │
│ ├─ 日志采集:Flume NG + LogAgent │
│ ├─ 数据同步:DataX 3.0(美团定制版) │
│ └─ 第三方数据接入:统一API网关 │
└─────────────────────────────────────────────────────────┘
2. 核心平台详解
(1)达芬奇智能构建平台(数仓 3.0 的大脑)

这是美团数仓 3.0 最核心的自研平台,实现了 “定义即研发” 的目标,覆盖数仓建设全生命周期。

三大核心能力:

  • 结构化指标管理:基于数据域、业务过程的统一定义规范,确保指标定义无重复、无歧义。建立了 “业务过程→原子指标→派生指标→复合指标→应用指标” 的五层指标治理框架。
  • 自动化 ETL 构建:基于组件模型语义,自动生成 ETL 任务代码、调度配置和监控规则。支持复杂指标公式、预聚合配置和多引擎适配。
  • 统一查询路由:基于模型内容与指标维度语义,自动选择最优的查询引擎(Spark/Doris/Kylin)和物理表,屏蔽底层存储差异。

关键创新:

  • 级联变更引擎:当组件层修改字段或指标口径时,系统自动识别所有下游影响,生成测试用例并完成灰度发布。原先需要 2 周协调的变更流程压缩至 8 小时内完成。
  • 虚拟表达式技术:允许将 “近 30 日复购率” 这类复杂指标通过声明式语法定义,系统自动拆解为底层计算逻辑,无需人工编写 SQL。
  • 多引擎适配架构:支持 Hive2Hive、Hive2Doris、Flink2Doris 等 9 种任务类型,同一语义配置可同时生成离线和实时任务。
(2)统一指标平台
  • 实现了 “一个指标、一个口径、一次计算、多处使用”
  • 支持指标的版本管理、变更通知和血缘追踪
  • 与达芬奇平台深度集成,指标定义自动同步到数仓模型
  • 提供指标的自助查询和分析能力今日头条
(3)元数据中心
  • 覆盖全链路元数据:表、字段、指标、维度、任务、血缘、权限
  • 支持字段级别的血缘追溯和影响分析
  • 提供元数据的搜索、浏览和编辑功能
  • 是达芬奇平台自动生成代码的基础美团技术团队
(4)数据质量平台
  • 提供自动化的数据质量校验规则:完整性、准确性、一致性、及时性
  • 支持实时监控和告警,异常数据自动拦截
  • 与调度平台集成,质量不达标任务自动终止下游任务
  • 生成数据质量报告,量化数据质量水平美团技术团队
(5)统一调度平台(Azkaban 定制版)
  • 支持离线和实时任务的统一调度
  • 提供任务的依赖管理、资源管理和运行监控
  • 支持任务的优先级调度和故障自动重试
  • 与数据质量平台集成,实现质量驱动的调度
3. 核心技术详解
(1)存储引擎:Beluga 3.0(基于 Hudi 深度改造)

美团基于 Apache Hudi 0.14 版本深度改造的增量存储引擎,是数仓 3.0 的核心技术底座。

核心改进:

  • 两层分桶设计:将原来的单层 Bucket 改为两层分桶,解决了原生 Hudi 文件数量上限问题,支持单表 PB 级数据存储
  • 独立 MetaServer 服务:将元数据管理从客户端侧剥离,形成独立的 MetaServer 服务,负责维护 Timeline、Bucket 等组织关系,大幅提升元数据操作性能
  • 一表三模式:支持 “全量表、快照表、增量表” 三种模式在同一张表中同时存在,满足不同业务场景的需求
  • 原生 CDC 支持:基于 HBase KV 存储生产 Changelog,提供毫秒级的增量数据消费能力
  • 智能 Compaction:根据数据写入频率和查询模式自动调整 Compaction 策略,平衡写入和查询性能

应用场景:

  • 离线数仓最新快照事实表的生产
  • 增量数据的高效合并和计算
  • 流批一体的数据湖存储
  • 实时数仓的明细层和汇总层存储

美团是国内最早大规模使用 Flink 的公司之一,对 Flink 进行了数百项优化。

核心优化:

  • 状态管理优化:自研增量 Checkpoint + 异步上传机制,将 Checkpoint 时间从分钟级缩短到秒级
  • 跨作业状态复用:支持多个作业共享同一个状态后端,避免重复计算
  • 动态资源弹性伸缩:根据作业负载自动调整并行度,提高资源利用率
  • SQL 层增强:统一 UDF/UDTF/UDAF 注册中心,提供面向业务语义的流式指标 DSL
  • Exactly-Once 保证:优化了两阶段提交协议,确保端到端的数据一致性

应用场景:

  • 实时数据接入和清洗
  • 实时指标计算
  • 实时数仓建设
  • 实时特征工程
(3)OLAP 引擎:Apache Doris 2.1(深度定制)

美团是 Apache Doris 的主要贡献者之一,将 Doris 作为实时 OLAP 的核心引擎美团技术团队。

核心改进:

  • Join 谓词下推的传递性优化:解决了多表关联查询性能问题,复杂查询性能提升 3-5 倍
  • 数据导入性能优化:支持百万级 / 秒的实时数据写入,导入延迟降低到秒级
  • 聚合模型和 Unique 模型增强:支持部分列更新和 Merge-on-Write 模式,满足业务状态变更的需求
  • 向量化执行引擎优化:全面支持向量化执行,查询性能提升 2-3 倍
  • 智能查询路由:根据查询复杂度自动选择合适的执行计划和资源配置美团技术团队

应用场景:

  • 实时报表和大屏
  • 即席查询和自助分析
  • 高并发数据服务
  • 实时数仓的应用层存储

二、数仓 3.0 标准化开发流程

数仓 3.0 的开发流程与 2.0 有本质区别,它是 “元数据驱动、工具化支撑、全流程自动化” 的流程。一个需求从提出到上线,80% 以上的工作由工具自动完成,数据工程师只需要关注业务逻辑和模型设计。

1. 开发流程总览
1
需求提出 → 需求评审 → 模型设计 → 组件开发/复用 → 应用建模 → 自动生成任务 → 测试验证 → 灰度发布 → 全量上线 → 运维监控
2. 详细步骤说明
阶段 1:需求提出与评审(第 1 天)

输入:业务需求文档

输出:需求确认书、数据需求说明书

使用工具:Jira、Confluence

核心步骤:

  1. 业务方在 Jira 上提交数据需求,明确需求背景、指标定义、维度、更新频率和应用场景
  2. 数据产品经理组织需求评审会,邀请数据工程师、业务方和相关方参加
  3. 评审通过后,数据产品经理编写数据需求说明书,明确数据口径和交付时间
  4. 需求分配给对应的数据工程师

与 2.0 的区别:

  • 需求必须明确到原子指标和维度,不能模糊不清
  • 优先复用现有指标和组件,避免重复建设
阶段 2:模型设计(第 2-3 天)

输入:数据需求说明书

输出:数据模型设计文档

使用工具:达芬奇平台(模型设计器)、元数据中心

核心步骤:

  1. 现有资产梳理

    :在元数据中心搜索是否已有相关的数据组件和指标

    • 如果已有,直接复用,进入应用建模阶段
    • 如果没有,进行新组件的设计
  2. 分析对象识别:根据需求识别对应的分析对象(如商家、用户、订单)

  3. 组件设计:

    • 对于多维明细组件:确定粒度、分区字段、分桶字段、维度字段和原子指标
    • 对于轻度汇总组件:确定聚合维度和原子指标
  4. 模型注册:在达芬奇平台中注册新组件,填写组件描述、字段信息、负责人等元数据

  5. 模型评审:组织模型评审会,确保组件设计符合规范和复用性要求

与 2.0 的区别:

  • 不再设计业务线专用的 MDL 表,而是设计通用的数据组件
  • 组件设计必须遵循 “分析对象唯一” 和 “原子指标标准化” 原则
  • 所有设计都在达芬奇平台中完成,自动生成 DDL 语句
阶段 3:数据组件开发(第 4-5 天)

输入:数据模型设计文档

输出:可复用的数据组件

使用工具:达芬奇平台、Spark、Flink、Beluga

核心步骤:

  1. 自动生成 DDL:达芬奇平台根据模型设计自动生成 Beluga 表的 DDL 语句

  2. 执行 DDL:在集群中创建对应的表

  3. 计算逻辑配置:

    • 在达芬奇平台中配置组件的计算逻辑,选择数据源和计算规则
    • 对于简单的统计逻辑,通过可视化配置完成
    • 对于复杂的业务逻辑,编写 UDF 并注册到公共 UDF 库
  4. 自动生成 DML:达芬奇平台根据配置自动生成 Spark/Flink 计算代码

  5. 本地调试:在开发环境中运行任务,验证数据准确性

  6. 数据质量规则配置:在数据质量平台中配置组件的数据质量校验规则

  7. 组件发布:将组件发布到生产环境,供所有业务线使用

示例:商家交易汇总组件开发

  1. 在达芬奇平台中创建组件,名称为dcl_merchant_trade_summary_di

  2. 配置数据源为dcl_order_full_detail_di(订单全链路明细组件)

  3. 配置聚合维度:merchant_id、city_id、category_id、dt

  4. 配置原子指标:order_count、valid_order_count、total_amount等

  5. 达芬奇平台自动生成以下 Spark SQL 代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    INSERT OVERWRITE TABLE dcl_merchant_trade_summary_di PARTITION (dt='${dt}')
    SELECT
    merchant_id,
    city_id,
    category_id,
    '${dt}' AS dt,
    COUNT(order_id) AS order_count,
    COUNT(CASE WHEN order_status = 'VALID' THEN order_id END) AS valid_order_count,
    SUM(total_amount) AS total_amount,
    ...
    FROM dcl_order_full_detail_di
    WHERE dt = '${dt}'
    GROUP BY merchant_id, city_id, category_id;
  6. 运行任务,验证数据准确性

  7. 配置数据质量规则:order_count > 0、total_amount > 0等

  8. 发布组件

与 2.0 的区别:

  • 不需要手动编写 SQL,大部分逻辑通过可视化配置完成
  • 计算逻辑统一在组件层,所有下游应用共享
  • 自动生成调度配置和监控规则
阶段 4:应用建模(第 6 天)

输入:数据组件、数据需求说明书

输出:应用模型

使用工具:达芬奇平台(应用建模器)

核心步骤:

  1. 组件选择:在达芬奇平台中选择需要的数据组件
  2. 组件裁剪:只选择需要的指标和维度,去掉不需要的字段
  3. 自动关联:达芬奇平台根据元数据自动关联多个组件,冗余维度属性
  4. 派生指标计算:配置派生指标的计算公式(如客单价=total_amount/order_count)
  5. 按需聚合:配置需要的聚合维度和聚合方式
  6. 模型预览:预览生成的逻辑宽表和数据
  7. 应用模型保存:保存应用模型,生成唯一的模型 ID

示例:外卖商家日报表应用建模

  1. 选择组件:dcl_merchant_trade_summary_di(商家交易汇总)、dcl_merchant_dim_df(商家维度)
  2. 裁剪字段:选择merchant_id、merchant_name、city_name、category_name、order_count、valid_order_count、total_amount、user_count
  3. 自动关联:通过merchant_id关联两个组件,冗余商家名称、城市名称、品类名称等维度属性
  4. 配置派生指标:客单价=total_amount/order_count
  5. 配置聚合维度:按dt、city_id、category_id聚合
  6. 预览数据,确认无误后保存应用模型

与 2.0 的区别:

  • 不需要手动编写 SQL 关联多个表,工具自动完成
  • 派生指标统一在应用层计算,组件层只存原子指标
  • 应用模型是逻辑模型,不是物理表,节省存储资源
阶段 5:自动生成任务(第 7 天)

输入:应用模型

输出:可执行的 ETL 任务、调度配置、监控配置

使用工具:达芬奇平台、统一调度平台

核心步骤:

  1. 任务生成:达芬奇平台根据应用模型自动生成 ETL 任务代码
  2. 引擎选择:根据数据量和更新频率自动选择最优的计算引擎(Spark/Doris)
  3. 调度配置:自动生成调度配置,包括任务依赖、运行时间、资源配置等
  4. 监控配置:自动生成监控配置,包括任务运行状态、数据质量、延迟等
  5. 任务提交:将任务提交到统一调度平台

与 2.0 的区别:

  • 不需要手动配置调度和监控,工具自动完成
  • 任务代码自动生成,避免人工编码错误
  • 支持多引擎自动适配,优化任务性能
阶段 6:测试验证(第 8-9 天)

输入:ETL 任务

输出:测试报告

使用工具:达芬奇平台、数据质量平台

核心步骤:

  1. 测试环境运行:在测试环境中运行任务,生成测试数据

  2. 数据准确性验证:

    • 与 2.0 时代的旧数据进行对比,验证数据一致性
    • 与业务系统的原始数据进行对比,验证数据准确性
    • 验证派生指标的计算逻辑是否正确
  3. 性能测试:测试任务的运行时间和资源消耗,确保满足 SLA 要求

  4. 数据质量验证:运行数据质量校验规则,确保数据质量符合要求

  5. 生成测试报告:编写测试报告,记录测试结果和问题

  6. 问题修复:如果发现问题,修改模型设计或计算逻辑,重新生成任务

与 2.0 的区别:

  • 工具自动生成测试用例,减少人工测试工作量
  • 数据质量校验自动化,提高测试效率
  • 支持一键回滚,方便问题修复
阶段 7:灰度发布与全量上线(第 10 天)

输入:测试通过的任务

输出:上线的任务

使用工具:达芬奇平台、统一调度平台

核心步骤:

  1. 灰度发布:

    • 先发布到灰度环境,只供内部测试使用
    • 运行 1-2 天,观察任务运行状态和数据质量
  2. 业务验证:邀请业务方验证数据是否符合需求

  3. 全量发布:如果灰度验证通过,将任务发布到生产环境

  4. 流量切换:将业务流量从旧系统切换到新系统

  5. 旧系统下线:观察新系统运行稳定后,下线旧系统

与 2.0 的区别:

  • 支持灰度发布和流量切换,降低上线风险
  • 级联变更自动处理,不需要手动修改下游任务
  • 自动生成上线检查清单,确保上线质量
阶段 8:运维监控与迭代优化(长期)

输入:上线的任务

输出:稳定运行的任务、优化后的模型

使用工具:统一调度平台、数据质量平台、元数据中心

核心步骤:

  1. 日常监控:监控任务的运行状态、数据质量和延迟
  2. 故障处理:及时处理任务失败、数据异常等问题
  3. 性能优化:根据任务运行情况,优化任务的资源配置和计算逻辑
  4. 模型迭代:根据业务需求的变化,迭代优化数据组件和应用模型
  5. 资产治理:定期清理没有被使用的组件和应用模型,避免资产膨胀

与 2.0 的区别:

  • 监控和告警自动化,减少人工运维工作量
  • 元数据驱动的运维,问题定位更快速
  • 数据驱动的模型优化,根据查询行为自动优化组件

三、3.0 vs 2.0 开发流程对比

阶段 数仓 2.0 开发流程 数仓 3.0 开发流程 效率提升
模型设计 人工设计表结构,编写 DDL 可视化设计组件,自动生成 DDL 60%
代码开发 人工编写 SQL,平均 100 + 行 / 需求 可视化配置,自动生成 SQL 80%
调度配置 人工配置调度和依赖 自动生成调度配置 90%
监控配置 人工配置监控和告警 自动生成监控配置 90%
测试验证 人工编写测试用例,手动验证 自动生成测试用例,自动化验证 70%
变更管理 人工修改所有下游任务,平均 7.3 个 / 变更 自动识别影响范围,级联变更 95%
总周期 3-5 天 / 需求 1 天以内 / 简单需求,3 天以内 / 复杂需求 70%

四、最佳实践与避坑指南

1. 组件设计最佳实践
  • 先梳理后设计:先梳理所有业务线的需求,找出共同的分析对象,再设计组件
  • 粒度适中:组件粒度不要过粗或过细,以 “80% 的需求能直接使用” 为标准
  • 原子指标优先:组件内只存原子指标,派生指标统一在应用层计算
  • 避免过度设计:不要为了未来可能的需求提前设计太多字段,保持组件的简洁性
  • 持续迭代:根据业务需求和查询行为,持续优化组件的结构和内容
2. 开发流程避坑指南
  • 不要跳过需求评审:需求不明确会导致后续返工,浪费大量时间
  • 优先复用现有资产:不要重复建设已有的组件和指标,避免口径不一致
  • 严格遵守模型规范:不规范的模型设计会导致后续维护困难
  • 充分测试:不要急于上线,确保数据准确性和性能满足要求
  • 做好灰度发布:全量上线前一定要经过灰度验证,降低上线风险
3. 常见问题与解决方案
问题 解决方案
组件不满足特殊需求 1. 评估是否可以扩展现有组件;2. 如果不能,在应用层进行定制化开发;3. 不要为了特殊需求修改通用组件
组件性能差 1. 优化分桶和分区策略;2. 增加预计算的轻度汇总组件;3. 调整 Compaction 策略
级联变更影响大 1. 组件设计时尽量保持稳定;2. 变更前进行充分的影响分析;3. 采用灰度发布策略
组件膨胀 1. 建立组件审批流程,严格控制新组件的创建;2. 定期清理没有被使用的组件;3. 合并相似的组件

五、总结

美团数仓 3.0 的开发流程本质上是 “将数据工程师的经验和最佳实践固化到工具中” 的过程。通过达芬奇智能构建平台,实现了数仓建设的标准化、自动化和智能化,大幅提升了开发效率,降低了维护成本,从根源上解决了数据口径不一致的问题。

对于正在进行数仓升级的企业来说,不需要一步到位完全复制美团的技术栈,可以先从核心业务入手,逐步引入数据组件化和工具化的理念,先解决最痛点的重复建设和口径不一致问题,再逐步完善整个技术体系。

需要我将以上开发流程整理成一份可直接使用的数仓 3.0 开发规范文档,包含每个阶段的检查清单和交付物模板吗?

美团数仓 3.0 数据组件:存在形式、存储与使用全解

核心结论:美团数据组件既不是纯逻辑视图,也不是传统物理表,而是 **“逻辑语义层 + 物理存储层” 的双层绑定实体 **。逻辑层定义统一的数据口径和使用契约,物理层存储预计算好的实际数据,两者通过元数据中心一一映射。这种设计既解决了传统视图性能差的问题,又解决了物理表口径混乱的问题。

一、数据组件的存在形式:逻辑 + 物理的双层结构

1. 第一层:逻辑语义层(组件的灵魂)

这是数据组件最核心的部分,也是与传统物理表最本质的区别。它是在达芬奇智能构建平台和统一元数据中心中注册的标准化语义实体,不存储任何实际数据,只定义数据的 “是什么” 和 “怎么算”。

逻辑层包含的核心元数据
元数据项 内容说明 示例
全局唯一 ID 组件的唯一标识,全公司通用 comp_merchant_trade_summary_di
基本信息 名称、描述、负责人、业务线、创建时间 名称:商家交易汇总组件描述:按商家和日期汇总的所有交易原子指标
分析对象 组件对应的唯一业务实体 商家
粒度定义 数据的最细粒度 商家 ID + 日期
字段列表 所有维度和原子指标的定义、口径、类型 维度:merchant_id、city_id、category_id指标:order_count、total_amount
计算逻辑 数据的来源和计算公式 数据源:dcl_order_full_detail_di计算逻辑:按 merchant_id、city_id、category_id、dt 分组聚合
更新策略 更新频率、更新方式、SLA 更新频率:日增量SLA:每日 8:00 前产出
存储配置 存储引擎、分桶策略、分区策略 存储引擎:Beluga MOR分桶字段:merchant_id分区字段:dt
权限控制 不同角色的访问权限 所有业务线可读,只有数据基建组可写
血缘关系 上下游依赖关系 上游:dcl_order_full_detail_di下游:127 个应用模型
质量规则 数据质量校验规则和告警阈值 order_count > 0、total_amount > 0
逻辑层的核心价值
  • 统一口径:所有使用该组件的应用都共享完全相同的计算逻辑,从根源上解决 “一个指标多个版本” 的问题
  • 契约化使用:组件对外提供稳定的接口,内部实现的修改对下游透明
  • 全局可发现:所有组件都在元数据中心可搜索,业务人员可以快速找到需要的数据
  • 自动化治理:基于元数据自动实现血缘追踪、影响分析、质量监控等治理功能
2. 第二层:物理存储层(组件的实体)

这是数据组件的实际载体,存储预计算好的结构化数据。一个逻辑组件通常对应 1-4 个物理存储实体,分别满足不同的查询性能和时效性需求。

物理存储实体的类型与对应关系
组件类型 物理实体数量 物理实体类型 存储引擎 典型延迟 适用场景
多维明细组件 3 个 离线明细表实时明细表热点缓存 Beluga MORDoris UniqueRedis T+11 分钟10ms 即席分析实时明细查询高并发查询
轻度汇总组件 4 个 离线汇总表实时汇总表预计算 Cube热点缓存 Beluga COWDoris AggregateKylinRedis T+11 分钟<1s10ms 离线报表实时监控高并发固定报表接口服务

示例:商家交易汇总组件的完整物理映射

  • 逻辑组件 ID:comp_merchant_trade_summary_di
  • 离线物理表:dcl_merchant_trade_summary_di(Beluga COW 表,T+1 更新)
  • 实时物理表:r_dcl_merchant_trade_summary_mi(Doris Aggregate 表,1 分钟更新)
  • 预计算 Cube:kylin_merchant_trade_cube(Kylin,T+1 更新,固定维度组合)
  • 缓存层:Redis 中缓存的 Top1000 商家实时交易数据(10 秒过期)
3. 与传统视图、物理表的本质区别
对比项 数据组件 传统物理表 传统视图
存在形式 逻辑 + 物理双层 纯物理 纯逻辑
数据存储 预计算,有实际存储 有实际存储 无实际存储,每次查询计算
查询性能 高(直接读预计算数据) 高 低(每次都要计算)
口径一致性 全局统一 差(不同表口径不同) 中(同一视图口径一致)
复用性 极高(一次建模,全公司复用) 低(通常为特定业务设计) 中(可复用但性能差)
元数据完整性 极高(包含所有治理信息) 低(只有表结构) 低(只有视图定义)
变更影响 自动级联变更 手动修改所有下游 自动级联但性能差
多引擎支持 自动适配 Spark/Doris/Kylin 单一引擎 单一引擎

二、数据组件的存储方式

美团数据组件的存储完全基于流批一体的架构设计,形成了 “离线存储 + 实时存储 + 预计算层 + 缓存层” 的四层存储体系,不同层之间通过统一元数据中心进行管理和同步。

1. 离线组件存储:Beluga 引擎(基于 Hudi 深度改造)

Beluga 是美团基于 Apache Hudi 0.14 版本深度改造的增量存储引擎,是离线数据组件的核心存储载体。

(1)存储引擎选择
  • 多维明细组件:使用 MOR(Merge On Read)表

    • 优势:支持高效的行级更新和删除,写入性能好
    • 适用场景:数据频繁更新,需要保留最细粒度的明细数据
  • 轻度汇总组件:使用 COW(Copy On Write)表

    • 优势:查询性能极高,与普通 Parquet 表一致
    • 适用场景:数据更新频率低,查询频繁的汇总数据
(2)核心存储优化策略
  • 两层分桶设计(美团特色):将原生 Hudi 的单层 Bucket 改为两层分桶

    • 第一层:按分析对象主键哈希分桶(如 merchant_id)
    • 第二层:按时间哈希分桶
    • 解决了原生 Hudi 单表文件数量上限问题,支持单表 PB 级数据存储
  • 分桶策略:所有组件都按分析对象的主键进行分桶

    • 优势:大幅提升查询和关联性能,避免全表扫描
    • 分桶数量:根据数据量大小设置为 128、256、512 等 2 的幂次
  • 分区策略:按日期分区,部分高频查询的组件按小时分区

    • 优势:支持分区裁剪,只扫描需要的时间范围数据
  • 压缩算法:使用ZSTD 压缩算法,压缩比可达 1:5 以上,大幅减少存储成本

  • 索引选择:使用 Bucket Index,将 Upsert 性能提升 10 倍以上

    • 原理:对主键进行哈希计算,直接定位到对应的文件组,无需扫描所有文件
  • 智能 Compaction:根据数据写入频率和查询模式自动调整 Compaction 策略

    • 对于写入频繁的表:增加 Compaction 频率,减少 Log File 数量
    • 对于查询频繁的表:优先执行 Compaction,提高查询性能
(3)文件组织形式

一个 Beluga 表的目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
dcl_merchant_trade_summary_di/
├── .hoodie/ ### Hudi元数据目录
│ ├── timeline/ ### 时间线,记录所有操作
│ ├── metadata/ ### 元数据表(0.14+版本,包含文件、列统计、布隆过滤器)
│ └── ...
├── dt=2026-05-27/ ### 日期分区
│ ├── bucket_0_00000.parquet ### 第一层分桶文件
│ ├── bucket_1_00000.parquet
│ └── ...
├── dt=2026-05-26/
└── ...
2. 实时组件存储:Apache Doris(深度定制版)

美团是 Apache Doris 的主要贡献者之一,将 Doris 作为实时数据组件的核心存储引擎。

(1)存储引擎选择
  • 实时明细组件:使用 Unique 模型

    • 优势:支持主键去重和更新,保证数据的唯一性
    • 适用场景:实时接入 Binlog 等有主键的更新数据
  • 实时汇总组件:使用 Aggregate 模型

    • 优势:支持预聚合,写入时自动合并相同维度的数据
    • 适用场景:实时指标计算,如实时订单量、实时交易额
(2)核心存储优化策略
  • 分桶策略:与离线组件保持一致,按分析对象的主键分桶

  • 分区策略:按日期 + 小时分区,支持更细粒度的时间范围查询

  • 存储格式:使用 Doris 自研的列式存储格式,支持向量化执行

  • 数据生命周期:实时数据默认保留 7 天,高频查询的保留 30 天,超过的自动归档到 Beluga 离线存储

  • 索引优化:

    • 前缀索引:默认对前 36 个字节建立前缀索引
    • 布隆过滤器:对高基数列建立布隆过滤器,加速等值查询
    • 倒排索引:对字符串列建立倒排索引,加速模糊查询
  • Compaction 优化:实现了分层 Compaction 机制,将小文件合并为大文件,提高查询性能

  • 冷热数据分离:热数据(最近 3 天)存储在 SSD,冷数据存储在 HDD,降低存储成本

3. 预计算层:Apache Kylin

对于高频固定维度的查询,美团会自动生成 Kylin 预计算 Cube,作为数据组件的补充存储。

  • 适用场景:高并发、低延迟的固定维度报表
  • 自动生成机制:达芬奇平台会分析用户的查询模式,识别高频的维度组合,自动在后台生成对应的 Kylin Cube
  • 优势:查询延迟 < 1 秒,支持每秒数万次的并发查询
  • 更新频率:T+1 全量更新,部分重要指标支持小时级更新
4. 缓存层:Redis 集群

对于高并发的接口服务和实时大屏,美团会将热点数据缓存到 Redis 集群中。

  • 适用场景:TopN 排行榜、实时大盘、高并发 API 接口

  • 缓存策略:

    • 热点数据自动缓存:系统自动识别访问频率高的数据,自动缓存到 Redis
    • TTL 自动过期:根据数据的更新频率设置不同的过期时间(10 秒 - 1 小时)
    • 缓存击穿保护:使用布隆过滤器防止缓存击穿
  • 优势:查询延迟 < 10ms,支持每秒数十万次的并发查询

三、数据组件的三种使用方式

数据组件设计了三种不同的使用方式,分别面向业务人员、数据工程师和应用系统,实现了 “人人都能使用数据” 的目标。

1. 业务人员:可视化拖拽,零 SQL 使用

这是最主要的使用方式,80% 以上的报表需求由业务人员通过这种方式完成,完全不需要编写 SQL。

完整操作流程
  1. 打开美团 BI 平台或达芬奇智能构建平台
  2. 在组件库中搜索并选择需要的数据组件(如 “商家交易汇总组件”)
  3. 从组件的字段列表中拖拽需要的指标(如订单量、交易额)和维度(如日期、城市、品类)
  4. 设置过滤条件(如日期 = 近 7 天、城市 = 北京)
  5. 点击 “生成报表”,平台自动完成以下工作:
    • 解析查询语义
    • 通过智能查询路由选择最优的物理存储和引擎
    • 自动生成优化后的 SQL
    • 提交任务到集群执行
    • 将结果可视化展示为表格、柱状图、折线图等
  6. 保存报表或分享给其他用户
示例:生成商家交易日报表

整个过程耗时不超过 5 分钟,不需要编写一行 SQL。

2. 数据工程师:SQL 查询与组件开发

数据工程师可以通过标准 SQL 直接查询数据组件,也可以在开发新组件时复用现有组件。

(1)标准 SQL 查询示例
1
2
3
4
5
6
7
8
9
10
11
-- 查询2026年5月北京地区各品类的交易额和订单量
SELECT
category_id,
category_name,
SUM(order_count) AS order_count,
SUM(total_amount) AS total_amount
FROM comp_merchant_trade_summary_di -- 使用逻辑组件名查询,不是物理表名
WHERE dt BETWEEN '2026-05-01' AND '2026-05-31'
AND city_id = '100' -- 北京
GROUP BY category_id, category_name
ORDER BY total_amount DESC;

重要注意事项:数据工程师应该通过逻辑组件名查询,而不是直接查询底层物理表。达芬奇平台会自动根据查询特征选择最优的物理表和引擎,保证查询性能和口径一致性。

(2)组件间引用示例

开发新组件时,可以直接引用现有组件作为数据源,避免从原始数据开始开发:

1
2
3
4
5
6
7
8
9
10
11
-- 开发"城市交易汇总组件",复用"商家交易汇总组件"
INSERT OVERWRITE TABLE comp_city_trade_summary_di PARTITION (dt='${dt}')
SELECT
city_id,
city_name,
SUM(order_count) AS order_count,
SUM(total_amount) AS total_amount,
SUM(user_count) AS user_count
FROM comp_merchant_trade_summary_di -- 复用现有组件
WHERE dt = '${dt}'
GROUP BY city_id, city_name;
3. 应用系统:统一数据服务 API 调用

应用系统可以通过统一数据服务层的 RESTful API 调用数据组件,获取所需的数据。

(1)API 接口规范
1
2
3
4
5
6
7
GET /api/v1/data/component/{component_id}
参数:
- dimensions: 维度列表,逗号分隔
- metrics: 指标列表,逗号分隔
- filters: 过滤条件,JSON格式
- startTime: 开始时间
- endTime: 结束时间
(2)调用示例
1
2
### 获取2026年5月北京地区的交易数据
curl "https://data.meituan.com/api/v1/data/component/comp_merchant_trade_summary_di?dimensions=category_id&metrics=order_count,total_amount&filters={"city_id":"100"}&startTime=2026-05-01&endTime=2026-05-31"
(3)优势
  • 统一口径:所有应用系统使用相同的数据口径
  • 权限控制:统一的权限管理,控制不同应用的访问权限
  • 流量控制:限流、熔断、降级机制,保证服务稳定性
  • 监控告警:全面的监控和告警,及时发现和解决问题

四、数据组件的全生命周期管理

为了避免组件膨胀和维护困难,美团建立了严格的数据组件全生命周期管理流程。

1. 需求提出与评审
  • 业务方或数据工程师提交组件需求,明确需求背景、分析对象、字段列表和使用场景
  • 数据架构师组织评审,评估组件的通用性和复用价值
  • 优先复用现有组件,只有当现有组件无法满足需求时才允许新建
2. 模型设计与注册
  • 在达芬奇平台中设计组件模型,定义维度、指标、计算逻辑和存储配置
  • 注册到元数据中心,生成全局唯一的组件 ID
  • 组织模型评审,确保组件设计符合规范和复用性要求
3. 开发与测试
  • 达芬奇平台自动生成 DDL 和 DML 代码
  • 数据工程师编写必要的 UDF 实现复杂业务逻辑
  • 进行单元测试、集成测试和数据验证,确保数据准确性和性能
4. 发布与上线
  • 发布到测试环境,邀请业务方验证
  • 验证通过后发布到生产环境
  • 通知所有相关用户组件上线
5. 迭代与维护
  • 根据业务需求的变化迭代优化组件
  • 监控组件的运行状态、数据质量和使用情况
  • 定期优化组件的性能和存储
6. 下线与归档
  • 对于连续 6 个月没有被使用的僵尸组件,自动发送下线通知
  • 通知所有下游用户,给出替代方案
  • 下线后保留 30 天数据,然后归档到低成本存储

五、最佳实践与避坑指南

1. 组件粒度设计:黄金法则与反模式
  • 黄金法则:一个组件对应一个且仅一个分析对象,80% 的需求能直接使用该组件
  • 反模式 1:过粗:一个组件包含多个分析对象,导致复用性差
  • 反模式 2:过细:拆分出太多小组件,导致应用层需要关联大量组件
2. 避免组件膨胀
  • 建立严格的组件审批流程,控制新组件的创建
  • 每季度清理一次僵尸组件
  • 合并功能相似的组件
  • 优先通过派生指标满足个性化需求,而不是新建组件
3. 依赖管理
  • 遵循单向依赖原则:只能依赖更底层的组件,不能依赖上层组件
  • 禁止循环依赖
  • 尽量减少依赖的数量,一个组件的依赖不超过 3 个
4. 性能优化
  • 按查询频率最高的字段进行分桶和分区
  • 对于高频查询的维度组合,预计算成轻度汇总组件
  • 合理设置数据生命周期,及时清理过期数据
  • 使用 ZSTD 压缩算法,减少存储成本和 IO 开销
5. 口径变更管理
  • 组件口径变更必须升级版本号
  • 提前通知所有下游用户,给出迁移时间
  • 采用灰度发布策略,先发布新版本,再逐步切换流量
  • 保留旧版本至少 3 个月,保证向下兼容

六、总结

数据组件是美团数仓 3.0 的灵魂,它通过 “逻辑语义层 + 物理存储层” 的双层设计,完美解决了传统数仓 “口径不一致、重复建设严重、开发效率低下” 的痛点。

通过数据组件层的建设,美团实现了:

  • 开发效率提升 50% 以上:新需求响应周期从 3-5 天缩短到 1 天以内
  • 数据口径不一致问题减少 80%:所有应用使用同一套原子指标
  • 计算资源节省 30%:避免了重复计算,提高了资源利用率
  • 维护成本大幅降低:只需要维护一套公共组件,而不是成千上万的业务表

对于正在进行数仓升级的企业来说,数据组件化是一个非常值得借鉴的设计思路。它不需要推翻现有的数仓架构,可以在现有 CDL 层的基础上逐步改造,先从最核心、复用性最高的分析对象开始,逐步扩展到所有业务领域。

美团数仓 3.0 开发范式:两个核心问题的精准解答

一句话结论:80% 以上的报表需求确实不需要写 SQL,业务人员只需拖拽指标和维度即可生成;但数据组件层的基础建设仍然需要 SQL / 代码,只是开发模式从 “逐需求写 SQL” 变成了 “配置化生成 SQL + 少量 UDF 定制”。

一、报表层:真的不用写 SQL,拖拽即可生成

1. 核心实现原理:语义层屏蔽技术细节

美团数仓 3.0 通过统一语义层彻底隔离了业务用户与底层技术。业务人员看到的不是表和字段,而是标准化的 “指标” 和 “维度”,所有 SQL 生成、引擎选择、数据关联都由达芬奇平台自动完成。

完整的自助报表生成流程
1
2
3
4
5
6
7
8
9
10
11
用户操作:拖拽指标(订单量、交易额) + 维度(日期、城市、品类) + 过滤条件(近7天)
↓
平台解析:提取查询语义 → 匹配数据组件 → 生成逻辑执行计划
↓
智能路由:选择最优引擎(Spark/Doris/Kylin)和物理表
↓
自动生成SQL:生成对应引擎的优化SQL
↓
执行查询:提交任务到集群执行
↓
返回结果:可视化展示为表格、图表或仪表盘
2. 不同场景的 SQL 依赖程度
报表类型 占比 是否需要写 SQL 说明
标准固定报表 50% ❌ 完全不需要 基于预定义的指标和维度,一键生成
自助分析报表 30% ❌ 完全不需要 业务人员自由组合指标和维度,平台自动处理
复杂多维度分析 15% ⚠️ 可选 平台提供高级表达式编辑器,支持简单公式计算
特殊定制化需求 5% ✅ 需要 涉及跨域关联、复杂逻辑计算,需数据工程师介入

美团内部数据:80% 以上的报表需求由业务人员自助完成,无需数据工程师介入;新需求响应周期从 3-5 天缩短到 1 小时以内。

3. 关键能力支撑
(1)统一指标平台
  • 所有指标都在平台中注册,每个指标只有一个官方口径
  • 指标自带维度、计算逻辑、数据来源和更新频率
  • 支持派生指标的自动计算,如 “客单价 = 交易额 / 订单量”
(2)自动关联能力
  • 基于元数据中心的主外键关系,自动关联多个数据组件
  • 自动冗余维度属性,避免用户手动写 JOIN
  • 支持星型、雪花型、星座型等多种模型的自动关联
(3)虚拟表达式技术
  • 允许用户通过声明式语法定义复杂指标,如 “近 30 日复购率”
  • 系统自动将复杂指标拆解为底层原子指标的计算逻辑
  • 无需编写 SQL,即可实现多步骤的复合计算
4. 实际操作示例

生成 “2026 年 5 月北京地区各品类外卖交易额报表” 的完整步骤:

  1. 打开美团 BI 平台,点击 “新建报表”
  2. 在指标库中搜索并选择 “交易额”
  3. 在维度库中选择 “日期”、“城市”、“品类”
  4. 设置过滤条件:日期 = 2026-05-01 至 2026-05-31,城市 = 北京
  5. 点击 “生成报表”,平台自动生成 SQL 并执行
  6. 选择可视化方式(柱状图、折线图、表格),保存报表

整个过程不需要编写一行 SQL,耗时不超过 5 分钟。

二、数据组件层:配置化为主,代码为辅

数据组件层是数仓 3.0 的核心,它的开发模式与 2.0 时代有本质区别:从 “逐需求写 SQL” 变成了 “一次配置,多处复用”。大部分简单组件完全不需要写代码,只有复杂的业务逻辑才需要编写 UDF。

1. 数据组件的标准化开发流程
1
2
3
4
5
6
7
8
9
10
11
12
13
需求分析:识别分析对象(商家、用户、订单) → 梳理业务逻辑
↓
模型设计:在达芬奇平台中定义组件的维度、指标、计算逻辑
↓
可视化配置:通过ER图配置表关联关系、聚合规则、更新策略
↓
自动生成代码:平台自动生成Spark/Flink SQL和任务配置
↓
复杂逻辑定制:编写UDF实现平台不支持的特殊计算逻辑
↓
测试验证:验证数据准确性和性能
↓
发布上线:注册到元数据中心,供所有业务线使用
2. 不同复杂度组件的代码依赖程度
组件类型 占比 是否需要写代码 开发方式
简单统计类组件 60% ❌ 完全不需要 纯可视化配置,自动生成 SQL
中等复杂度组件 30% ⚠️ 少量 UDF 配置为主,少量自定义函数
复杂业务逻辑组件 10% ✅ 需要 配置 + 大量自定义代码

美团内部数据:数据组件的平均复用率达到 90% 以上,一个组件平均被 15 个以上的应用调用;开发一个新组件的平均时间从 2.0 时代的 3 天缩短到 4 小时。

3. 核心开发能力支撑
(1)可视化建模工具
  • 提供 ER 图可视化界面,拖拽式设计组件模型
  • 自动识别主外键关系,生成 JOIN 逻辑
  • 支持维度、指标、计算规则的可视化配置
(2)多引擎自动代码生成
  • 基于统一语义配置,自动生成 Spark、Flink、Doris 等多引擎的 SQL
  • 同一套逻辑可以同时生成离线和实时任务
  • 自动优化 SQL 执行计划,提升性能
(3)公共 UDF 库
  • 沉淀了数千个通用业务 UDF,覆盖大部分常见计算场景
  • 支持 UDF 的统一注册、版本管理和共享
  • 复杂逻辑只需编写一次,所有组件都可以调用
4. 实际开发示例

开发 “商家交易汇总组件” 的完整步骤:

  1. 在达芬奇平台中创建新组件,名称为dcl_merchant_trade_summary_di

  2. 配置分析对象为 “商家”,粒度为 “商家 + 日期”

  3. 配置数据源为dcl_order_full_detail_di(订单全链路明细组件)

  4. 配置聚合维度:merchant_id、city_id、category_id、dt

  5. 配置原子指标:

    • order_count = COUNT(order_id)
    • valid_order_count = COUNT(CASE WHEN order_status = 'VALID' THEN order_id END)
    • total_amount = SUM(total_amount)
  6. 点击 “生成代码”,平台自动生成以下 Spark SQL:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    INSERT OVERWRITE TABLE dcl_merchant_trade_summary_di PARTITION (dt='${dt}')
    SELECT
    merchant_id,
    city_id,
    category_id,
    '${dt}' AS dt,
    COUNT(order_id) AS order_count,
    COUNT(CASE WHEN order_status = 'VALID' THEN order_id END) AS valid_order_count,
    SUM(total_amount) AS total_amount
    FROM dcl_order_full_detail_di
    WHERE dt = '${dt}'
    GROUP BY merchant_id, city_id, category_id;
  7. 配置数据质量规则和调度策略

  8. 测试验证通过后,发布上线

整个过程中,数据工程师没有编写一行 SQL,所有代码都是平台自动生成的。

三、2.0 vs 3.0 开发范式对比

维度 数仓 2.0 数仓 3.0
开发主体 数据工程师 数据工程师 + 业务人员
开发模式 逐需求写 SQL 一次开发组件,多次复用
报表开发 数据工程师写 SQL 生成报表 业务人员拖拽指标生成报表
需求响应周期 3-5 天 1 小时以内
数据工程师工作内容 80% 时间写 SQL,20% 时间做设计 20% 时间写 UDF,80% 时间做设计和治理
代码量 每个需求平均 100 + 行 SQL 每个组件平均 10 行以内 UDF
复用率 <30% >90%
口径一致性 差,同一个指标多个版本 好,所有应用使用同一套指标

四、常见误区澄清

误区 1:数仓 3.0 不需要数据工程师了

真相:数据工程师的角色不是消失了,而是升级了。他们从重复的 SQL 开发工作中解放出来,专注于更有价值的工作:

  • 设计通用的数据组件
  • 治理数据资产,保证数据质量
  • 优化平台性能和稳定性
  • 解决复杂的业务逻辑问题
误区 2:所有需求都能自助完成

真相:还有约 20% 的复杂需求需要数据工程师介入,包括:

  • 跨多个业务域的复杂关联分析
  • 涉及机器学习模型的预测类指标
  • 特殊的业务规则和计算逻辑
  • 性能要求极高的定制化报表
误区 3:数据组件层不需要写任何代码

真相:简单组件不需要写代码,但复杂组件仍然需要编写 UDF。不过,代码量比 2.0 时代减少了 90% 以上,而且代码只需要写一次,就能被所有应用复用。

五、总结

美团数仓 3.0 的本质是 “将重复的工作自动化,将专业的工作平台化”:

  • 对于业务人员:报表层完全屏蔽了 SQL 和技术细节,实现了 “人人都是数据分析师”
  • 对于数据工程师:组件层将重复的 SQL 开发变成了配置化工作,让他们能够专注于更有价值的数据资产建设

这种开发范式的转变,不仅大幅提升了开发效率,更从根源上解决了数据口径不一致的问题,为业务的快速迭代提供了强大的数据支撑。

数仓 2.0 vs 3.0

美团外卖数仓 2.0 到 3.0 升级全解析技术文档

文档版本:v1.0

更新日期:2026-05-27

适用范围:大数据开发、数据架构师、数据产品经理

文档说明:本文档基于美团外卖公开技术分享整理,详细拆解数仓从 2.0 标准化分层到 3.0 工具化、流批一体升级的完整过程,包含架构设计、核心技术、设计理念及落地效果。


目录

  1. 升级背景:数仓 2.0 的核心痛点
  2. 架构升级:从 “人工建模” 到 “工具驱动”
  3. 核心技术突破
  4. 核心设计理念
  5. 落地效果与业务价值
  6. 未来演进方向
  7. 附录:关键技术对比表

一、升级背景:数仓 2.0 的核心痛点

美团外卖数仓 2.0(2016-2019 年)通过明确分工、分层和主题标准,彻底解决了 1.0 时代 “烟囱式” 开发导致的数据口径混乱、重复建设严重的问题。但随着外卖业务爆发式增长(日订单量突破千万级),新的矛盾逐渐凸显:

1. 应用层与集市层过度膨胀
  • 基础层(ODS/IDL/CDL)由数据基建组统一维护,但应用层(MDL/ADL)完全下放给各业务团队独立开发
  • 不同业务线为满足相似需求,重复建设大量宽表和汇总表,库表数量呈指数级增长
  • 数据口径严重不一致:某业务线曾出现 “同一个交易额指标有 17 个不同计算版本” 的情况,数据信任度大幅下降
2. 开发效率与资源成本失衡
  • 60% 以上的 ETL 开发资源消耗在应用层的重复劳动上,核心数据资产建设投入不足
  • 全量计算模式导致资源浪费严重:大表 Merge 任务经常占用集群 70% 以上的计算资源
  • 需求响应周期长:简单的指标变更需要 3-5 天才能上线,无法支撑快速迭代的业务需求
3. 实时能力严重不足
  • 2.0 时代以离线数仓为主,实时数据采用 “Storm+Redis” 的点对点开发模式
  • 没有统一的实时数仓分层,逻辑重复、资源浪费、运维困难
  • 无法满足业务对实时监控、实时营销、实时风控的核心需求
4. 数据治理体系滞后
  • 元数据管理不完善,数据血缘不完整,问题排查平均耗时超过 2 小时
  • 数据质量监控主要依赖人工,故障发现和修复周期长
  • 缺乏统一的指标管理体系,指标定义和计算口径不透明

二、架构升级:从 “人工建模” 到 “工具驱动”

数仓 3.0 的总体愿景是 “用建模工具替代人工开发”,通过架构重构和技术创新,实现数仓建设的自动化、标准化和智能化。

1. 整体架构对比
维度 数仓 2.0 数仓 3.0
核心理念 标准化分层,人工建模 工具化驱动,数据组件化
架构分层 ODS→IDL→CDL→MDL→ADL(五层) 基础层→数据组件层→应用层(三层)
计算模式 离线全量计算为主 离线增量 + 实时流计算结合
开发模式 人工编写 SQL,全流程手动 工具自动生成 SQL,配置化开发
数据服务 多引擎独立服务 统一数据服务层,智能路由
数据治理 事后治理,人工为主 事前治理,自动化为主
2. 数仓 2.0 架构图(文字版)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌─────────────────────────────────────────────────────────┐
│ ADL 应用数据层 │
│ (报表、大屏、接口、导出,各业务线独立开发) │
├─────────────────────────────────────────────────────────┤
│ MDL 集市数据层 │
│ (业务线定制化宽表、汇总表,重复建设严重) │
├─────────────────────────────────────────────────────────┤
│ CDL 公共数据层 │
│ (维度建模、原子指标、轻度汇总,基建组统一维护) │
├─────────────────────────────────────────────────────────┤
│ IDL 数据集成层 │
│ (数据清洗、标准化、脱敏,基建组统一维护) │
├─────────────────────────────────────────────────────────┤
│ ODS 操作数据存储层 │
│ (原始数据接入,T+1全量同步为主) │
└─────────────────────────────────────────────────────────┘
3. 数仓 3.0 架构图(文字版)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌─────────────────────────────────────────────────────────┐
│ 应用层 │
│ ├─ 统一数据服务层(API网关、智能路由、权限控制) │
│ ├─ 自助分析平台(拖拽式建模、自动生成SQL) │
│ └─ 业务应用(报表、大屏、特征工程、数据接口) │
├─────────────────────────────────────────────────────────┤
│ 数据组件层(3.0核心创新) │
│ ├─ 多维明细组件(商家交易、用户行为、订单全链路) │
│ ├─ 轻度汇总组件(原子指标、基础维度组合) │
│ └─ 指标中心(统一指标定义、口径管理、版本控制) │
├─────────────────────────────────────────────────────────┤
│ 基础层 │
│ ├─ 实时接入层(Kafka+Flink,Binlog/日志实时同步) │
│ ├─ 离线接入层(Hudi/Beluga,增量Merge) │
│ └─ 统一存储层(HDFS+Hudi+Doris,流批一体存储) │
└─────────────────────────────────────────────────────────┘
4. 离线数仓架构重构
(1)基础层:统一数据接入与标准化
  • ODS 层升级:引入流式数据集成架构,通过 Canal 采集 Binlog 到 Kafka,再通过 Flink 实时写入 Hive,替代原来的 T+1 全量同步
  • IDL 层优化:按照业务过程划分主题(交易、用户、商家、配送等),统一数据标准和字段命名,屏蔽底层业务系统变化
  • 增量计算框架:引入 HIDI 架构(美团内部基于 HDFS 开发的增量数据处理框架),实现增量数据的高效 Merge,将大表合并时间从 2-3 小时缩短到 1 小时以内
(2)数据组件层:3.0 最核心的创新

定义:将原子指标、基础维度和多维明细数据封装成可复用的 “数据组件”,是连接基础层和应用层的桥梁。

建设原则:

  • 分析对象唯一封装:每个组件对应一个明确的分析对象(如商家交易、用户行为)
  • 原子指标标准化:组件内只包含原子指标,派生指标在应用层计算
  • 高内聚低耦合:组件内部逻辑高度聚合,组件之间通过主键关联

组件类型:

  • 多维明细组件:以实体为中心,包含该实体的所有属性信息(如商家多维明细组件)
  • 轻度汇总组件:以 “实体 + 行为” 为中心,包含该行为的原子指标(如商家交易汇总组件)
(3)应用层:工具化自动建模

应用层不再直接依赖基础层,而是通过数据组件拼接生成。提供应用层建模工具,支持:

  • 数据组件裁剪:只选择需要的指标和维度
  • 自动维度关联:根据元数据自动关联维表并冗余维度属性
  • 按需聚合计算:支持上卷、下钻和复合指标计算
  • 多模型拼接:将多个小模型拼接成完整的应用宽表
5. 实时数仓架构建设

3.0 时代首次构建了统一的实时数仓体系,采用 “以 Kappa 为基、以 Lambda 为补” 的混合架构:

实时数仓架构图(文字版)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─────────────────────────────────────────────────────────┐
│ 业务数据源 │
│ ├─ 业务数据库Binlog(订单、支付、商家) │
│ └─ 用户行为日志(点击、浏览、下单) │
├─────────────────────────────────────────────────────────┤
│ Kafka 消息队列 │
│ (统一数据接入,按主题分区存储) │
├─────────────────────────────────────────────────────────┤
│ 实时明细层(R-ODL) │
│ (Flink清洗、过滤、标准化,按主题划分) │
├─────────────────────────────────────────────────────────┤
│ 实时汇总层(R-CDL) │
│ (Flink计算多维度实时指标,形成统一实时指标池) │
├─────────────────────────────────────────────────────────┤
│ 实时OLAP层(Apache Doris) │
│ (Unique模型/聚合模型,支持历史+实时统一查询) │
├─────────────────────────────────────────────────────────┤
│ 实时应用 │
│ (实时大屏、实时营销、实时风控、实时特征) │
└─────────────────────────────────────────────────────────┘
流批结合的实践
  • 日志类数据:走实时流计算链路,用于实时大屏、实时特征等低延迟场景
  • 业务类数据:走实时 OLAP 链路,利用 Doris 的 Unique 模型和聚合模型解决业务状态变更和回撤计算问题
  • 历史数据:通过离线数仓批量导入 Doris 的历史分区,实现 “历史 + 实时” 的统一查询

三、核心技术突破

1. 计算引擎升级
(1)离线计算:从 Hive 全面迁移到 Spark
  • 2017 年启动迁移,目前 95% 以上的离线任务运行在 Spark 上
  • 核心优势:
    • 算子丰富,支持更复杂的业务逻辑
    • 中间结果内存缓存,避免频繁磁盘 IO
    • 资源复用,申请的 Executor 可以重复利用
  • 效果:整体计算资源节省 20% 以上,任务平均运行时间缩短 30%

深度定制 Flink 引擎,针对外卖场景做了大量优化:

  • 状态管理优化:自研增量 Checkpoint + 异步上传机制,将 Checkpoint 时间从分钟级缩短到秒级
  • 跨作业状态复用:支持多个作业共享同一个状态后端,避免重复计算
  • 动态资源弹性伸缩:根据作业负载自动调整并行度,提高资源利用率
  • SQL 层增强:统一 UDF/UDTF/UDAF 注册中心,提供面向业务语义的流式指标 DSL
  • 效果:实时任务开发效率提升 50%,端到端延迟从秒级降低到毫秒级
2. 存储引擎创新
(1)OLAP 引擎:从 Kylin 到 Doris 的双引擎架构
  • Kylin:保留用于预计算场景,支持高并发、低延迟的固定维度查询
  • Doris:引入用于灵活分析场景,解决 Kylin 预计算周期长、维度组合受限的问题

美团对 Doris 的核心改进:

  • 实现 Join 谓词下推的传递性优化,解决多表关联查询性能问题
  • 优化数据导入性能,支持百万级 / 秒的实时数据写入
  • 增强聚合模型和 Unique 模型,支持业务状态的快速还原
  • 效果:明细查询响应时间从分钟级缩短到秒级,支持任意维度的即席分析
(2)增量存储:Beluga 引擎(基于 Hudi 改造)

为解决离线数仓全量 Merge 效率低的问题,美团基于 Apache Hudi 0.12 版本深度改造了内部存储引擎 Beluga。

核心能力:

  • 支持事务管理、主键约束和 CDC 能力
  • 支持按行和按列的部分更新
  • 提供统一的流批读写接口
  • 两层分桶设计,解决原生 Hudi 文件数量上限问题
  • 独立 MetaServer 服务,提升元数据操作性能

应用场景:

  • 离线数仓最新快照事实表的生产
  • 增量数据的高效合并和计算
  • 流批一体的数据湖存储
3. 建模工具链
(1)基础层建模工具
  • 基于元数据中心,自动记录业务过程、表关联关系、实体对象和分析对象
  • 自动生成基础层表的 DDL 和 DML 代码
  • 提供模型设计的可视化界面,支持拖拽式建模
(2)自助查询工具
  • 用户只需选择需要的指标和维度,工具自动构建逻辑宽表
  • 根据逻辑宽表自动匹配最佳的物理模型(明细、汇总或预计算)
  • 生成最优的查询语句并执行,将结果返回给用户
  • 收集用户查询行为,反过来指导数据组件的建设(如根据高频查询组合优化组件)
(3)应用层建模工具
  • 支持通过配置化方式生成应用层宽表和汇总表
  • 自动处理维度关联、指标计算和数据聚合
  • 提供版本管理和数据回溯功能
  • 自动生成任务调度和监控配置
4. 元数据与数据治理
(1)统一元数据中心
  • 覆盖全链路的元数据管理,包括表、字段、指标、维度、任务、血缘等
  • 提供元数据的搜索、浏览和编辑功能
  • 支持元数据的版本管理和变更通知
(2)全链路数据血缘
  • 自动采集数据加工过程中的血缘关系,支持字段级别的血缘追溯
  • 问题排查时,可以快速定位数据异常的源头
  • 支持影响分析,评估表或字段变更对下游的影响
(3)智能数据治理
  • 统一指标管理:建立五层指标治理框架(业务过程→原子指标→派生指标→复合指标→应用指标),实现指标的统一管理和共享
  • 智能物化:根据用户查询行为自动构建和优化物化视图,平衡查询性能和存储成本
  • 数据质量监控:提供自动化的数据质量校验规则,支持实时监控和告警
  • 资源优化:自动识别冷数据和冗余表,提供存储和计算资源的优化建议

四、核心设计理念

1. 工具化替代人工

将数仓建设中重复、机械的工作(如表结构生成、SQL 编写、任务调度)通过工具自动化完成,让数据工程师从繁琐的开发工作中解放出来,专注于业务逻辑和数据质量的提升。

2. 数据组件化

将数据按照 “分析对象” 进行封装,形成可复用的数据组件。这类似于软件开发中的 “组件化” 思想,通过组件的拼接和组合快速构建数据应用,避免重复开发,提高开发效率。

3. 流批一体

统一离线和实时的计算逻辑、数据口径和存储格式,实现 “一套逻辑、两种运行模式”。这样不仅可以降低开发和维护成本,还可以保证离线和实时数据的一致性。

4. 数据驱动建模

通过收集用户的查询行为和使用习惯,自动分析哪些指标和维度组合是高频使用的,从而指导数据组件的建设和优化。这种 “自下而上” 的建模方式比传统的 “自上而下” 建模更加贴合业务需求。

5. 分层解耦

通过基础层、数据组件层和应用层的分层设计,实现了 “基础层面向业务、数据组件层面向分析、应用层面向应用” 的解耦。这样当业务系统发生变化时,只需要修改基础层;当分析需求发生变化时,只需要修改数据组件层;当应用需求发生变化时,只需要修改应用层,提高了系统的可维护性和扩展性。


五、落地效果与业务价值

  1. 开发效率大幅提升:新需求响应周期从 3-5 天缩短到 1 天以内,简单的指标变更可以实现小时级上线
  2. 资源成本显著降低:通过增量计算和资源优化,整体计算资源节省 30% 以上,存储资源节省 25% 以上
  3. 数据口径一致性提高:统一的指标管理体系和数据组件层,使得数据口径不一致的问题减少了 80% 以上
  4. 查询性能显著提升:OLAP 查询平均响应时间从分钟级缩短到秒级,支持千万级数据的实时分析
  5. 实时能力全面增强:构建了覆盖全业务的实时数仓体系,支持端到端毫秒级的实时数据服务,满足了业务对实时监控、实时营销、实时风控的需求

六、未来演进方向

美团外卖数仓 3.0 仍在持续演进中,未来的发展方向包括:

  • AI 赋能数仓:利用大模型技术实现自然语言查询、自动建模和智能问题排查
  • 湖仓一体深化:进一步统一数据湖和数据仓库的存储和计算,实现更高效的流批一体
  • 数据资产化:建立数据资产的评估和定价体系,实现数据价值的量化和变现
  • 云原生架构:全面迁移到云原生架构,实现资源的弹性伸缩和按需付费

附录:关键技术对比表

表 1:离线计算引擎对比
维度 Hive Spark
计算模型 MapReduce(磁盘 IO 密集) DAG(内存计算)
任务启动时间 慢(秒级) 快(毫秒级)
中间结果存储 磁盘 内存 + 磁盘
资源复用 差(每个任务独立 JVM) 好(Executor 复用)
SQL 支持 完善 更完善(支持更多高级语法)
适用场景 大规模离线批处理 复杂 ETL、交互式分析、机器学习
表 2:实时计算引擎对比
维度 Storm Flink
计算模型 纯流处理 流批一体
状态管理 弱(需要第三方存储) 强(内置状态后端)
Checkpoint 机制 简单(全局快照) 完善(增量 Checkpoint、异步上传)
Exactly-Once 保证 弱 强
SQL 支持 有限 完善(标准 SQL + 流式扩展)
适用场景 简单实时处理 复杂实时计算、流批一体
表 3:OLAP 引擎对比
维度 Kylin Doris
核心思想 预计算(Cube) MPP + 列式存储
查询延迟 极低(亚秒级) 低(秒级)
维度灵活性 差(预定义维度组合) 好(支持任意维度组合)
数据更新 慢(T+1) 快(实时更新)
存储成本 高(Cube 膨胀) 中(列式压缩)
适用场景 固定维度报表、高并发查询 即席分析、实时查询、多表关联
表 4:存储引擎对比
维度 传统 Hive 表 Hudi/Beluga 表
更新方式 全量重写分区 行级增量更新
事务支持 弱 强(ACID)
增量查询 不支持 支持
时间旅行 不支持 支持
小文件问题 严重 自动合并
适用场景 静态数据、批量加载 动态数据、CDC 同步、流批一体

美团数仓 2.0 vs 3.0:优势对比与选型决策指南

核心结论先行:数仓 3.0 不是 2.0 的 “完全替代”,而是 “特定场景下的升级方案”。2.0 在架构简单性、开发灵活性、小团队适配性、一次性需求处理上仍有不可替代的优势;3.0 则在大规模数据治理、复用性、实时性、长期可维护性上碾压 2.0。选型的本质是平衡 “短期开发效率” 与 “长期治理成本”。

一、数仓 2.0 相较 3.0 的核心优势

很多人认为 3.0 是 2.0 的全面升级,但实际上 2.0 的很多设计恰恰是 3.0 的痛点所在。以下是 2.0 仍然显著优于 3.0 的 6 个方面:

1. 架构极简,学习和运维成本极低
2.0 的优势
  • 行业通用标准:ODS→IDL→CDL→MDL→ADL 五层架构是数据仓库的 “通用语言”,任何有经验的数据工程师都能在 1 天内上手
  • 技术栈单一:核心只需要 Hive/Spark + 调度系统 (Azkaban),不需要掌握 Flink、Hudi、Doris、建模工具等一整套复杂技术栈
  • 运维简单:只需要维护 Hadoop 集群和调度系统,没有 3.0 那么多的组件依赖和服务需要运维
  • 问题排查直观:数据流转是线性的,从 ODS 到 ADL 一层一层往下找,问题定位非常容易
3.0 的对应痛点
  • 学习曲线陡峭:需要掌握流批一体、增量计算、数据组件化、元数据驱动等多个新概念,新人上手需要 1-3 个月
  • 技术栈复杂:需要维护 Flink 集群、Hudi/Beluga 存储、Doris OLAP 引擎、元数据中心、建模工具等多个系统
  • 运维难度大:任何一个组件出问题都会影响整个数仓,比如 Hudi 的 Compaction 任务失败会导致所有下游任务延迟
  • 问题排查困难:自动生成的 SQL、组件间的隐式依赖、流批混合计算都会增加问题排查的难度
2. 开发门槛低,能快速响应临时需求
2.0 的优势
  • 纯 SQL 开发:所有任务都是用 SQL 编写,不需要掌握 Java/Scala 等编程语言
  • 自由灵活:可以根据需求任意编写 SQL,没有任何约束,适合处理各种特殊的、一次性的需求
  • 快速上线:一个简单的报表需求,从开发到上线只需要几个小时
  • 不需要基建投入:不需要先建设组件层、建模工具等基础设施,直接从原始数据开始开发
3.0 的对应痛点
  • 基建依赖重:必须先建设好数据组件层和建模工具,才能开始开发应用
  • 约束多:必须按照组件化的规范来开发,不能自由编写 SQL,很多特殊需求无法直接实现
  • 流程复杂:新需求需要先申请组件、配置模型、生成任务,流程比 2.0 复杂很多
  • 临时需求处理慢:对于一次性的、临时的分析需求,3.0 的开发效率反而比 2.0 低
3. 数据血缘清晰,治理成本可控
2.0 的优势
  • 血缘关系直观:表之间的依赖是显式的,通过 SQL 的 JOIN 关系就能清晰看到
  • 影响分析简单:修改一张表,只需要看哪些任务直接依赖它,就能评估影响范围
  • 数据质量容易保障:可以在每一层都加入数据质量校验,确保数据从下到上的准确性
  • 治理成本线性增长:随着表数量的增加,治理成本是线性增长的,不会出现指数级爆炸
3.0 的对应痛点
  • 隐式依赖多:应用层是通过组件拼接生成的,表之间的依赖是隐式的,很难通过传统的血缘工具追踪
  • 自动生成 SQL 导致血缘断裂:建模工具自动生成的 SQL,很多血缘工具无法正确解析
  • 影响分析困难:修改一个组件,可能会影响成百上千个应用,很难准确评估影响范围
  • 组件膨胀问题:如果管理不善,组件数量会快速增长,导致治理成本指数级上升
4. 适合小数据量和低并发场景
2.0 的优势
  • 全量计算简单可靠:在数据量不大(日增量 < 100GB)的情况下,全量计算比增量计算更简单、更可靠
  • 没有小文件问题:全量计算每天生成一个完整的分区,不会产生大量小文件
  • 查询性能足够:对于低并发的报表和分析需求,Hive 的查询性能完全足够
  • 存储成本低:不需要存储多个版本的数据和增量日志,存储成本比 3.0 低
3.0 的对应痛点
  • 增量计算复杂:需要处理数据更新、删除、回撤等问题,逻辑比全量计算复杂很多
  • 小文件问题严重:频繁的增量写入会产生大量小文件,需要额外的 Compaction 任务来合并
  • 存储成本高:Hudi 需要存储多个版本的数据和增量日志,存储成本比 2.0 高 30%-50%
  • 资源开销大:Flink 实时任务需要长期占用资源,Compaction 任务也会消耗大量计算资源
5. 业务定制化能力强
2.0 的优势
  • 可以深度定制:对于业务逻辑非常特殊的需求,可以在 MDL 层进行深度定制,满足各种个性化需求
  • 不受组件限制:不需要依赖公共组件,可以根据业务需求自由设计表结构和计算逻辑
  • 适合创新业务:对于快速迭代的创新业务,业务逻辑变化非常快,2.0 的灵活性可以更好地适应这种变化
  • 容易与第三方系统集成:可以很容易地将数据导出到 MySQL、Redis 等第三方系统,满足各种业务系统的需求
3.0 的对应痛点
  • 组件化限制了灵活性:公共组件是为了满足通用需求设计的,很难满足特殊的个性化需求
  • 修改成本高:如果公共组件不满足需求,需要修改组件,而组件的修改会影响所有下游应用
  • 不适合快速变化的业务:创新业务的逻辑变化非常快,组件的迭代速度跟不上业务的变化速度
  • 集成复杂:3.0 的统一数据服务层增加了与第三方系统集成的复杂度
6. 过渡平滑,风险可控
2.0 的优势
  • 成熟稳定:经过了十几年的发展,技术非常成熟,几乎没有什么坑
  • 风险可控:即使出了问题,也很容易回滚和修复
  • 可以逐步升级:可以在 2.0 的基础上逐步引入 3.0 的技术,比如先引入 Flink 做实时计算,再引入 Hudi 做增量存储
  • 人才充足:市场上有大量熟悉 2.0 架构的数据工程师,招聘容易
3.0 的对应痛点
  • 技术相对较新:很多技术还在快速发展中,存在不少坑
  • 升级风险大:从 2.0 全面升级到 3.0 是一个巨大的工程,风险很高,一旦失败会影响整个公司的数据业务
  • 回滚困难:一旦切换到 3.0 架构,很难再回滚到 2.0
  • 人才稀缺:熟悉 3.0 架构的高级数据工程师非常稀缺,招聘难度大,成本高

二、2.0 vs 3.0 核心优势对比表

维度 数仓 2.0 数仓 3.0 更优者
架构复杂度 低(五层标准架构) 高(组件化 + 流批一体 + 工具链) 2.0
学习成本 低(1 天上手) 高(1-3 个月上手) 2.0
开发门槛 低(纯 SQL) 高(需要掌握多种技术) 2.0
开发灵活性 高(自由编写 SQL) 中(受组件和工具约束) 2.0
临时需求响应速度 快(几小时) 慢(需要基建支持) 2.0
运维成本 低 高 2.0
问题排查难度 低 高 2.0
数据血缘清晰度 高 中 2.0
小数据量性能 好 一般 2.0
存储成本 低 高 2.0
业务定制化能力 强 中 2.0
技术成熟度 高 中 2.0
升级风险 低 高 2.0
大规模数据处理能力 差 好 3.0
数据复用性 低 高 3.0
数据口径一致性 差 好 3.0
实时性支持 差 好 3.0
长期治理成本 高 低 3.0
高并发查询支持 差 好 3.0
团队协作效率 低 高 3.0

三、选型决策框架:什么时候用 2.0,什么时候用 3.0

选型不是非黑即白的选择,而是要根据团队、业务、数据、需求四个维度综合评估。以下是一个可落地的决策框架:

第一步:评估团队能力和规模
团队情况 推荐架构 说明
数据工程师 < 5 人 优先 2.0 3.0 的基建和运维成本会拖垮小团队
数据工程师 5-20 人 混合架构 核心业务用 3.0,边缘业务用 2.0
数据工程师 > 20 人 优先 3.0 团队有能力支撑 3.0 的基建和运维,3.0 的复用性优势会体现出来
团队技术能力较弱 优先 2.0 3.0 需要较强的技术能力,强行上会导致项目失败
团队技术能力较强 可以考虑 3.0 有能力解决 3.0 遇到的各种技术问题
第二步:评估业务复杂度和数据规模
业务和数据情况 推荐架构 说明
日订单量 < 10 万,日增量 < 100GB 优先 2.0 2.0 的全量计算完全足够,3.0 的优势体现不出来
日订单量 10 万 - 100 万,日增量 100GB-1TB 混合架构 核心交易数据用 3.0 做增量计算,其他数据用 2.0
日订单量 > 100 万,日增量 > 1TB 优先 3.0 2.0 的全量计算会遇到严重的性能瓶颈,资源成本会超过 3.0
业务线 < 3 个,需求简单 优先 2.0 没有严重的重复建设和口径不一致问题
业务线 > 5 个,需求复杂 优先 3.0 重复建设和口径不一致问题会非常严重,3.0 的治理优势会体现出来
创新业务,快速迭代 优先 2.0 2.0 的灵活性可以更好地适应业务的快速变化
成熟业务,稳定运行 优先 3.0 3.0 的可维护性和治理优势更适合成熟业务
第三步:评估需求特性
需求特性 推荐架构 说明
以离线报表为主,T+1 延迟足够 优先 2.0 2.0 完全可以满足需求
需要实时数据,延迟 < 1 小时 优先 3.0 3.0 的流批一体架构是实时数仓的最佳选择
临时需求多,一次性分析多 优先 2.0 2.0 的开发效率更高
固定需求多,长期运行的报表多 优先 3.0 3.0 的复用性和可维护性优势会体现出来
数据口径一致性要求高 优先 3.0 3.0 的统一指标和组件化可以从根源上解决口径不一致问题
高并发查询需求 优先 3.0 3.0 的 Doris OLAP 引擎可以支持高并发查询
第四步:评估长期规划
长期规划 推荐架构 说明
业务快速增长,预计 1 年内数据量翻倍 优先 3.0 提前建设 3.0 架构,避免未来出现性能瓶颈
业务稳定,预计 1 年内数据量变化不大 优先 2.0 不需要为未来不确定的增长付出额外的成本
计划建设统一的数据中台 优先 3.0 3.0 的组件化和服务化架构是数据中台的基础
只是为了满足当前的报表需求 优先 2.0 2.0 是最简单、最经济的解决方案

四、最佳实践:混合架构模式

绝大多数公司都不需要完全从 2.0 切换到 3.0,“核心业务 3.0 + 边缘业务 2.0” 的混合架构是最适合大多数公司的选择。

混合架构的设计原则
  1. 核心业务优先 3.0:将交易、用户、支付等核心业务的数据用 3.0 架构建设,保证数据的一致性和实时性
  2. 边缘业务保留 2.0:将市场、运营、财务等边缘业务的数据保留在 2.0 架构,降低开发和运维成本
  3. 统一数据服务层:建设统一的数据服务层,屏蔽底层 2.0 和 3.0 的差异,为上层应用提供统一的接口
  4. 逐步迁移:随着业务的发展,逐步将 2.0 架构下的核心数据迁移到 3.0 架构
混合架构的落地步骤
  1. 第一阶段:保留现有的 2.0 架构,同时引入 Flink 和 Doris 建设实时数仓,满足实时需求
  2. 第二阶段:选择最核心、复用性最高的业务(如交易),建设数据组件层,试点 3.0 架构
  3. 第三阶段:将试点成功的 3.0 架构推广到其他核心业务,逐步减少 2.0 架构的使用范围
  4. 第四阶段:建设统一的数据服务层和自助分析平台,实现 2.0 和 3.0 的无缝集成

五、常见误区与避坑指南

误区 1:3.0 一定比 2.0 好

真相:3.0 是为了解决大规模数据治理问题而设计的,对于小团队和简单业务来说,3.0 的成本会远远超过收益。很多公司盲目上 3.0,结果基建投入了几百万,业务效率反而下降了。

误区 2:2.0 已经过时了

真相:2.0 的分层架构是数据仓库的基础,即使在 3.0 架构中,基础层仍然沿用了 2.0 的设计理念。2.0 在很多场景下仍然是最简单、最可靠的解决方案。

误区 3:上了 3.0 就能解决所有数据问题

真相:3.0 只是一个技术架构,它可以解决重复建设和口径不一致的问题,但不能解决数据质量差、需求不明确、业务不配合等问题。数据治理是一个长期的过程,不能指望一个技术架构就能解决所有问题。

误区 4:必须完全从 2.0 切换到 3.0

真相:混合架构是大多数公司的最佳选择。完全切换到 3.0 是一个巨大的工程,风险很高,而且很多边缘业务根本不需要 3.0 的能力。

误区 5:3.0 的开发效率一定比 2.0 高

真相:在基建完善的情况下,3.0 的长期开发效率比 2.0 高。但在基建建设阶段,3.0 的开发效率比 2.0 低很多。而且对于临时需求和特殊需求,2.0 的开发效率仍然更高。

六、总结

数仓 2.0 和 3.0 没有绝对的好坏,只有适合不适合。

  • 如果你是小团队、简单业务、数据量小,2.0 是最好的选择,它简单、可靠、成本低,能快速满足业务需求。
  • 如果你是大团队、复杂业务、数据量大,3.0 是最好的选择,它能解决重复建设和口径不一致的问题,降低长期治理成本。
  • 如果你处于两者之间,混合架构是最佳选择,它能平衡短期开发效率和长期治理成本。

选型的关键是不要盲目追求技术先进,而是要根据自己的实际情况,选择最适合自己的架构。技术是为业务服务的,只有能解决业务问题的技术才是好技术。

美团数仓 2.0 与 3.0 使用比例及现状分析(2026 年最新)

核心结论:截至 2026 年 5 月,美团集团整体数仓架构中,3.0 架构占比约 75%-80%,2.0 架构占比约 20%-25%。其中外卖、闪购、到店餐饮三大核心业务线已 100% 完成 3.0 架构改造,剩余 2.0 架构主要集中在边缘业务、历史遗留系统和特殊定制化需求场景。

一、整体使用比例与分布

1. 集团整体比例
架构版本 使用比例 主要承载业务 数据来源
数仓 3.0 75%-80% 外卖、闪购、到店餐饮、酒店旅游、配送、支付 美团技术团队 2025 年 5 月公开分享
数仓 2.0 20%-25% 财务、法务、HR、部分新业务、历史遗留系统 内部技术文档及行业分析
2. 分业务线使用情况
业务线 3.0 覆盖比例 说明
外卖业务 100% 2022 年底完成全量迁移,是 3.0 架构最早落地的业务线
闪购业务 100% 2023 年底完成全量迁移,基于外卖 3.0 架构直接搭建
到店餐饮 100% 2024 年中完成全量迁移
酒店旅游 95% 剩余 5% 为历史财务对账系统
配送业务 90% 剩余 10% 为骑手薪资计算等特殊系统
支付业务 85% 剩余 15% 为金融合规相关系统
新业务(如 Keeta 海外) 80% 新业务直接采用 3.0 架构,但部分本地化系统仍使用 2.0
职能部门(财务、法务、HR) 50% 大部分报表需求仍使用 2.0 架构

二、3.0 架构的核心覆盖范围

1. 数据组件层覆盖情况
  • 已建设1200 + 个核心数据组件,覆盖 95% 以上的公共分析需求
  • 新增数据模型中,60% 以上直接基于数据组件构建,无需从原始数据开发
  • 核心分析对象(用户、商家、订单、骑手、商品)的组件化率达到 100%
2. 技术栈覆盖情况
  • 计算引擎:95% 以上的离线任务运行在 Spark 上,100% 的实时任务运行在 Flink 上
  • 存储引擎:80% 以上的增量数据使用 Beluga(基于 Hudi 改造)存储,70% 以上的 OLAP 查询使用 Doris
  • 工具链:达芬奇智能构建平台已覆盖 80% 以上的数仓开发工作
3. 业务应用覆盖情况
  • 所有核心业务报表(如外卖日报、商家经营报表)均基于 3.0 架构
  • 所有实时应用(如实时大屏、实时营销、实时风控)均基于 3.0 架构
  • 所有算法特征工程均基于 3.0 架构的统一数据组件

三、2.0 架构的主要留存场景

虽然 3.0 架构已经成为主流,但 2.0 架构在以下场景中仍然发挥着重要作用:

1. 历史遗留系统
  • 部分早期建设的财务、法务、HR 系统,由于业务逻辑复杂且稳定,改造收益低
  • 这些系统通常只需要 T+1 级别的报表,对实时性和复用性要求不高
  • 改造风险高,一旦出现问题可能影响公司正常运营
2. 特殊定制化需求
  • 某些业务线有非常特殊的计算逻辑,无法通过通用的数据组件满足
  • 例如:骑手薪资计算、商家分账、金融合规报表等
  • 这些需求通常涉及复杂的业务规则和法律要求,需要高度定制化的开发
3. 一次性临时需求
  • 对于一次性的、临时的分析需求,2.0 架构的开发效率更高
  • 不需要先建设数据组件和建模工具,可以直接从原始数据开始开发
  • 需求完成后可以直接删除,不需要长期维护
4. 小团队和边缘业务
  • 部分小团队和边缘业务,数据量小,需求简单
  • 3.0 架构的基建和运维成本对于这些团队来说过高
  • 2.0 架构简单、灵活、成本低,更适合这些场景

四、未来演进计划

1. 2026-2027 年目标
  • 将 3.0 架构的整体使用比例提升至 90% 以上
  • 完成酒店旅游、配送、支付等业务线的剩余改造
  • 逐步淘汰历史遗留的 2.0 系统
2. 混合架构长期共存

美团官方明确表示,不会完全淘汰 2.0 架构,而是采用 “核心业务 3.0 + 边缘业务 2.0” 的混合架构模式长期共存。这是因为:

  • 2.0 架构在某些场景下仍然具有不可替代的优势
  • 完全淘汰 2.0 架构的成本过高,收益有限
  • 混合架构可以平衡短期开发效率和长期治理成本

五、选型建议(基于美团实践)

根据美团的经验,不同规模和阶段的企业应该选择不同的数仓架构:

1. 适合使用 3.0 架构的情况
  • 数据工程师团队规模 > 20 人
  • 日订单量 > 100 万,日增量数据 > 1TB
  • 业务线 > 5 个,存在严重的重复建设和口径不一致问题
  • 对实时性要求高,需要分钟级甚至秒级的数据更新
  • 计划建设统一的数据中台
2. 适合使用 2.0 架构的情况
  • 数据工程师团队规模 < 5 人
  • 日订单量 < 10 万,日增量数据 < 100GB
  • 业务线 < 3 个,需求简单
  • 主要是 T+1 级别的报表需求,对实时性要求不高
  • 处于业务早期,快速迭代是首要目标
3. 最佳实践:混合架构

对于大多数处于中间阶段的企业,“核心业务 3.0 + 边缘业务 2.0” 的混合架构是最佳选择:

  • 先将最核心、复用性最高的业务(如交易、用户)迁移到 3.0 架构
  • 边缘业务和临时需求继续使用 2.0 架构
  • 随着业务的发展,逐步将更多业务迁移到 3.0 架构

需要我根据你的团队规模、数据量和业务场景,帮你制定一份具体的数仓架构选型和迁移路线图吗?

美团达芬奇平台:智能查询路由核心原理全解

达芬奇平台的统一查询路由是数仓 3.0"逻辑唯一、物理多元" 架构的核心实现。它不是简单的 “条件判断”,而是一个基于语义理解 + 多维度成本模型 + 自学习优化的智能决策系统。其核心目标是:在保证数据口径 100% 一致的前提下,自动为每个查询选择性能最优、成本最低的执行路径。

一、整体架构:三层决策体系

达芬奇查询路由采用 “语义解析层→候选匹配层→成本优化层” 的三层架构,将技术复杂性完全封装,业务用户只需通过统一 API 或可视化界面提交查询,无需感知底层引擎和物理表的差异。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
┌─────────────────────────────────────────────────────────┐
│ 用户查询入口(统一API/可视化拖拽/SQL编辑器) │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ 语义解析层 │
│ ├─ SQL解析器 → 抽象语法树(AST) │
│ ├─ 语义理解 → 提取指标、维度、过滤条件、时间范围 │
│ └─ 口径校验 → 确保查询符合统一指标规范 │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ 候选匹配层 │
│ ├─ 模型匹配 → 找出所有支持该查询的物理模型 │
│ ├─ 维度校验 → 验证模型是否包含所需的所有维度 │
│ └─ 数据新鲜度校验 → 筛选满足时间要求的模型 │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ 成本优化层(核心) │
│ ├─ 统计信息获取 → 从元数据中心获取表和列的统计信息 │
│ ├─ 多引擎成本计算 → 计算每个候选模型的执行成本 │
│ ├─ 最优路径选择 → 选择成本最低的执行路径 │
│ └─ 执行计划生成 → 生成对应引擎的SQL和执行参数 │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ 执行与监控层 │
│ ├─ 引擎执行 → 提交任务到Spark/Doris/Kylin │
│ ├─ 动态降级 → 超时或失败时自动切换到次优路径 │
│ └─ 效果反馈 → 将执行结果反馈给自学习模块 │
└─────────────────────────────────────────────────────────┘

二、决策基础:全维度元数据与统计信息

智能路由的所有决策都基于准确、实时的元数据和统计信息。达芬奇平台构建了一个覆盖全链路的元数据中心,持续收集和更新以下核心信息:

1. 模型元数据(物理模型注册信息)

每个数据组件会注册多个物理模型,对应不同的存储引擎和计算粒度:

模型类型 存储引擎 粒度 更新频率 典型延迟 适用场景
预计算 Cube Kylin 固定维度组合 T+1 <1s 高并发固定维度报表
实时汇总表 Doris Aggregate 常用维度组合 分钟级 <5s 实时监控大屏
离线汇总表 Beluga COW 常用维度组合 T+1 <10s 离线日报 / 周报
实时明细表 Doris Unique 最细粒度 分钟级 <30s 实时明细查询
离线明细表 Beluga MOR 最细粒度 T+1 <5min 即席分析、异常排查

示例:商家交易汇总组件的物理模型注册信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"component_id": "comp_merchant_trade_summary_di",
"physical_models": [
{
"model_id": "model_kylin_merchant_trade_1",
"engine": "kylin",
"dimensions": ["dt", "city_id", "category_id"],
"metrics": ["order_count", "total_amount"],
"update_frequency": "daily",
"data_latency": "8h",
"table_name": "kylin_merchant_trade_cube"
},
{
"model_id": "model_doris_merchant_trade_1",
"engine": "doris",
"dimensions": ["dt", "city_id", "category_id", "merchant_id"],
"metrics": ["order_count", "total_amount", "user_count"],
"update_frequency": "15min",
"data_latency": "15min",
"table_name": "r_dcl_merchant_trade_summary_mi"
},
{
"model_id": "model_beluga_merchant_trade_1",
"engine": "spark",
"dimensions": ["dt", "city_id", "category_id", "merchant_id"],
"metrics": ["order_count", "total_amount", "user_count", "avg_order_amount"],
"update_frequency": "daily",
"data_latency": "8h",
"table_name": "dcl_merchant_trade_summary_di"
}
]
}
2. 数据统计信息

元数据中心会每日全量 + 每小时增量收集以下统计信息,用于成本计算:

  • 表级统计:行数、数据大小、分区数量、文件数量
  • 列级统计:基数(distinct 值数量)、最小值、最大值、空值率、数据分布直方图
  • 分区级统计:每个分区的行数、数据大小、更新时间
  • 索引统计:索引类型、索引大小、索引命中率
3. 引擎性能统计信息

持续收集每个引擎在不同负载下的性能表现:

  • Spark:不同数据量下的扫描速度、聚合速度、Join 速度
  • Doris:不同查询复杂度下的响应时间、并发支持能力
  • Kylin:不同 Cube 大小下的查询响应时间、并发支持能力

三、核心决策流程:四步选出最优路径

第一步:查询语义解析与口径校验

无论用户是通过可视化拖拽还是直接输入 SQL 提交查询,达芬奇平台都会先将其解析为标准化的逻辑查询计划,提取以下关键特征:

  • 指标列表:查询涉及的所有指标(如 order_count、total_amount)
  • 维度列表:查询涉及的所有维度(如 dt、city_id、category_id)
  • 过滤条件:WHERE 子句中的所有过滤条件(如 dt=‘2026-05-27’、city_id=100)
  • 聚合粒度:GROUP BY 的维度组合
  • 时间范围:查询的时间跨度(如近 7 天、近 30 天)
  • 业务要求:隐含的响应时间要求(如报表查询要求 < 10s,即席查询要求 < 1min)

关键创新:虚拟表达式解析

对于 “近 30 日复购率” 这类复杂指标,达芬奇平台会通过虚拟表达式技术自动将其拆解为底层原子指标的计算逻辑:

1
近30日复购率 = 近30天有2次及以上下单的用户数 / 近30天下单用户总数

解析后,系统会自动识别出需要的原子指标(user_count、repeat_user_count)和维度(dt),然后去匹配对应的物理模型。

第二步:候选物理模型匹配

根据解析出的查询特征,系统会从元数据中心筛选出所有满足以下条件的物理模型:

  1. 指标覆盖:模型包含查询所需的所有原子指标
  2. 维度覆盖:模型包含查询所需的所有维度(维度可以多于查询,但不能少)
  3. 数据新鲜度:模型的数据延迟满足查询的时间要求
  4. 时间范围:模型包含查询的时间范围数据

示例:查询 “2026-05-27 北京地区各品类的订单量和交易额”

  • 指标:order_count、total_amount
  • 维度:dt、city_id、category_id
  • 过滤条件:dt=‘2026-05-27’、city_id=100
  • 数据新鲜度要求:T+1

候选模型匹配结果:

  • ✅ Kylin 预计算 Cube(包含所有指标和维度,数据延迟 8h)
  • ✅ Doris 实时汇总表(包含所有指标和维度,数据延迟 15min)
  • ✅ Beluga 离线汇总表(包含所有指标和维度,数据延迟 8h)
  • ❌ Beluga 离线明细表(虽然包含所有指标和维度,但粒度更细,成本更高)
第三步:多维度成本模型计算

这是达芬奇平台最核心的技术。系统会为每个候选模型计算综合执行成本,然后选择成本最低的模型。

1. 成本公式

综合执行成本 = 扫描成本 + 计算成本 + 传输成本 + 并发代价 + 数据准备成本

2. 各成本项详细说明
成本项 计算公式 说明
扫描成本 数据扫描量 * 引擎扫描速度系数 数据扫描量 = 过滤后的数据行数 * 平均行大小引擎扫描速度系数:Doris (1.0) > Spark (0.3) > Kylin (0.1)
计算成本 计算复杂度 * 引擎计算速度系数 计算复杂度由聚合维度数、指标数、函数复杂度决定引擎计算速度系数:Doris (1.0) > Spark (0.4) > Kylin (0.05)
传输成本 结果数据量 * 网络传输系数 结果数据量 = 结果行数 * 平均行大小网络传输系数:10ms/MB
并发代价 当前引擎负载 * 负载系数 当前引擎负载 = 正在运行的查询数 / 最大并发数负载系数:Doris (5.0) > Spark (2.0) > Kylin (10.0)
数据准备成本 数据延迟 * 时间敏感系数 数据延迟 = 当前时间 - 数据最后更新时间时间敏感系数:实时查询 (100) > 离线查询 (1)
3. 引擎权重调整

系统会根据查询类型动态调整各成本项的权重:

  • 高并发报表查询:提高并发代价和响应时间的权重
  • 大数据量即席查询:提高扫描成本和计算成本的权重
  • 实时监控查询:提高数据准备成本的权重

示例:三个候选模型的成本计算

假设查询过滤后的数据量为 10 万行,结果数据量为 100 行,当前各引擎负载均为 30%:

模型 扫描成本 计算成本 传输成本 并发代价 数据准备成本 综合成本
Kylin Cube 10 万行 * 0.1 = 10 3 个维度 * 0.05 = 0.15 100 行 * 10ms/MB ≈ 0 0.3 * 10 = 3 8h * 1 = 8 21.15
Doris 实时表 10 万行 * 1.0 = 100 3 个维度 * 1.0 = 3 100 行 * 10ms/MB ≈ 0 0.3 * 5 = 1.5 15min * 1 = 0.25 104.75
Spark 离线表 10 万行 * 0.3 = 30 3 个维度 * 0.4 = 1.2 100 行 * 10ms/MB ≈ 0 0.3 * 2 = 0.6 8h * 1 = 8 39.8

决策结果:选择 Kylin 预计算 Cube,综合成本最低。

第四步:执行计划生成与动态降级

系统会根据选择的物理模型生成对应引擎的 SQL 和执行参数,然后提交任务执行。如果执行过程中出现以下情况,系统会自动降级到次优模型:

  1. 查询超时(超过预设的超时时间)
  2. 查询失败(引擎故障、资源不足等)
  3. 实际执行时间远超预期(超过成本估算的 2 倍)

降级策略:

  • 同源降级:切换查询引擎,但使用相同的物理表
  • 异构降级:切换到另一个物理模型
  • 精度降级:如果所有模型都失败,返回近似结果(如采样计算)

四、物理表选择的特殊策略

除了基于成本模型的通用选择逻辑,达芬奇平台还针对美团外卖的业务特点,设计了以下特殊的物理表选择策略:

1. 实时 + 离线融合策略

对于需要查询 “历史 + 实时” 数据的场景,系统会自动将查询拆分为两部分:

  • 历史数据(如 7 天前的数据):从离线汇总表查询
  • 实时数据(如最近 7 天的数据):从实时汇总表查询
  • 系统自动将两部分结果合并,返回给用户

优势:既保证了历史数据的查询性能,又保证了实时数据的新鲜度。

2. 预计算优先策略

对于固定维度组合的查询,系统会优先选择 Kylin 预计算 Cube。如果 Cube 不存在,系统会自动记录查询模式,并在后台异步生成对应的 Cube,下次相同查询就会直接命中 Cube。

效果:随着查询次数的增加,预计算 Cube 的覆盖率会越来越高,整体查询性能会持续提升。

3. 大查询降级策略

对于扫描数据量超过 1TB 的大查询,系统会自动将其路由到 Spark 引擎,避免影响 Doris 和 Kylin 的在线服务。同时,系统会限制大查询的并发数,防止集群资源被耗尽。

4. 业务优先级策略

系统会根据业务的优先级分配不同的资源和查询策略:

  • P0 级业务(如实时交易大屏):优先使用 Doris 实时表,保证最低延迟
  • P1 级业务(如日常运营报表):优先使用 Kylin 或 Doris 汇总表
  • P2 级业务(如即席分析):可以使用 Spark 离线表,容忍较高的延迟

五、语义一致性保证

这是达芬奇平台最关键的能力之一。无论查询被路由到哪个引擎和物理表,系统都必须保证返回的结果口径 100% 一致。

1. 口径统一机制
  • 所有指标都在统一指标平台中定义,每个指标只有一个官方口径
  • 所有物理模型的计算逻辑都从统一指标平台自动生成,确保口径一致
  • 系统会定期对不同物理模型的结果进行交叉验证,误差超过 0.05% 时自动告警
2. 函数适配层

系统内置了一个跨引擎函数适配层,自动将统一的函数调用转换为对应引擎的方言。例如:

  • 统一函数:date_diff(dt1, dt2)
  • 转换为 Spark SQL:datediff(dt1, dt2)
  • 转换为 Doris SQL:DATEDIFF(dt1, dt2)
  • 转换为 Kylin SQL:DATE_DIFF(dt1, dt2)
3. 数据校准机制

每天凌晨,系统会用离线计算的全量数据校准实时计算的结果,修正实时计算中可能出现的误差。

六、自学习优化机制

达芬奇平台的路由决策不是静态的,而是会根据实际执行效果持续自学习优化:

  1. 执行效果反馈:每次查询执行完成后,系统会记录实际的执行时间、扫描数据量、资源消耗等信息
  2. 成本模型校准:根据实际执行效果调整成本模型的参数,提高成本估算的准确性
  3. 查询模式分析:分析用户的查询模式,识别高频查询的维度组合,自动生成预计算 Cube
  4. 异常检测:检测异常的查询模式和引擎性能波动,自动调整路由策略

七、实际效果与典型案例

1. 整体效果
  • 路由决策准确率:>95%
  • 平均查询响应时间:从 2.0 时代的 30s 缩短到 3.0 时代的 5s
  • 查询成功率:从 90% 提升到 99.9%
  • 引擎资源利用率:提升 45%
2. 典型案例
案例 1:商家经营日报表
  • 查询内容:商家每日的订单量、交易额、客单价
  • 维度:dt、merchant_id
  • 路由结果:Kylin 预计算 Cube
  • 响应时间:<500ms
  • 说明:这是一个高频固定维度查询,系统自动生成了对应的 Kylin Cube,查询性能极佳
案例 2:实时交易大屏
  • 查询内容:今日实时订单量、交易额、下单用户数
  • 维度:无(全局聚合)
  • 路由结果:Doris 实时汇总表
  • 响应时间:<1s
  • 说明:需要最新的实时数据,Doris 实时汇总表更新频率为 1 分钟,满足要求
案例 3:用户行为分析
  • 查询内容:近 30 天不同年龄段用户的下单偏好
  • 维度:age_group、category_id
  • 路由结果:Spark 离线明细表
  • 响应时间:<3min
  • 说明:这是一个低频次的即席分析查询,维度组合不固定,没有预计算 Cube,使用 Spark 离线明细表查询

八、总结

达芬奇平台的智能查询路由本质上是 “将数据工程师的经验固化到系统中” 的过程。它通过语义理解、成本模型和自学习机制,实现了查询路径的自动最优选择,让业务用户无需关心底层技术细节,只需专注于业务分析。

这种设计带来了三个核心价值:

  1. 性能最优:每个查询都能获得最佳的响应时间
  2. 成本最低:合理利用不同引擎的优势,降低整体资源消耗
  3. 口径一致:从根源上解决了 “一个指标多个版本” 的问题

对于正在建设数仓的企业来说,这种 “逻辑统一、物理多元” 的架构是一个非常值得借鉴的思路。它不需要一步到位替换所有技术栈,可以在现有架构的基础上逐步引入统一查询路由层,先解决最痛点的性能和口径问题。

项目举例

介绍一个典型的因为空值导致数据倾斜的实例

在美团外卖的数据分析里,一个典型的会因为空值导致数据倾斜的场景是:在统计“骑手配送时长”这类和配送效率相关的指标时,使用“骑手ID”(rider_id)字段进行聚合。

为了清楚地展示问题,我虚构了一个具体的分析任务,并模拟了数据分布的场景,来说明空值是如何引发数据倾斜的。

📊 一个典型的聚合统计场景

假设:BI(商业智能)团队需要计算美团各城市骑手的平均配送时长,以此来评估物流效率。这通常需要对订单流水表按 rider_id 进行分组(GROUP BY)聚合。以下是一张简化版的“订单事实表(dwd_order_detail_inc)”:

order_id user_id restaurant_id rider_id … delivery_duration (秒)
10001 U001 R001 RID1001 … 1800
10002 U002 R001 RID1002 … 2100
… … … NULL … 2400
99999 U999 R999 NULL … 2100

🎯 谁在“作祟”? 聚焦 rider_id 字段

这个字段在大数据分析中极易引发数据倾斜,主要是由两个原因造成的:

  • 真实业务场景导致大量空值:rider_id 字段为空,通常是因为订单未被任何骑手接单而取消,或者订单信息在数据流转过程中出现了缺失。这种情况在大促(如618、双11)或恶劣天气等高峰时段尤为普遍。由于订单量大,rider_id为NULL的记录总量会变得非常巨大。
  • 分布式聚合任务中的“分配黑洞”:在大数据计算引擎(如Hive, Spark)中执行 GROUP BY rider_id 时,所有 NULL 值会无视原本的逻辑,被分配到同一个Reduce任务上进行处理-。当参与计算的 NULL 值数据量庞大时,负责处理它们的任务就会不堪重负。而这个任务一旦运行缓慢,就会卡住整个作业的进度,造成典型的数据倾斜。

💥 从“数据异常”到“任务灾难”

这种由一个很小的字段设计问题,引发巨大计算资源的浪费和核心报表产出延误,正是数据倾斜最令人头痛的地方。具体的灾难链条如下:

  1. 极少数Reducers负载失衡:在200个处理并行任务的 Reducer 中,rider_id 为 NULL 的海量数据都会涌向其中唯一的一个 Reducer,使其过载。
  2. 作业完成时间失控:负责处理 NULL 值的 Reducer 成为整个任务的性能瓶颈。比如,其他任务10分钟就能完成,而处理 NULL 的任务可能需要运行数小时。
  3. 下游数据产出延迟:关键分析任务延迟,会直接导致依赖此数据的BI报表不能按时刷新,影响业务决策。对于实时性要求较高的业务监控,甚至可能触发监控告警。

💡 实战解法:三步消灭空值倾斜

在真实的数据处理中,可以通过以下几种方式来解决这一问题:

  • 方案一:数据清洗,赋予“身份”:在ETL(数据抽取、转换、加载)过程中,利用COALESCE等函数将这些NULL值替换为有业务意义的默认值,如 'unknown' 或 '0'。

    sql

    复制下载

    1
    2
    3
    SELECT COALESCE(rider_id, 'unknown') AS rider_id, AVG(delivery_duration) 
    FROM dwd_order_detail_inc
    GROUP BY COALESCE(rider_id, 'unknown');

    这样处理后,所有的NULL就变成了一个统一的标识 unknown。

  • 方案二:排除无效,业务过滤:如果业务分析明确不关心无人接单的订单,可以在聚合前就使用 WHERE 子句将其过滤掉。

    sql

    复制下载

    1
    2
    3
    4
    SELECT rider_id, AVG(delivery_duration) 
    FROM dwd_order_detail_inc
    WHERE rider_id IS NOT NULL
    GROUP BY rider_id;
  • 方案三:添加随机前缀,化“一”为“多”:这是最彻底的办法。通过给NULL值加上随机数,将它们均匀分散到不同的任务中-。

    sql

    复制下载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    SELECT 
    CASE
    WHEN rider_id IS NULL
    THEN CONCAT('null_', CAST(RAND() * 100 AS INT))
    ELSE rider_id
    END AS randomized_rider_id,
    AVG(delivery_duration)
    FROM dwd_order_detail_inc
    GROUP BY
    CASE
    WHEN rider_id IS NULL
    THEN CONCAT('null_', CAST(RAND() * 100 AS INT))
    ELSE rider_id
    END;

    但这个方法的代价是,原本要聚合到一起的NULL值被分散成了多行,最终结果需要进行二次聚合才能得出最终结论。

🔍 如何主动发现潜在“雷区”?

在实际工作中,可以采用一些定量的指标或借助自动化工具来主动发现这些可能引发数据倾斜的字段。

  • 使用“空值率”这个定量指标:在美团的数据治理实践中,他们可能会通过数据探查工具(如DataProfiler)定期计算核心字段的空值率。可以设定一个阈值(例如5%或10%),当字段的空值率持续高于这个阈值,并且该字段在后续ETL流程中经常作为分组或关联(JOIN)键时,就应该自动发出告警,提示存在数据倾斜风险。
  • 利用元数据管理平台:具体实施上,可以依赖于元数据管理平台。这个平台能够捕获数据表、字段的统计信息和血缘关系,通过分析数据分布直方图、字段关联热度等指标,自动标记出高风险字段。

字段为空,但在某些统计需求下又不能简单的过滤掉,是什么统计需求,什么字段,如何处理

好的,我来举一个字段不能简单过滤掉的典型例子:统计“用户下单转化率”时,user_id 字段为空。


场景:美团外卖“用户下单转化率”分析

📌 业务需求

运营团队需要分析不同用户群体的“浏览→下单”转化率,以便针对不同用户类型优化推荐策略。转化率的计算口径是:

text

复制下载

1
转化率 = 下单用户数 / 浏览用户数

其中“浏览”是指当天至少访问了App首页的用户,“下单”是指当天至少提交了一个订单的用户。

⚠️ 问题:user_id 为空的情况

在美团外卖的埋点日志中,未登录用户浏览App时,user_id 字段会是 NULL。这些用户虽然未登录,但他们仍然可能浏览商品、甚至下单(美团允许未登录用户下单,需手机号验证)。因此:

  • 如果直接过滤掉 user_id IS NULL 的记录,就会丢失未登录用户的浏览和下单行为,导致转化率统计偏高(因为分母中剔除了大量未登录浏览用户,但下单用户中也有未登录的,剔除比例可能不一致)。
  • 业务方明确要求:未登录用户必须单独作为一个群体“匿名用户”参与统计,以便评估匿名用户的转化效率,以及后续引导登录的策略效果。
🧩 涉及的字段
  • user_id:用户唯一标识。未登录时为 NULL。
  • 统计维度:user_type(登录用户 vs 匿名用户)、date、channel_id(渠道来源)。

🛠️ 处理方法(不能简单过滤,也不能直接聚合)

方案一:将 NULL 替换为有业务含义的默认值(推荐)

在 ETL 中将 NULL 转换为 'anonymous' 或 '0',使其成为一个合法的分组键。

sql

复制下载

1
2
3
4
5
-- DWD层清洗时处理
SELECT
COALESCE(user_id, 'anonymous') AS user_id,
...
FROM ods_log_inc

这样在后续 GROUP BY user_id 时,所有匿名用户会被聚到同一组,既不会丢失数据,也不会导致倾斜(因为只有一个 'anonymous' 键)。

方案二:保留 NULL 但使用 GROUP BY + 自定义处理

如果数据库引擎支持,可以直接将 NULL 视为一个分组值。大多数数据库(如 Hive、Spark SQL)中,GROUP BY 时 NULL 会单独成一组,不会倾斜(因为只有一个 NULL 组)。但是需要注意:如果匿名用户量极大,这个 NULL 组的数据量可能非常大,但相比加盐方案,这已经是可接受的,因为只有一个组负责所有匿名用户的聚合,可能会成为单点瓶颈,但通常远小于热点倾斜(因为只有一个 reducer)。如果匿名用户量达数亿,仍可能倾斜。因此更推荐方案一。

方案三:加盐法(如果匿名用户数据量极大)

将 NULL 替换为 'anonymous_' + random_salt,然后先打散聚合,再二次聚合。例如:

sql

复制下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- 第一层:加盐打散
SELECT
CASE
WHEN user_id IS NULL
THEN CONCAT('anonymous_', FLOOR(RAND() * 100))
ELSE user_id
END AS salted_user_id,
COUNT(1) AS cnt
FROM logs
GROUP BY salted_user_id

-- 第二层:汇总匿名用户
SELECT
CASE
WHEN salted_user_id LIKE 'anonymous_%' THEN 'anonymous'
ELSE salted_user_id
END AS user_id,
SUM(cnt) AS total
FROM temp
GROUP BY
CASE
WHEN salted_user_id LIKE 'anonymous_%' THEN 'anonymous'
ELSE salted_user_id
END

这能将海量匿名用户打散到多个 reducer 中,再合并结果。


📊 总结对比

方案 适用场景 优点 缺点
替换为默认值 匿名用户量级中等(< 1000万) 简单高效,无倾斜 单个组仍可能数据量过大
保留 NULL + GROUP BY 引擎原生支持,且匿名用户量可控 无需修改数据 可能单点瓶颈
加盐 + 二次聚合 匿名用户量极大(亿级) 彻底打散,无倾斜 实现复杂,需二次聚合

在美团外卖的实际数据场景中,通常采用方案一,并将 'anonymous' 作为一个普通用户处理,同时在用户维度表中增加一行记录 user_id = 'anonymous' 用于关联。这样既保证了统计的完整性,又避免了数据倾斜。

再给一个实例

好的,再给你一个在美团外卖场景中非常经典的例子:统计“优惠券核销率”时,coupon_id 字段为空。


场景:美团外卖“优惠券核销率”分析

📌 业务需求

营销团队需要分析不同优惠券的核销情况,评估发券活动的ROI。核销率的口径是:

text

复制下载

1
核销率 = 某张优惠券被使用的订单数 / 该优惠券被领取的总次数

但这里有一个更常见的分析需求是:整体优惠券核销率,即:

text

复制下载

1
整体核销率 = 使用了任意优惠券的订单数 / 全部订单数

或者按优惠券类型(新客券、满减券、配送券)统计核销率。

⚠️ 问题:coupon_id 为空的情况

在美团外卖的订单事实表中,有一条核心字段 coupon_id,记录该订单使用的优惠券ID。如果订单没有使用任何优惠券,该字段为 NULL。

在统计核销率时:

  • 不能直接过滤掉 coupon_id IS NULL 的订单,因为这些订单正是“未核销”的订单,构成了分母的一部分。
  • 如果过滤掉,算出来的核销率会变成 100%(因为分子分母都只统计使用了优惠券的订单),毫无意义。
  • 业务方需要看到真实的核销率,即包含未使用优惠券的订单作为分母。
🧩 涉及的字段
  • coupon_id:优惠券ID,未使用优惠券时为 NULL。
  • 统计维度:date(日期)、coupon_type(优惠券类型)、restaurant_id(商家)等。

🛠️ 处理方法(不能过滤,需正确处理 NULL 分组)

方案一:将 NULL 替换为有业务含义的默认值(推荐)

在 ETL 或查询时,将 NULL 转换为 'NO_COUPON',使其成为一个合法的分组键。

sql

复制下载

1
2
3
4
5
6
7
-- 查询整体核销率
SELECT
COUNT(CASE WHEN coupon_id IS NOT NULL THEN 1 END) AS used_order_cnt,
COUNT(1) AS total_order_cnt,
COUNT(CASE WHEN coupon_id IS NOT NULL THEN 1 END) / COUNT(1) AS redemption_rate
FROM dwd_order_detail_inc
WHERE dt = '20250517';

如果希望按优惠券类型统计,需要先关联优惠券维度表,此时 NULL 对应的类型应为 '无优惠券':

sql

复制下载

1
2
3
4
5
6
7
8
SELECT 
COALESCE(c.type, '无优惠券') AS coupon_type,
COUNT(DISTINCT o.order_id) AS order_cnt,
COUNT(CASE WHEN o.coupon_id IS NOT NULL THEN 1 END) AS used_order_cnt
FROM dwd_order_detail_inc o
LEFT JOIN dim_coupon c ON o.coupon_id = c.coupon_id
WHERE o.dt = '20250517'
GROUP BY COALESCE(c.type, '无优惠券');
方案二:使用 GROUP BY 时保留 NULL 分组

大多数 SQL 引擎中,GROUP BY 会自然将 NULL 作为一个单独的分组。这样可以不用替换值,直接聚合。

sql

复制下载

1
2
3
4
5
6
SELECT 
coupon_id, -- NULL 会单独成一行
COUNT(1) AS order_cnt
FROM dwd_order_detail_inc
WHERE dt = '20250517'
GROUP BY coupon_id;

但如果需要与优惠券维度表关联,NULL 无法匹配,仍需要处理。

方案三:加盐法(如果 NULL 值极大导致倾斜?)

注意:在这个场景中,NULL 代表“未使用优惠券”,这部分订单量可能非常大(比如占总订单的 80% 以上)。在 GROUP BY coupon_id 时,所有 NULL 会进入同一个 reducer,可能导致数据倾斜。因此需要加盐打散,再二次聚合。

sql

复制下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-- 第一步:加盐打散
SELECT
CASE
WHEN coupon_id IS NULL
THEN CONCAT('NULL_', CAST(RAND() * 100 AS INT))
ELSE coupon_id
END AS salted_coupon_id,
COUNT(1) AS cnt
FROM dwd_order_detail_inc
WHERE dt = '20250517'
GROUP BY salted_coupon_id;

-- 第二步:合并 NULL 组
SELECT
CASE
WHEN salted_coupon_id LIKE 'NULL_%' THEN NULL
ELSE salted_coupon_id
END AS coupon_id,
SUM(cnt) AS order_cnt
FROM temp
GROUP BY
CASE
WHEN salted_coupon_id LIKE 'NULL_%' THEN NULL
ELSE salted_coupon_id
END;

但大多数情况下,NULL 作为一个单独的组,即便数据量很大,也不会严重倾斜,因为只有一个 reducer 处理它,只要该 reducer 的内存和时长可接受,就不需要加盐。如果数据量达到数亿行,则建议加盐。


📊 总结对比

方案 适用场景 优点 缺点
替换为默认值 通用,尤其适合关联维度表 简单,可读性好 可能增加 'NO_COUPON' 维度行
保留 NULL 分组 仅需聚合,不需关联维表 无需修改数据 关联维表时需特殊处理
加盐 + 二次聚合 NULL 数据量极大(亿级) 彻底解决倾斜 实现复杂,需二次聚合

在美团外卖的实际数据仓库中,通常会在 DWD 层对 coupon_id 进行清洗:如果为 NULL,则赋值为 '0' 或 'NO_COUPON',同时在优惠券维度表中维护一行 coupon_id = '0' 表示“无优惠券”。这样后续所有聚合和关联操作都可以统一处理,既简单又高效,且避免了数据倾斜。

实际问题

对于维度表,公司级别生产的就每天直接拷贝到我们的业务库,如果是业务级别的维度表,就自己生成,保证数据的一致性

你这个说法整体方向是合理的,但在实际数据架构实践中,有一些关键点需要补充和细化,才能更好地保证“一致性”并避免数据孤岛。下面我来帮你分析并润色。

✅ 合理性分析

  • 公司级维度表(主数据)统一生产并分发:这是推荐做法。例如客户、产品、组织架构等核心维度,应由主数据管理(MDM)或数据治理委员会统一维护,每天以批处理或增量方式同步到各业务库,确保全公司口径一致。
  • 业务级维度表自行生成:业务部门自己维护一些局部维度(如活动配置、渠道分组、内部项目代码),是合理的。完全依赖公司级生产会过度集中、响应慢。

⚠️ 潜在风险与待补充点

  1. “直接拷贝”可能导致覆盖风险:如果业务库允许对维度表进行修改,每日全量拷贝会抹掉业务侧的定制属性(如本地标签、临时状态)。建议明确“公司级维度表在业务库中是否只读”。
  2. 业务级维度表自行生成可能导致数据孤岛:不同业务部门可能各自生成“客户等级”或“产品分类”,造成口径冲突。应建立一个轻量级的企业维度注册表,登记各业务级维度的定义、负责人、血缘关系。
  3. 一致性如何保障? 需要明确:
    • 公司级维度变更时(例如产品拆分),如何通知下游业务级维度表?
    • 业务级维度是否需要与公司级维度保持外键约束?
  4. 更新策略应细化:每日拷贝是全量还是增量?是否有拉链表保留历史?是否考虑用视图或数据虚拟化代替物理拷贝?

📝 润色后的表述建议

维度表管理策略:

  • 公司级统一维度(如客户、产品、组织等)由主数据管理平台每日生成标准版本,通过数据同步工具以“全量覆盖”或“增量拉取”的方式分发到各业务数据库。业务库中这些维度表只读引用,不进行本地修改,确保全公司口径一致。
  • 业务级维度(如活动配置、临时项目标签)由各业务团队自行维护,但须在企业数据资产目录中登记定义、更新频率及数据血缘,并与公司级维度建立显式的映射关系或依赖规则。
  • 一致性保障:通过定期对账、异常监控和变更通知机制,确保业务级维度与公司级维度的关联键、业务含义保持一致;关键维度发生变更时,触发下游评估与适配流程。

💎 总结

你的原始描述抓住了核心分层思想,但需要补充“只读约束”、“资产登记”、“变更联动”三个关键点,才能构成一个健壮的维度管理规范。如果需要,我可以帮你画一张简单的流程图或给出具体的SOP条目。

待整理

项目实战模板(直接背诵,离线+实时通用)

一、项目名称:电商离线+实时混合数据仓库搭建

1、项目背景

原有数据分散在MySQL业务库、Nginx日志,数据孤立、统计缓慢、无实时监控,T+1报表延迟高,无法支撑业务实时运营;需要搭建统一分层数仓,实现离线复盘+实时大屏双架构。

2、技术栈

DataX、Canal、Hive3.1、Spark3.5、Flink1.19、Kafka3.6、Doris2.1、Redis、Azkaban、Prometheus。

3、个人职责
  1. 数据同步:使用Canal监听MySQL binlog,实时同步增量数据入Kafka;DataX每日全量同步业务库至Hive ODS层。
  2. 离线建模:遵循五分层规范,完成交易、用户、商品主题建模;DWD清洗去重,DWS构建用户行为宽表,DIM维护拉链表缓慢变化维度。
  3. 实时开发:Flink消费Kafka,完成实时清洗、双流join、窗口聚合;构建实时交易宽表,写入Doris,支撑实时大屏。
  4. 性能优化:解决交易表热点key倾斜,采用加盐打散+拆分热点;优化Hive小文件,合并分片;优化Flink水位线解决乱序漂移;P95查询延迟降低60%。
  5. 治理运维:搭建数据质量监控,配置空值、重复、波动告警;梳理数据血缘,下线无效任务;规范字段命名、统一指标口径。
4、项目难点与解决方案
  1. 难点1:订单数据倾斜,热门商家数据量大;方案:拆分热点key、加盐打散、分桶聚合。
  2. 难点2:实时数据乱序、漂移;方案:事件时间+水位线,设置5分钟乱序容忍,迟到数据单独补批。
  3. 难点3:维度频繁变更;方案:DIM层制作拉链表,永久保留历史快照。
5、项目成果

搭建完整离线+实时混合数仓;离线每日生成200+业务报表,实时大屏延迟稳定在3秒内;数据准确率99.95%;任务失败率下降90%;支撑运营、风控、商品分析多业务线。

项目高频问题(重点追问)

(一)去哪儿网机票部门(2016.7-2019.5)

1. 你在去哪儿网负责机票预订、支付、退改签这些核心域,当时遇到过哪些比较棘手的数据质量问题?你是怎么解决的?

参考答案:

当时遇到最多的是数据不一致和脏数据问题。比如退改签数据,因为涉及到多个系统的交互,经常出现状态不同步的情况,导致统计出来的退改签率不准确。

我主要做了三件事:

  • 首先,梳理了所有核心业务流程的数据流向,明确了每个数据字段的来源和含义,制定了统一的数据接入规范。

  • 然后,开发了一套自动化的数据质量校验脚本,在数据接入和 ETL 的每个环节都加入了校验规则,比如主键唯一性、字段值域、业务逻辑校验等。

  • 最后,建立了数据质量告警机制,一旦发现数据异常,会立即发送告警通知,及时排查和修复问题。

    通过这些措施,我们把核心指标的数据错误率从原来的 15% 左右降低到了 1% 以下。

2. 你当时处理日均 5 亿条数据,主要用的是什么技术栈?遇到过哪些性能问题?

参考答案:

当时主要用的是 Hive 和 MapReduce,后期开始引入 Spark。遇到的主要性能问题是任务运行时间长和数据倾斜。

比如有一个机票订单的汇总任务,原来用 MapReduce 跑需要 6 个多小时,经常超时。我把它改写成了 Spark SQL,然后做了一些优化:比如合理设置分区和分桶,对倾斜的 key 进行拆分,调整 Spark 的资源参数等。最后这个任务的运行时间缩短到了 1 个小时以内。

3. 你在去哪儿网参与搭建了全业务流程的报表体系,主要有哪些类型的报表?你是怎么保证报表的准确性和及时性的?

参考答案:

主要有三类报表:

  • 日常运营报表:比如每日的订单量、出票量、退改签率等,给运营和管理层看。

  • 产品分析报表:比如不同航线、不同舱位的销售情况,给产品经理做产品迭代用。

  • 财务结算报表:给财务部门做结算用,对准确性要求最高。

    为了保证准确性,我们建立了多层数据校验机制,从原始数据到最终报表,每个环节都有校验。为了保证及时性,我们对任务进行了优先级划分,核心报表任务优先调度,并且建立了任务监控和告警机制,一旦任务失败或者超时,会立即通知我们处理。

(二)美团外卖事业部(2019.5-2021.6)

1. 你在美团负责用户和订单域的数仓重构,当时为什么要做重构?重构前后有什么变化?

参考答案:

​ 当时 V1.0 数仓主要存在三个问题:

  • 烟囱式开发导致的数据冗余:不同业务线各自开发订单表,全公司有 17 个不同版本的订单事实表,存储冗余超过 80%

  • 指标口径完全混乱:同一个 “有效订单量” 指标有 12 种不同的计算逻辑,业务方经常因为数据不一致吵架

  • 性能瓶颈严重:核心订单汇总任务依赖 5 张大表关联,数据量超过 30 亿条,运行时间经常超过 3 小时

  • 维护成本高:表结构混乱,文档缺失,新人上手非常困难。

    我遇到的最复杂的问题是订单状态变更的一致性问题。原来的订单表是全量快照,每天凌晨同步前一天的最终状态,但很多订单的状态会跨天变更(比如用户凌晨下单,第二天支付),导致统计出来的每日订单量和支付量永远对不上。

    解决方案:

  • 设计了订单流水事实表,不再存储全量快照,而是存储订单的每一次状态变更事件

  • 基于流水表构建了订单最新状态视图,使用 Spark 的窗口函数 row_number () 按订单号分组,取最新的状态

  • 对于历史数据,我写了一个回溯脚本,从业务数据库的 binlog 中还原了过去 3 年的所有订单状态变更事件

  • 为了保证性能,我对订单号进行了分桶,并且使用了分区裁剪,只处理需要更新的分区

    重构之后的变化:

  • 订单状态的准确率从原来的 92% 提升到了 99.99%

  • 统一了数据模型和指标口径,同一个指标在整个公司只有一个计算逻辑。

  • 核心任务性能提升了 60% 以上,核心订单报表的产出时间从 T+2 小时缩短到了 T+45 分钟。

  • 数据复用率提高了,存储成本降低了 20% 左右,维护成本也大大降低。

追问:

1.1 你刚才提到在美团负责用户和订单域的数仓重构,统一了 150 多个业务指标口径。请具体说明:

  1. 当时指标口径混乱到了什么程度?举一个最典型的例子

  2. 你是如何推动全公司统一指标口径的?具体做了哪些工作

  3. 统一口径之后,给业务带来了什么具体的价值

  4. 混乱程度:当时就一个order_count,有着超过10种不同的口径,最极端的情况下,运营部门和财务部门统计的同一天订单量相差超过 20%。有一次因为两个部门的数据不一致,导致一个全国性的运营活动预算审批推迟了一周,直接影响了活动上线。当时下游使用报表字段的时候需要频繁确认,但依然避免不了使用错误导致的数据不一致问题。导致了我们不得不耗费大量精力去排查这些问题。

  5. 推动过程:

    • 首先,成立了一个数据指标治理小组,成员包括了数据团队、产品、运营和财务的核心人员
    • 然后,制定了统一的指标命名规范和计算逻辑规范,对于不同的统计逻辑,分别明确不同的指标定义,比如 “有效订单” 、 “支付订单”、“完成订单”等
    • 接着,在数仓层面下线了所有非标准的订单表,只在DWS层保留了一个统一的订单事实表,所有下游报表的开发必须基于这个基础表。
    • 最后,搭建了元数据管理平台,将所有指标的定义、计算逻辑、负责人都录入平台,并且建立了指标审批流程
  6. 具体价值:

    • 统一口径后,数据问题工单下降了 80%,我们团队每个月节省了超200小时的问题排查时间
    • 所有部门使用同一套数据,再也没有出现过因为数据不一致导致的业务纠纷
    • 数据的可信度大大提升,管理层开始直接基于数据做决策

2. 你刚才说把核心订单报表的产出时间从 T+2 小时缩短到了 T+45 分钟,具体是怎么优化的?

参考答案:

我主要从三个方面做了优化:

  • 数据模型优化:重新设计了订单域的事实表和维度表,采用了星型模型,减少了表之间的关联次数。把原来需要多次关联的大表,提前做了预聚合,生成了中间层表。

  • SQL 优化:对所有核心 SQL 进行了重写,避免了笛卡尔积和全表扫描,合理使用了分区裁剪和分桶。对于数据倾斜的情况,采用了动态分区和随机前缀的方法进行处理。

  • Spark 参数调优

    :根据任务的特点,调整了 executor 的数量、内存大小、cores 数量等参数,开启了动态资源分配和推测执行机制。

  1. 报表详情:这个核心报表是全平台活动效果分析报表,计算逻辑是统计每个活动在不同用户标签、不同地区、不同时间段的曝光量、点击量、下单量、支付量和转化率。它依赖了 5 张核心表:活动维表(100 万条)、用户标签维表(5 亿条)、曝光日志表(每日 20 亿条)、点击日志表(每日 5 亿条)、订单事实表(每日 3000 万条),总数据量约每日 3TB。
  2. 瓶颈定位方法:
    • 首先通过 Spark Web UI 的 DAG 图,发现整个作业有 3 个 shuffle 阶段,其中第二个 shuffle 阶段的运行时间占了总时间的 70%
    • 然后查看 Task 的执行时间,发现有 10 个 Task 的运行时间超过了 1 小时,而其他 Task 只需要 10 分钟左右,确认存在严重的数据倾斜
    • 最后通过spark.eventLog.enabled生成的事件日志,统计出倾斜最严重的 10 个活动 ID,它们的订单量占了总订单量的 40%
  3. 优化措施及效果:
    • 前置过滤优化:在 SQL 最外层就过滤掉了不参与活动的订单和无效的曝光日志,减少了 60% 的输入数据量,运行时间缩短了 30 分钟
    • 分桶优化:将订单事实表和点击日志表按活动ID进行分桶,分桶数设置为 2000,避免了 shuffle 阶段的数据重分区,运行时间缩短了 20 分钟
    • MapJoin 优化:将活动维表和用户标签维表广播到所有 executor,在 map 端进行 join,消除了两个 shuffle 阶段,运行时间缩短了 15 分钟
    • 数据倾斜治理:将倾斜最严重的 10 个活动 ID 单独拆分出来,使用加盐法进行处理,运行时间缩短了 10 分钟
    • 资源调优:将 executor 的数量从 100 增加到 200,每个 executor 的 core 数从 2 增加到 4,运行时间缩短了 5 分钟
    • 总优化效果:从原来的 120 分钟缩短到 40 分钟,比预期的 45 分钟还要好
  4. 最大技术挑战:最大的挑战是用户标签维表太大,无法直接进行 MapJoin。用户标签维表有 5 亿条数据,大小超过了 10GB,直接广播会导致 driver OOM。
    • 我的解决方案是:将用户标签维表按用户ID的哈希值分成 10 个分片,然后将订单表也按同样的哈希值分成 10 个分片,分别进行 join,最后再合并结果。
    • 这样既避免了 shuffle,又解决了大维表无法广播的问题,同时还提高了并行度。

追问:

2.1 你在美团通过数据倾斜治理、分区裁剪、Spark SQL 调优,将核心订单报表产出从 T+2 小时优化至 T+45 分钟。请详细说明:这张报表的业务逻辑、依赖表与整体数据体量;你如何定位性能瓶颈?逐项说明每一项优化动作、对应的原理以及各自带来的提速效果。

该报表为全平台活动效果分析报表,核心逻辑是按活动、地区、时间段、用户标签多维度,统计曝光、点击、下单、支付全链路指标并计算转化类指标。共依赖 5 张核心表:活动维表、用户标签维表、曝光日志表、点击日志表、订单 DWD 明细事实表,单日全量数据约 3TB。

一、瓶颈定位
  1. 依托 Spark Web UI 查看 DAG 与 Stage 耗时,全作业共 3 个 Shuffle 阶段,第二个 Shuffle 阶段耗时占整体 70%,是核心瓶颈;
  2. 查看 Task 运行时长,发现两极分化:10 个 Task 运行超 1 小时,其余仅十余分钟,判定存在严重数据倾斜;
  3. 解析 Spark Event Log 日志,统计 Key 分布,定位出 10 个头部活动 ID 为热点 Key,对应订单数据占整体 30%,是倾斜根源;同时观测到该阶段大量数据磁盘溢写、GC 频繁,进一步放大耗时。
二、分维度优化及效果
  1. 前置过滤:在 SQL 最前端过滤无效曝光、非活动订单、测试数据,输入数据量减少 60%,运行时长缩短 30 分钟;
  2. 表分桶优化:将订单事实表、点击日志表统一按活动ID分桶,桶数与任务并行度对齐设为 2000,规避 Shuffle 重分区开销,时长再缩短 20 分钟;
  3. MapJoin 优化:对体积较小的活动维表启用广播 Join,在 Map 端完成关联,消除 2 个 Shuffle 阶段,时长缩短 15 分钟;
  4. 热点倾斜治理:拆分 10 个倾斜活动 ID 单独处理,对热点 Key 追加随机前缀做二次打散,再二次聚合,时长缩短 10 分钟;
  5. 资源精细化调优:结合集群水位,将 Executor 数量由 100 调至 400,单 Executor 核数由 2 调至 4,提升整体并行能力,时长缩短 5 分钟。
三、核心挑战与解决方案

最大难点:用户标签维表数据量过大,远超 Spark 默认广播阈值,直接 MapJoin 会引发 Driver OOM。

解决方案:采用分片并行 Join方案,将大维表与主表按统一哈希规则拆分为多个分片,分片内独立执行 Join,最后合并结果;同时搭配分区裁剪,只加载当日增量分区数据,既规避广播上限问题,又不引入额外 Shuffle,彻底解决该卡点。

整体优化后,报表产出时长从 T+2 小时降至 T+45 分钟,任务稳定性也同步提升。

参考答案:

当时主要做了一些业务实时监控指标,比如实时订单量、实时交易额、实时骑手在线数等。

遇到的主要问题:

  • 数据延迟:因为 Kafka 的分区数和 Flink 的并行度设置不合理,导致数据处理延迟比较高。后来我们调整了 Kafka 的分区数和 Flink 的并行度,让它们一一对应,解决了这个问题。
  • 状态管理:有些指标需要计算一天的累计值,状态会越来越大,导致 checkpoint 时间过长。后来我们采用了增量 checkpoint 和 RocksDB 状态后端,并且设置了状态的过期时间,解决了这个问题。
  • 数据一致性:因为网络或者机器故障,可能会出现数据重复或者丢失的情况。我们采用了 Flink 的 Exactly-Once 语义,并且在下游做了幂等处理,保证了数据的一致性。

4. 用户标签维表是怎么设计的,结构是什么样的,为什么这么设计,如何更新

5. 那你在做美团数仓时,Spark 任务数据倾斜是非常高频的问题,你当时遇到过哪些典型的倾斜场景?分别是怎么解决的?给我讲两个最典型的,要结合外卖业务场景。

5.1 null值的数据倾斜

业务场景:衡量骑手的服务质量中有一项是退款率,计算逻辑就是因骑手原因退款订单占支付订单的占比,而有些订单记录会因为运力不足等原因,没有分配到骑手,就导致骑手ID为空。这种情况在极端天气的情况下才比较严重,平时并不会有太明显的数据倾斜,所以在最开始开发时没有注意到这个问题。

5.2 大表join小表

6. 在美团外卖这个项目里,你负责了活动、订单、骑手三大主题域,那在 DWD 层做维度建模时,你是怎么区分事实表和维度表的?

7. 针对骑手这种会频繁变化的维度信息,你具体是怎么设计拉链表的?用的是全量拉链还是增量拉链?

8. 数仓整体架构是如何的,核心表有哪些

(三)微软中国 STCA Bing(2021.6 至今)

1. 你在微软处理 PB 级的全球搜索日志数据,和之前处理国内业务数据有什么不同?

参考答案:

最大的不同主要有三个方面:

  • 数据规模更大:每天都是 PB 级的数据量,而且是全球范围的,对存储和计算的要求都非常高。

  • 数据复杂度更高:涉及到多语言、多时区、多地区的数据,还有各种合规要求,比如 GDPR 等。

  • 对成本更敏感

    :这么大规模的数据,哪怕是 1% 的存储或者计算成本优化,都能节省非常可观的费用。

    所以在微软做开发,不仅要考虑功能和性能,还要特别关注成本和合规问题。

2. 你说通过优化把日志处理任务的整体运行时间缩短了 40%,具体做了哪些优化?

参考答案:

我主要做了以下几个方面的优化:

  • 数据压缩优化:原来用的是 Snappy 压缩,后来我们换成了 ZSTD 压缩,压缩比提高了 30% 左右,不仅节省了存储成本,还减少了网络传输和磁盘 IO 的时间。
  • 数据分区优化:原来的分区粒度比较粗,我们改成了按小时 + 地区的细粒度分区,这样查询的时候可以更精准地进行分区裁剪,减少了需要处理的数据量。
  • 任务拆分优化:把原来一个大的任务拆分成了多个小的任务,并行执行,提高了整体的处理效率。
  • Spark 参数调优:针对 PB 级数据的特点,调整了 Spark 的内存管理模型、shuffle 参数等,避免了频繁的 GC 和 OOM 问题。

3. 你在微软处理全球数据,是怎么解决多语言和多时区的问题的?

参考答案:

  • 多时区问题:我们统一使用 UTC 时间存储所有数据,在计算和展示的时候,再根据用户所在的时区转换成当地时间。这样可以避免因为时区转换导致的数据不一致问题。

  • 多语言问题

    :我们建立了统一的语言编码标准,所有的文本数据都使用 UTF-8 编码。对于需要进行文本分析的内容,我们使用微软自己的多语言 NLP 模型进行处理,支持全球 100 多种语言。

    另外,我们还建立了一套完整的数据验证机制,确保不同时区和不同语言的数据都能被正确处理。

4. 你在微软处理 PB 级的全球搜索日志,和之前在美团处理国内业务数据最大的技术挑战是什么?你说把任务运行时间缩短了 40%,请具体说明你是如何做 Spark 参数调优的,哪些参数的调整带来了最大的性能提升?

最大的技术挑战不是数据量大,而是数据的不均匀性和成本约束。全球搜索日志的数据分布极不均匀,美国和中国的流量占了总流量的 70% 以上,而一些小国家的流量可能只有几万条。如果按照统一的分区策略,会出现严重的数据倾斜。同时,微软对成本控制非常严格,每 1% 的性能提升都意味着几十万美元的成本节约。

我做的 Spark 参数调优主要包括以下几个方面,其中shuffle 优化和内存管理优化带来了最大的性能提升,占了总提升的 60% 以上:

  1. shuffle 优化(贡献最大,约 25% 的提升)
    • 将spark.shuffle.file.buffer从默认的 32KB 调整到 128KB,减少了磁盘 IO 次数
    • 将spark.reducer.maxSizeInFlight从默认的 48MB 调整到 192MB,增加了 reduce 端的并行度
    • 开启了spark.shuffle.compress和spark.shuffle.spill.compress,使用 ZSTD 压缩算法,减少了 shuffle 数据的传输量
  2. 内存管理优化(约 15% 的提升)
    • 将spark.memory.fraction从默认的 0.6 调整到 0.8,因为我们的任务是计算密集型,需要更多的执行内存
    • 将spark.memory.storageFraction从默认的 0.5 调整到 0.3,因为我们不需要缓存太多数据
    • 开启了spark.memory.offHeap.enabled,使用堆外内存,减少了 GC 的压力
  3. 并行度优化(约 10% 的提升)
    • 将spark.default.parallelism从默认的 200 调整到 2000,与 Kafka 的分区数保持一致
    • 对于大分区,使用repartition进行拆分,对于小分区,使用coalesce进行合并

5. 你在微软负责 PB 级全球搜索日志处理,通过 ZSTD 压缩、细粒度分区和 Spark 参数调优,将任务运行时间缩短了 40%。请具体说明:

  1. 原来的日志处理任务是怎么设计的?存在哪些具体的性能问题?
  2. 为什么选择 ZSTD 压缩而不是 Snappy 或 Gzip?它带来了多少压缩比提升和性能提升?
  3. 细粒度分区是怎么设计的?原来的分区粒度是什么?为什么要改成现在的粒度?
  4. 你提到的 Spark 参数调优中,哪一个参数的调整带来了最大的性能提升?为什么?

5.1 你在微软负责 PB 级全球搜索日志处理,原来的日志处理任务是怎么设计的?存在哪些具体的性能问题?请详细说明。

原来的日志处理任务是基于 Spark SQL 开发的离线批处理任务,每天凌晨运行一次,处理前一天的全球搜索日志。

  • 原设计:数据按天分区存储在 HDFS 上,使用 Snappy 压缩。处理流程分为三步:第一步解析原始的 JSON 日志,提取关键字段;第二步过滤掉无效的爬虫日志和测试日志;第三步按地区、语言和搜索关键词进行聚合,生成用户行为分析报表。

  • 存在的具体性能问题:

    1. 执行时间过长:随着搜索量增长,任务执行时间从原来的 4 小时增加到了 12 小时以上,经常无法在早上 8 点前产出报表,影响业务决策。
    2. 任务重试率高:平均每天有 20% 的任务会因为 OOM 或者 shuffle 失败而重试,严重时需要手动干预。
    3. 存储成本高:每天产生的日志数据超过 1PB,Snappy 压缩比只有 2:1 左右,存储成本非常高。
    4. 查询效率低:原来按天分区,查询某个地区或者某个时间段的数据时,需要扫描整个天分区的数据,查询延迟很高。

5.2 为什么选择 ZSTD 压缩而不是 Snappy 或 Gzip?它带来了多少压缩比提升和性能提升?请具体说明。

我们最终选择 ZSTD 压缩,是经过了严格的性能测试和对比分析的,三种压缩算法的对比如下:

  • Snappy:压缩速度最快,但压缩比最低(约 2:1),适合对速度要求极高但对存储不敏感的场景
  • Gzip:压缩比最高(约 4.5:1),但解压速度最慢,CPU 开销大,适合冷数据存储
  • ZSTD:压缩比与 Gzip 相当(约 4:1),但解压速度是 Gzip 的 3-5 倍,CPU 开销低,同时支持分块压缩和随机访问,非常适合热数据处理

除了技术优势外,微软的 Cosmos 平台确实对 ZSTD 有原生的优化支持,能够进一步提升性能。

实际效果:

  • 压缩比从原来的 2:1 提升到了 4:1,存储成本直接降低了 50%
  • 由于数据量减少了一半,网络传输和磁盘 IO 时间也相应减少,任务整体运行时间缩短了 15%
  • 同时,ZSTD 的分块压缩特性使得我们可以直接读取日志的某一部分,而不需要解压整个文件,大大提升了数据查询效率

5.3 细粒度分区是怎么设计的?原来的分区粒度是什么?为什么要改成现在的粒度?请具体说明。

原来的分区粒度是按天分区,每个天分区包含全球所有地区的搜索日志,数据量约 1PB。随着数据量增长,天级分区的问题越来越突出:

  1. 单个分区数据量过大,一个任务需要处理 1PB 的数据,经常出现 OOM 和 shuffle 失败的问题
  2. 查询效率极低,即使只需要查询某个地区 1 小时的数据,也需要扫描整个天分区的 1PB 数据
  3. 无法支持小时级的统计需求,下游业务只能等到第二天才能看到前一天的数据

我们最终改成了按 “小时 + 地区” 的二级分区设计:

  • 一级分区按小时划分,每天 24 个分区
  • 二级分区按地区代码划分,全球共分为 10 个大区
  • 每个分区的数据量约 4TB,正好适合一个 Spark 任务处理

优化效果:

  1. 任务并行度从原来的 1 个提升到 240 个,整体运行时间缩短了 20%
  2. 查询效率提升了 100 倍以上,查询某个地区 1 小时的数据只需要扫描 4TB,而不是 1PB
  3. 支持了小时级的统计需求,下游业务可以在每个小时结束后 15 分钟内看到最新的数据

解决的副作用:小时级分区会产生大量小文件,我们通过在任务最后增加一个合并小文件的步骤,将每个分区的文件数控制在 100 个以内,避免了 NameNode 的压力。

5.4 你提到的 Spark 参数调优中,哪一个参数的调整带来了最大的性能提升?为什么?请具体说明这个参数的作用,以及你是如何确定调整到什么值的。

在所有的 Spark 参数调优中,spark.shuffle.file.buffer 这个参数的调整带来了最大的性能提升,约占总提升的 25%。

参数作用:这个参数控制的是 shuffle 写过程中每个 map 任务的输出缓冲区大小,默认值是 32KB。当缓冲区满了之后,才会将数据溢写到磁盘上。

为什么影响最大:我们的日志处理任务是典型的 IO 密集型任务,shuffle 过程中会产生大量的磁盘 IO。默认的 32KB 缓冲区太小,导致每个 map 任务会频繁地溢写磁盘,产生大量的小文件,严重影响了性能。

如何确定调整值:

  1. 我首先通过 Spark Web UI 查看了 shuffle 阶段的磁盘 IO 统计,发现每个 map 任务平均溢写了 10 次以上
  2. 然后我进行了梯度测试,分别将参数调整为 64KB、128KB、256KB 和 512KB
  3. 测试结果显示,当调整到 128KB 时,溢写次数减少到了 2 次,性能提升最明显;继续增大到 256KB 时,性能提升不明显,反而增加了内存开销
  4. 最终我将这个参数设置为 128KB

实际效果:shuffle 阶段的磁盘 IO 减少了 80%,任务整体运行时间缩短了 15%。

6. 你在微软主导了 Scope SQL 向 Spark Structured Streaming 的技术栈迁移,请说明:原有 Scope SQL 任务存在哪些核心痛点?迁移前整体任务架构、数据流向是怎样的?

原有 Scope SQL 是微软内部专属的数据查询计算组件,任务全部为离线批处理作业,长期运行在 Cosmos 存储与计算集群上,核心痛点分为业务、技术、成本三大类:

  1. 业务时效不足:所有任务按天调度,仅能产出天级统计数据,无法支撑小时级、准实时的数据查询与监控需求,不能满足广告、搜索业务对数据时效性的要求。
  2. 技术栈封闭,扩展性差:Scope 为私有技术,语法、函数、计算引擎均不对外开源,无法对接 Kafka、Flink 等主流实时组件;自定义逻辑、复杂计算扩展难度大,遇到性能问题无开源方案参考,问题排障效率低。
  3. 资源与成本压力:Cosmos 是公司核心集群,整体资源池紧张,高峰期任务排队严重;且 Cosmos 专属硬件与服务定价高,批量离线任务长期运行,整体硬件与运维成本居高不下。
  4. 运维能力薄弱:原生监控、日志排查、故障重试机制不完善,任务失败后定位根因、恢复业务耗时久。

迁移前整体架构与数据流向

底层存储依托微软 Cosmos 分布式存储,全量原始日志、中间结果、最终报表数据均持久化在 Cosmos 中;任务由微软内部统一调度平台进行周期调度,所有计算逻辑通过 Scope SQL 实现。

完整数据流向:

原始搜索 / 广告日志落地至Cosmos 存储 → 调度平台按天触发 Scope 批处理任务 → Scope 引擎读取 Cosmos 数据完成清洗、转换、聚合统计 → 计算结果回写至 Cosmos 对应分区表 → 下游业务报表、分析应用直接读取 Cosmos 数据对外提供服务。

整套架构纯离线闭环,无中间消息队列、无流式计算链路,所有数据流转、计算、存储均依赖微软私有体系。

追问:

6.1 本次迁移包含 100 + 个离线、准实时任务,除了架构改造,你还做了全链路性能优化。请分别说明:针对原有 Scope SQL 任务、迁移后的 Spark Structured Streaming 任务,你采用了哪些具体优化手段?两类优化分别达成了怎样的量化效果?

7. 数据异常的自动化监控具体是怎么做的?

  1. 首先对于历史发生过问题的数据进行了总结,对于可

8. 细粒度分区是怎么设计的?原来的分区粒度是什么?为什么要改成现在的粒度?

9. 你在微软负责 广告日志处理,原来的日志处理任务是怎么设计的?存在哪些具体的性能问题?

10. 为什么选择 ZSTD 压缩而不是 Snappy 或 Gzip?它带来了多少压缩比提升和性能提升?

最终选择 ZSTD 压缩,是经过了严格的性能测试和对比的,三种压缩算法的对比如下:

  • Snappy:压缩速度最快,但压缩比最低(约2:1),适合对速度要求极高,但对存储不敏感的场景
  • Gzip:压缩比最高(约4.5:1),但解压速度最慢,CPU开销大,适合冷数据存储
  • ZSTD:压缩比与Gzip相当(约4:1),但压缩速度是Gzip的3-5倍,CPU开销低,同时支持分块压缩和随机访问,非常适合热数据处理

除了技术优势外,微软的Cosmos平台对ZSTD有原生的优化支持,能够进一步提升性能

  • 压缩比从原来的 2:1 提升到了 4:1,存储成本直接降了50%
  • 由于数据量减少了一半,网络传输和磁盘IO时间也相对减少,任务整体运行时间缩短了15%
  • 同时,ZSTD的分块压缩特性使得我们可以直接读取数据的一部分,而不需要解压整个文件,大大提升了数据查询效率

规范

美团外卖数仓表命名规范(全量详解 + 实战示例)

美团外卖数仓的命名规范是其 2.0 时代标准化建设的核心成果之一,严格遵循 “层级 + 业务 + 类型 + 粒度” 的四段式结构,实现了 “见名知意、自动解析、统一管理” 的目标。这套规范不仅适用于传统的五层架构,也在 3.0 时代得到了继承和优化。

一、核心命名原则

  1. 见名知意:表名必须清晰反映数据的层级、业务主题、数据类型和更新频率
  2. 统一规范:全公司统一标准,禁止自定义缩写和特殊字符
  3. 小写字母:全部使用小写英文字母,单词之间用下划线_分隔
  4. 禁止关键字:禁止使用 SQL 关键字(如order、user、date等)作为表名或字段名
  5. 长度限制:表名长度不超过 64 个字符,字段名长度不超过 32 个字符

二、通用命名规则

1. 基础格式

1
[层级前缀]_[业务主题]_[数据内容]_[数据类型/更新频率]

2. 层级前缀对照表

层级 前缀 说明
ODS 层 ods 操作数据存储层
IDL 层 idl 数据集成层
CDL 层 cdl 公共数据层
MDL 层 mdl 集市数据层
ADL 层 adl 应用数据层
数据组件层 (3.0) dcl Data Component Layer
实时数仓层 r_xxx 实时表前缀,如 r_ods、r_cdl
临时表 tmp 临时计算用表,生命周期≤7 天
中间表 mid 多步骤计算的中间结果表
备份表 bak 数据备份用表
拉链表 zip 缓慢变化维度拉链表

3. 美团专属更新频率后缀

这是美团数仓最具特色的部分,通过后缀明确表的更新频率和数据粒度:

后缀 全称 含义 示例
di Day Incremental 日增量表 ods_order_binlog_di
df Day Full 日全量表 idl_user_info_df
hh Hourly 小时级表 ods_user_click_log_hh
mi Minute 分钟级表 r_ods_order_binlog_mi
wk Weekly 周级表 cdl_merchant_trade_wk
mon Monthly 月级表 cdl_user_active_mon
zip Zipper 拉链表 cdl_user_dim_zip

三、分层命名规范详解(含实战示例)

1. ODS 层(操作数据存储层)

命名格式
1
ods_[数据源类型]_[业务主题]_[数据内容]_[更新频率]
数据源类型说明
  • binlog:业务数据库 Binlog 增量数据
  • log:用户行为日志数据
  • third:第三方接口数据
  • db:业务数据库全量同步数据
  • crawl:爬虫数据
典型示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- Binlog增量数据
ods_binlog_order_di -- 订单表Binlog日增量
ods_binlog_payment_record_di -- 支付记录表Binlog日增量
ods_binlog_merchant_info_di -- 商家信息表Binlog日增量

-- 用户行为日志
ods_log_user_click_hh -- 用户点击日志(小时级)
ods_log_user_view_hh -- 用户浏览日志(小时级)
ods_log_app_startup_hh -- App启动日志(小时级)

-- 第三方数据
ods_third_alipay_notify_di -- 支付宝回调数据
ods_third_map_location_di -- 地图定位数据
ods_third_sms_send_di -- 短信发送记录

-- 全量同步数据
ods_db_user_info_df -- 用户信息表日全量
ods_db_system_config_df -- 系统配置表日全量

2. IDL 层(数据集成层)

命名格式
1
idl_[业务主题]_[数据内容]_[更新频率]
核心特点
  • 去掉数据源类型前缀,因为已经完成了数据标准化
  • 业务主题和数据内容与 ODS 层对应,但结构已经标准化
  • 不包含任何业务计算逻辑,只做清洗和标准化
典型示例
1
2
3
4
5
6
idl_order_di                -- 标准化后的订单日增量表
idl_user_info_df -- 标准化后的用户信息日全量表
idl_merchant_info_df -- 标准化后的商家信息日全量表
idl_payment_record_di -- 标准化后的支付记录日增量表
idl_delivery_record_di -- 标准化后的配送记录日增量表
idl_user_behavior_hh -- 标准化后的用户行为小时表

3. CDL 层(公共数据层)

命名格式
1
cdl_[业务主题]_[数据类型]_[更新频率]
数据类型说明
  • dim:维度表
  • fact:事实表
  • sum:轻度汇总表
  • zip:拉链表
典型示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- 维度表(dim)
cdl_user_dim_df -- 用户维度表(日全量)
cdl_merchant_dim_df -- 商家维度表(日全量)
cdl_goods_dim_df -- 商品维度表(日全量)
cdl_city_dim_df -- 城市维度表(日全量)
cdl_time_dim_df -- 时间维度表(日全量)
cdl_user_dim_zip -- 用户维度拉链表(处理缓慢变化)

-- 事实表(fact)
cdl_order_fact_di -- 订单事实表(日增量)
cdl_payment_fact_di -- 支付事实表(日增量)
cdl_delivery_fact_di -- 配送事实表(日增量)
cdl_user_behavior_fact_hh -- 用户行为事实表(小时级)

-- 轻度汇总表(sum)
cdl_merchant_trade_sum_di -- 商家交易日汇总表
cdl_user_active_sum_di -- 用户活跃日汇总表
cdl_city_order_sum_di -- 城市订单日汇总表
cdl_category_sale_sum_wk -- 品类销售周汇总表

4. MDL 层(集市数据层)

命名格式
1
mdl_[业务线]_[业务主题]_[数据内容]_[更新频率]
核心特点
  • 必须加上业务线前缀,区分不同业务线的数据
  • 包含业务线特有的计算逻辑和指标
  • 粒度通常比 CDL 层粗,以日、周、月汇总为主
典型示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 外卖业务线
mdl_waimai_merchant_daily -- 外卖商家日汇总表
mdl_waimai_user_daily -- 外卖用户日汇总表
mdl_waimai_delivery_city_daily -- 外卖配送城市日汇总表
mdl_waimai_category_weekly -- 外卖品类周汇总表
mdl_waimai_rider_monthly -- 外卖骑手月汇总表

-- 到店业务线
mdl_daodian_shop_daily -- 到店商家日汇总表
mdl_daodian_coupon_daily -- 到店优惠券日汇总表

-- 酒店业务线
mdl_hotel_order_daily -- 酒店订单日汇总表
mdl_hotel_room_daily -- 酒店房间日汇总表

5. ADL 层(应用数据层)

命名格式
1
adl_[业务线]_[应用名称]_[数据内容]_[更新频率]
核心特点
  • 必须加上应用名称,明确数据的使用场景
  • 数据格式和粒度完全匹配应用需求
  • 直接面向报表、大屏、接口等数据应用
典型示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 报表类
adl_waimai_daily_report -- 外卖日报表数据
adl_waimai_weekly_report -- 外卖周报表数据
adl_waimai_monthly_report -- 外卖月报表数据

-- 大屏类
adl_waimai_real_time_screen -- 外卖实时大屏数据
adl_waimai_delivery_screen -- 外卖配送指挥大屏数据

-- 接口类
adl_merchant_ranking_daily -- 商家排行榜日数据
adl_user_portrait_daily -- 用户画像日数据
adl_rider_performance_daily -- 骑手绩效日数据

-- 导出类
adl_waimai_order_export_di -- 外卖订单导出数据
adl_merchant_finance_export_di -- 商家财务导出数据

6. 3.0 数据组件层(DCL 层)

命名格式
1
dcl_[分析对象]_[组件类型]_[更新频率]
核心特点
  • 以分析对象为中心,而不是以业务过程为中心
  • 组件类型分为detail(多维明细组件)和summary(轻度汇总组件)
  • 是连接基础层和应用层的桥梁,可被多个应用复用
典型示例
1
2
3
4
5
6
7
8
9
-- 多维明细组件(detail)
dcl_merchant_trade_detail_di -- 商家交易多维明细组件
dcl_user_behavior_detail_hh -- 用户行为多维明细组件
dcl_order_full_detail_di -- 订单全链路多维明细组件

-- 轻度汇总组件(summary)
dcl_merchant_trade_summary_di -- 商家交易汇总组件
dcl_user_active_summary_di -- 用户活跃汇总组件
dcl_city_trade_summary_di -- 城市交易汇总组件

四、特殊类型表命名规范

1. 临时表与中间表

1
2
3
4
5
6
7
8
-- 临时表(生命周期≤7天)
tmp_20240520_order_calculate -- 日期前缀+业务内容
tmp_user_behavior_20240520 -- 业务内容+日期后缀

-- 中间表(多步骤计算)
mid_order_step1_di -- 订单计算第一步中间表
mid_order_step2_di -- 订单计算第二步中间表
mid_merchant_merge_di -- 商家信息合并中间表

2. 备份表与历史表

1
2
3
4
5
6
7
-- 全量备份表
bak_idl_user_info_20240520 -- bak_原表名_备份日期
bak_cdl_order_fact_20240520 -- 备份CDL层订单事实表

-- 历史归档表
his_ods_order_binlog_2023 -- 2023年订单Binlog归档表
his_idl_payment_record_2023 -- 2023年支付记录归档表

3. 实时数仓表

1
2
3
4
5
6
7
8
9
10
-- 实时ODS层
r_ods_binlog_order_mi -- 订单Binlog分钟级实时表
r_ods_log_user_click_mi -- 用户点击日志分钟级实时表

-- 实时CDL层
r_cdl_order_fact_mi -- 实时订单事实表
r_cdl_merchant_trade_sum_mi -- 实时商家交易汇总表

-- 实时应用层
r_adl_waimai_real_time_screen -- 实时大屏数据

五、字段命名规范

1. 通用字段命名

字段类型 命名规则 示例
主键 xxx_id user_id、order_id、merchant_id
外键 xxx_id city_id、category_id、rider_id
时间字段 xxx_time create_time、update_time、pay_time
日期字段 xxx_date order_date、pay_date、delivery_date
状态字段 xxx_status order_status、pay_status、delivery_status
金额字段 xxx_amount order_amount、pay_amount、refund_amount
数量字段 xxx_count order_count、user_count、goods_count
标识字段 is_xxx is_deleted、is_valid、is_vip
分区字段 dt dt=‘2024-05-20’

2. 字段命名最佳实践

  • 所有时间字段统一使用yyyy-MM-dd HH:mm:ss格式
  • 所有金额字段统一以分为单位,避免浮点运算误差
  • 布尔类型字段使用tinyint类型,1 表示 true,0 表示 false
  • 禁止使用中文、拼音和特殊字符作为字段名
  • 相同含义的字段在所有表中必须使用相同的名称(如所有用户 ID 都叫user_id)

六、完整数据链路命名示例

以 “外卖订单数据从接入到日报表” 的完整链路为例:

1
2
3
4
5
6
1. ODS层:ods_binlog_order_di          -- 原始订单Binlog日增量
2. IDL层:idl_order_di -- 标准化后的订单日增量
3. CDL层:cdl_order_fact_di -- 订单事实表
4. CDL层:cdl_merchant_trade_sum_di -- 商家交易轻度汇总表
5. MDL层:mdl_waimai_merchant_daily -- 外卖商家日汇总表
6. ADL层:adl_waimai_daily_report -- 外卖日报表数据

七、命名规范最佳实践

  1. 避免过度缩写:除非是全公司通用的缩写(如 id、dt、amt),否则禁止使用自定义缩写
  2. 表名体现粒度:通过后缀明确表的更新频率和数据粒度,避免歧义
  3. 不要在表名中加入版本号:使用分区或备份表来管理数据版本
  4. 统一业务术语:建立公司级的业务术语表,确保相同业务概念使用相同的名称
  5. 自动化校验:将命名规范集成到数据开发平台中,在表创建时自动校验

参考资料项目细节

以美团外卖业务为例,分层设计数据仓库(细节)

以美团外卖业务为例,我们可以设计一个支撑其“订单交易、用户流量、商家运营、骑手配送、营销广告”五大核心业务的数据仓库。其中,订单流程(用户下单 → 商家接单 → 骑手配送)是数仓建模的核心业务总线。

下面我将从多角度具体展开这套设计思路。

📈 第一步:业务分析与主题域划分

首先,需要将外卖业务的整体流程拆解为多个业务过程,并划分到主题域。下表梳理了外卖业务的核心域划分与定义:

主题域 主题定义 核心业务过程 典型分析场景
交易域 外卖订单从生单到完单的全过程 下单、支付、接单、配送、完成、取消 GMV、客单价、订单量、完单率
用户域 用户的注册、活跃、留存、画像 注册、登录、浏览、点击、评价 DAU/MAU、用户留存、用户画像
商家域 门店的管理、运营、供给 开店、装修、菜品上/下架、接单 门店数量、营业时长、动销率
骑手域 骑手的配送活动与考勤 上线、接单、取餐、送达、下线 骑手在岗率、人均单量、配送时长
营销域 各类优惠活动、广告的投放与曝光 发券、领券、用券、广告点击 核销率、ROI、拉新成本

业务面覆盖:这套设计框架覆盖了用户、商家、骑手和财务结算等多个方面,通过一套主外键和粒度为单次下单事件明细的事务事实表,可以支撑绝大多数业务分析场景。


🏗️ 第二步:数据仓库分层架构设计

在明确了业务边界后,就需要在技术架构上分层建设。美团采用业界标准的四层数据架构,以下表格清晰地展示了各层职责与技术定位-1:

数仓分层 英文全称 层级定位与职责 在美团外卖的典型表
ODS 层 Operation Data Store 操作数据存储层,数据的“原始库”。近乎无处理地从业务源(MySQL、日志、Kafka等)同步数据,与源头结构保持一致-。 ods_order_inc,ods_user_inc,ods_log_inc
DIM 层 Dimension Data Layer 公共维度层,存储实体信息。存储不常变化的描述性信息,如用户、商家等,被数仓所有下游业务共用-10。 dim_user_full,dim_restaurant_full,dim_date,dim_geography
DWD 层 Data Warehouse Detail 明细数据层,业务的“事实库”。对ODS数据进行清洗、标准化、维度退化,构建面向业务过程的事实明细表-。 dwd_order_detail_inc,dwd_pay_detail_inc,dwd_log_inc
DWS 层 Data Warehouse Service 汇总数据层,应用的“公共库”。面向分析主题,基于DWD层进行轻度聚合,形成宽表模型,如单日用户下单等-10。 dws_user_order_1d,dws_restaurant_order_1d
ADS 层 Application Data Service 应用数据服务层,最终“产品”。为特定报表、大屏等需求高度定制,直接产出结果数据-10。 ads_gmv_dashboard,ads_user_retention

📊 第三步:各层表结构详细设计

1. 原始数据层
表名:ods_order_inc (订单全量增量数据)

该表为增量表,每日通过Spark作业批量同步,增量保留90天,全量归档至HDFS-29。

字段名 类型 说明
order_id bigint 订单ID (主键)
user_id bigint 用户ID
restaurant_id bigint 餐厅ID
rider_id bigint 骑手ID (可为空)
total_amount decimal(10,2) 订单原总金额
discount_amount decimal(10,2) 优惠金额
payment_amount decimal(10,2) 实付金额
order_status tinyint 订单状态 (0:待支付, 1:待接单,2:配送中…9:已完成,-1:已取消)
create_time timestamp 下单时间
pay_time timestamp 支付时间
update_time timestamp 更新时间
… … 其他订单字段
etl_time timestamp ETL处理时间,记录数据入库时间
表名:ods_log_inc (APP端/小程序用户行为日志)

该表存储来自客户端的用户前端操作日志,原始数据通过Flume或SDK收集。以埋点事件event_name为主要区分标识。

字段名 类型 说明
log_id string 日志唯一ID (主键)
user_id bigint 用户ID (若未登录则为0)
device_id string 设备唯一标识
event_name string 事件名称 (如 app_launch, page_view, button_click, order_success)
event_params map<string, string> 事件自定义参数 (K-V结构,如 {"page_name":"restaurant_detail", "restaurant_id":"1001"})
app_version string APP版本
os_type string 操作系统
log_time timestamp 日志产生时间 (客户端时间)
etl_time timestamp ETL处理时间
2. 公共维度层
表名:dim_user_full (用户维度表)

全量表,每日全量覆盖。对于用户信息的变更,采用直接全量覆盖的SCD Type-1策略,即直接UPDATE历史值,不保存完整历史轨迹-24。

字段名 类型 说明
user_id bigint 用户ID (主键)
user_name string 用户昵称
register_phone string 注册手机号 (脱敏)
register_time timestamp 注册时间
user_level string 会员等级 (如"普通",“黄金”,“白金”)
city_id int 注册城市ID
is_active boolean 是否活跃用户
… … 用户画像标签字段等
etl_time timestamp ETL处理时间
表名:dim_restaurant_full (商家门店维度表)
字段名 类型 说明
restaurant_id bigint 门店ID (主键)
restaurant_name string 门店名称
business_type string 经营品类 (如"中餐",“西餐”,“快餐”)
city_id int 门店所在城市ID
district_id int 所在区/县ID
business_hours string 营业时间段
address string 详细地址
score decimal 门店综合评分
… … 门店其他属性字段
etl_time timestamp ETL处理时间
3. 明细数据层
事实表:dwd_order_detail_inc (订单明细事务事实表)

核心事务事实表,行文级别为订单中每种商品的每个SKU。该表从ods_order_inc和ods_order_item_inc(订单中商品明细表)关联加工而成,保留了最细粒度的下单件数、商品原价等-29。

字段名 类型 说明 来源层/处理逻辑
order_detail_id bigint 订单子项ID (主键) ods_order_item_inc
order_id bigint 订单ID (外键) ods_order_inc
user_id bigint 用户ID (外键) 来源于 dim_user_full 的用户标识
restaurant_id bigint 餐厅ID (外键) 来源于 dim_restaurant_full 的门店标识
goods_id bigint 商品ID (外键) ods_order_item_inc
goods_name string 商品名称 (冗余) 维度退化自 ods_order_item_inc
goods_count int 商品数量 ods_order_item_inc
goods_price decimal(10,2) 商品单价 从订单总金额和总数量反推或从源表获取
order_status tinyint 订单状态 ods_order_inc
create_time timestamp 下单时间 ods_order_inc
pay_time timestamp 支付时间 ods_order_inc
… … 业务过程外键 关联 dim_date 等维度表
etl_time timestamp ETL处理时间 系统生成
4. 汇总数据层
表名:dws_user_order_1d (用户粒度订单汇总表)

该表是轻度汇总层(轻度汇总表),以1天为统计周期,围绕用户进行预聚合,支撑用户粒度分析查询。以用户ID和统计日期为联合主键-30。

字段名 类型 说明 来源层/处理逻辑
user_id bigint 用户ID dwd_order_detail_inc
stat_date date 统计日期 dwd_order_detail_inc / dim_date
order_count bigint 当天下单次数 聚合自 dwd_order_detail_inc
payment_amount decimal(10,2) 当天支付总金额 聚合自 dwd_order_detail_inc 等,通过 ‘支付’ 等业务过程过滤
avg_order_amount decimal(10,2) 当天平均订单金额 根据总金额和总次数计算
… … 当天用户行为打标 例如“是否使用优惠券”、“是否新客”等
etl_time timestamp ETL处理时间 系统生成
5. 应用数据层
表名:ads_gmv_dashboard (实时大屏销售表)

应用层表数据高度定制化,侧重于直接交付业务。本表可根据公司前端大屏需求,进行结果存储,直接写入MySQL或ClickHouse等查询系统,为实时大屏服务-6。

字段名 类型 说明 来源层/处理逻辑
stat_time string 统计时刻 (每5分钟一个批次) Flink从Kafka实时消费ods层数据
total_gmv decimal(10,2) 实时累计GMV 实时聚合自Kafka的订单支付消息
order_count bigint 实时累计订单数 实时聚合自Kafka的订单支付消息
avg_gmv_per_user decimal(10,2) 实时人均GMV 根据实时GMV和实时用户数计算
etl_time timestamp ETL处理时间 系统生成

🛠️ 第四步:技术平台选型与优化思路

⚙️ 技术选型建议

在架构和表结构之后,技术选型是决定数仓性能和稳定性的关键。下表是美团技术栈套件和通用大数据组件选型的常用组合建议:

技术层次 套件用途 整体框架组件 可选常用组件
数据集成 业务及日志数据采集 美团自研数据同步平台 Canal、DataX、Flume
存储 数据湖及原始存储 Hive(HDFS)Iceberg Table Hive、HDFS、HBase
离线计算 庞大历史数据PB级批量处理与建模 基于 Spark SQL (80%+任务) 的离线数仓 Spark, Hive on MapReduce
实时计算 毫秒级实时风控与近实时业务指标 Flink + Kafka/Paimon 实时数仓架构 Flink、Spark Streaming、Kafka
分析引擎 灵活多维分析和查询响应 Doris + Kylin 的双擎驱动策略-6 ClickHouse、Doris、Kylin、Presto/Trino
💡 复杂场景优化思路

针对外卖业务‘商圈划分频繁调整’等变化维度和海量数据挑战,美团在技术层面的核心优化策略是:

  1. 存储与计算分离:引入数据湖(如Iceberg)存储,解决存储扩容与计算资源绑定的问题,实现冷热数据分层。
  2. 流批一体:基于Flink统一计算引擎,实现实时与离线处理逻辑复用,保证口径一致性。
  3. 智能物化视图:基于Doris等引擎,按统计周期自动构建物化视图,避免手动维护,提升查询效率-6。