基于 Apache Doris 的用户画像数据模型设计方案

一、 需求分析与设计目标
  1. 数据源

    • 用户基本信息:用户ID、性别、出生日期、注册时间、常驻地域(省、市、区)、职业等。

    • 用户体检报告:每次体检的报告ID、体检时间、各项指标(如血压、血糖、血脂、BMI等)。

    • 用户关注的保健品:用户ID、关注/浏览/收藏的保健品ID、行为时间、行为类型(点击、收藏、加购)、保健品品类等。

  2. 核心需求

    • 支持以用户为主体的 360° 视图查询。

    • 支持在时间 (年、季度、月、周、日)和地域(国家、省、市)维度上进行灵活的上卷(Roll-up)和下钻(Drill-down)分析。

    • 支持对用户标签(如"高血压风险人群"、"VC补充剂潜在客户")的快速群体圈选和统计分析。

    • 查询响应速度快,即使在大数据量下也能亚秒级响应。

  3. 设计目标

    • 高性能:利用 Doris 的预聚合、列式存储和索引技术实现极速查询。

    • 可扩展性:模型能够轻松应对数据量的持续增长。

    • 易维护性:表结构清晰,数据更新和ETL流程高效。


二、 技术选型与 Doris 特性利用

选择 Apache Doris 的原因及其核心特性的利用:

  • MPP 架构:天然支持大规模并行处理,适合复杂的分析查询。

  • 列式存储:高压缩比,查询时只需读取相关列,极大降低 I/O。

  • 智能预聚合(Aggregate 模型)本方案核心,可预先计算常用维度的汇总数据,使聚合查询速度极快。

  • 分区与分桶:支持按时间分区,便于数据管理(如淘汰旧数据)和查询时剪枝。结合分桶(Hash分桶)实现数据均匀分布和高效点查。

  • 物化视图(Materialized View)另一核心,可自动或手动为不同维度组合创建预聚合表,查询时路由到最优的物化视图,灵活支持上卷下钻。

  • Duplicate 模型:用于存储需要保留原始明细数据的表,如用户行为流水。

  • 高效数据导入:支持 Broker Load / Stream Load / Routine Load,便于从 Kafka、HDFS、MySQL 等数据源批量或实时导入数据。


三、 数据模型详细设计

建议采用维度建模 的思想,构建雪花模型或星型模型。这里我们采用星型模型,以保持查询的简洁和高效。

1. ODS 层(操作数据层)

  • 目的:贴源数据层,结构与数据源保持一致。

  • 表设计:使用 Duplicate 模型,保留原始明细。

    • ods_user_basic (用户基本信息表)

    • ods_user_medical_report (用户体检报告表)

    • ods_user_behavior (用户保健品关注行为表)

  • 数据导入:使用 Routine Load 从 Kafka 实时接入用户行为数据,使用 Broker Load 每天批量导入体检报告和基本信息变更数据。

2. DWD 层(数据明细层)

  • 目的:对 ODS 层数据进行清洗、整合、轻度聚合,形成明细事实表和维度表。

  • 表设计:

    • 事实表

      • dwd_user_medical_fact (用户体检事实表)

        • 模型:Aggregate 模型

        • 分区:按 exam_date (体检日期) 进行 Range Partitioning(按月分区)。

        • 分桶:按 user_id 分桶。

        • 排序键:(user_id, exam_date)

        • Schema:

          sql

          复制代码
          CREATE TABLE dwd_user_medical_fact (
              `user_id` BIGINT,
              `exam_date` DATE,           -- 衍生自exam_time
              `exam_time` DATETIME,
              `province` VARCHAR(20),
              `city` VARCHAR(20),
              `blood_pressure_high` SMALLINT REPLACE_IF_NOT_NULL, -- 替换式聚合
              `blood_pressure_low` SMALLINT REPLACE_IF_NOT_NULL,
              `blood_sugar` FLOAT REPLACE_IF_NOT_NULL,
              `bmi` FLOAT REPLACE_IF_NOT_NULL,
              -- ... 其他指标
              `is_high_blood_pressure` BOOLEAN REPLACE, -- 是否高血压,根据指标计算得出
              `is_high_blood_sugar` BOOLEAN REPLACE     -- 是否高血糖
          ) ENGINE=OLAP
          AGGREGATE KEY(user_id, exam_date, exam_time, province, city)
          PARTITION BY RANGE(exam_date) (...)
          DISTRIBUTED BY HASH(user_id) BUCKETS 10;
      • dwd_user_behavior_fact (用户行为事实表)

        • 模型:Aggregate 模型

        • 分区:按 event_date (行为日期) 分区。

        • 分桶:按 user_id 分桶。

        • 排序键:(user_id, event_date,保健品品类)

        • Schema:

          sql

          复制代码
          CREATE TABLE dwd_user_behavior_fact (
              `user_id` BIGINT,
              `event_date` DATE,
              `event_time` DATETIME,
              `behavior_type` VARCHAR(20), -- 'click', 'favor', 'cart'
              `health_product_id` BIGINT,
              `product_category` VARCHAR(50),
              `click_count` BIGINT SUM,     -- 计数型聚合
              `is_latest_behavior` BOOLEAN REPLACE -- 是否为该商品最新行为
          ) ENGINE=OLAP
          AGGREGATE KEY(user_id, event_date, event_time, behavior_type, health_product_id, product_category)
          PARTITION BY RANGE(event_date) (...)
          DISTRIBUTED BY HASH(user_id) BUCKETS 10;
    • 维度表

      • dim_user (用户维度表)

        • 模型:Unique 模型(确保用户唯一性)

        • 分区:可按注册日期分区。

        • 分桶:按 user_id 分桶。

        • Schema:

          sql

          复制代码
          CREATE TABLE dim_user (
              `user_id` BIGINT,
              `gender` VARCHAR(10),
              `birth_date` DATE,
              `register_date` DATE,
              `常住省份` VARCHAR(20),
              `常住城市` VARCHAR(20),
              `occupation` VARCHAR(50),
              -- ... 其他属性
          ) ENGINE=OLAP
          UNIQUE KEY(user_id)
          DISTRIBUTED BY HASH(user_id) BUCKETS 10;
        • 时间维度表

CREATE TABLE dim_date (

`date_id` DATE, -- 代理键,也是自然键,格式 'YYYY-MM-DD'

`day` TINYINT, -- 本月中的第几天 (1-31)

`month` TINYINT, -- 年份中的第几月 (1-12)

`quarter` TINYINT, -- 年份中的第几季度 (1-4)

`year` SMALLINT, -- 年份 (e.g., 2023)

`day_of_week` TINYINT, -- 本周中的第几天 (1=Sunday, 7=Saturday)

`week_of_year` TINYINT, -- 年份中的第几周 (ISO标准 1-53)

`is_weekend` BOOLEAN, -- 是否是周末

`is_holiday` BOOLEAN, -- 是否是法定节假日

`holiday_name` VARCHAR(50), -- 节假日名称 (e.g., '国庆节', '元旦')

`is_workday` BOOLEAN, -- 是否是工作日(考虑调休后)

`month_name` VARCHAR(10), -- 月份英文缩写 (e.g., 'Jan', 'Feb')

`quarter_name` VARCHAR(2), -- 季度名称 (e.g., 'Q1', 'Q2')

`year_quarter` VARCHAR(7), -- 年份季度 (e.g., '2023-Q1')

`year_month` VARCHAR(7), -- 年份月份 (e.g., '2023-01')

`year_week` VARCHAR(7), -- 年份周数 (e.g., '2023-W01')

`last_year_same_day` DATE, -- 去年同一天

`last_month_same_day` DATE, -- 上月同一天(可能为空)

`day_num_in_epoch` INT, -- 自某个固定日期(如1970-01-01)以来的天数

`week_begin_date` DATE, -- 本周第一天(周日或周一)

`week_end_date` DATE, -- 本周最后一天

`month_begin_date` DATE, -- 本月第一天

`month_end_date` DATE, -- 本月最后一天

`quarter_begin_date` DATE, -- 本季度第一天

`quarter_end_date` DATE, -- 本季度最后一天

`year_begin_date` DATE, -- 本年第一天

`year_end_date` DATE -- 本年最后一天

) ENGINE=OLAP

DUPLICATE KEY(date_id)

COMMENT "时间维度表"

DISTRIBUTED BY HASH(date_id) BUCKETS 1

PROPERTIES (

"replication_num" = "3",

"dynamic_partition.enable" = "true",

"dynamic_partition.time_unit" = "YEAR",

"dynamic_partition.start" = "-3", -- 动态创建过去3年和未来2年的分区

"dynamic_partition.end" = "2",

"dynamic_partition.prefix" = "p",

"dynamic_partition.buckets" = "1"

);

3. DWS 层(数据服务层 / 宽表层)

  • 目的:面向业务场景构建宽表,进一步聚合数据,减少多表关联,提升查询性能。这是支持用户画像上卷下钻的核心层

  • 表设计:使用 Aggregate 模型 ,并创建物化视图

    • dws_user_profile_wide (用户画像宽表)

      • 模型:Aggregate 模型

      • 分区:按 date (日期) 分区。

      • 分桶:按 user_id 分桶。

      • 排序键:(date, province, city, user_id)

      • Schema:

        sql

        复制代码
        CREATE TABLE dws_user_profile_wide (
            `date` DATE,                    -- 日期粒度
            `province` VARCHAR(20),
            `city` VARCHAR(20),
            `user_id` BIGINT,
            `age_group` VARCHAR(10) REPLACE, -- 年龄段,e.g., '20-30'
            `gender` VARCHAR(10) REPLACE,
            `latest_blood_pressure_status` VARCHAR(20) REPLACE, -- 最新血压状态
            `latest_blood_sugar_status` VARCHAR(20) REPLACE,
            `is_high_risk_cardio` BOOLEAN REPLACE, -- 是否心脑血管高风险
            `favorited_vc_product_count` BIGINT SUM, -- 收藏的VC类产品总数
            `clicked_protein_powder_count` BIGINT SUM, -- 点击的蛋白粉类产品次数
            -- ... 其他标签和聚合指标
        ) ENGINE=OLAP
        AGGREGATE KEY(date, province, city, user_id)
        PARTITION BY RANGE(date) (...)
        DISTRIBUTED BY HASH(user_id) BUCKETS 10;
      • 物化视图创建示例

        sql

        复制代码
        -- 为按省和日期聚合创建物化视图,加速上卷查询
        CREATE MATERIALIZED VIEW province_date_agg AS
        SELECT
            `date`,
            `province`,
            COUNT(DISTINCT user_id) AS total_users,
            SUM(CAST(is_high_risk_cardio AS INT)) AS high_risk_users,
            SUM(favorited_vc_product_count) AS total_vc_favorites
        FROM dws_user_profile_wide
        GROUP BY `date`, `province`;
        
        -- 为按市、性别和日期聚合创建物化视图,支持下钻查询
        CREATE MATERIALIZED VIEW city_gender_date_agg AS
        SELECT
            `date`,
            `province`,
            `city`,
            `gender`,
            COUNT(DISTINCT user_id) AS total_users,
            ...
        FROM dws_user_profile_wide
        GROUP BY `date`, `province`, `city`, `gender`;

        Doris 的查询优化器会自动选择最优的物化视图来响应查询。


四、 ETL 数据处理流程
  1. 数据采集:用户行为数据通过埋点实时写入 Kafka。体检报告和用户信息通过业务数据库的 Binlog 或每日全量/增量导出文件到 HDFS。

  2. 数据导入

    • 实时 :使用 Routine Load 任务持续消费 Kafka 中的行为数据,导入到 ods_user_behavior 表。

    • 批量 :每天凌晨,使用 Broker Load 从 HDFS 拉取体检报告和用户信息的增量文件,导入到对应的 ODS 表。

  3. 数据加工

    * 通过INSERT INTO SELECT 语句,定期(如每小时或每天)将 ODS 层数据清洗、转换、聚合到 DWD 层事实表和维度表。

    * 同样通过 INSERT INTO SELECT ,将 DWD 层数据与维度表 JOIN 后,聚合到 DWS 宽表 dws_user_profile_wide。这个过程会计算用户标签(如 is_high_risk_cardio)和聚合指标。

  4. 数据服务 :应用层直接通过 MySQL 协议查询 DWS 层的宽表及其物化视图,快速获取分析结果。


五、 示例查询
  1. 下钻分析:"2023年Q4,江苏省南京市,高血糖用户中最关注维生素C保健品的人群分布?"

    sql

    复制代码
    SELECT
        city,
        gender,
        age_group,
        COUNT(DISTINCT user_id) AS user_count
    FROM dws_user_profile_wide
    WHERE
        `date` >= '2023-10-01' AND `date` <= '2023-12-31'
        AND province = '江苏省'
        AND city = '南京市'
        AND latest_blood_sugar_status = 'high' -- 高血糖标签
        AND favorited_vc_product_count > 0     -- 关注过VC产品
    GROUP BY
        city, gender, age_group -- 下钻到市、性别、年龄
    ORDER BY user_count DESC;

    Doris 会尝试使用 province_date_agg 等物化视图快速定位到江苏省2023Q4的数据,然后再进行下钻计算。

  2. 上卷分析:"近一年,全国各季度,心脑血管高风险用户的比例趋势?"

    sql

    复制代码
    SELECT
        YEAR(date) AS year,
        QUARTER(date) AS quarter,
        SUM(CAST(is_high_risk_cardio AS INT)) / COUNT(DISTINCT user_id) AS risk_ratio
    FROM dws_user_profile_wide
    WHERE `date` >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 YEAR)
    GROUP BY year, quarter
    ORDER BY year, quarter;

    此查询可能会直接命中按日期聚合的物化视图,效率极高。


六、 总结与优化建议
  • 优势:本方案充分利用了 Doris 的聚合模型、物化视图、分区分桶等核心特性,完美支撑了用户画像的多维分析需求,预计能达到极快的查询响应速度。

  • 监控与调优

    • 监控 dws_user_profile_wide 表的物化视图命中率,根据业务查询模式调整或增加物化视图。

    • 监控集群磁盘、内存、BE节点状态,随着数据增长水平扩展 Doris 集群。

    • 合理设置分桶数量,通常在10-100个之间,避免 Tablet 过多导致元数据压力过大。

  • 未来扩展

    • 可以引入更复杂的用户标签算法(如机器学习模型打分),通过 ETL 过程将结果写入用户宽表。

    • 可以探索使用 Doris 的 Array 类型存储用户的行为标签序列,进行用户行为路径分析。

相关推荐
cg.family5 天前
Doris 消费kafka消息
kafka·doris
cg.family7 天前
Doris 数据仓库例子
数据仓库·doris
cg.family8 天前
Doirs Routine Load
doris·routine load
SelectDB技术团队1 个月前
ApacheCon Asia 2025 中国开源年度报告:Apache Doris 国内第一
开源·apache·数据库开发·doris·实时分析
纯洁的小魔鬼1 个月前
Springboot 配置 doris 连接
spring boot·doris·连接池
SelectDB技术团队2 个月前
森马服饰从 Elasticsearch 到阿里云 SelectDB 的架构演进之路
elasticsearch·阿里云·doris
天翼云开发者社区2 个月前
Doris-HDFS LOAD常见问题汇总(二)
大数据·doris
Faith_xzc3 个月前
Apache Doris FE 问题排查与故障分析全景指南
大数据·数据仓库·apache·doris
寂夜了无痕3 个月前
doris manager 安装部署 、管理已有doris集群、使用studio进行SQL查询
doris·doris manager·doris studio