搭数仓一定要上 Hadoop 全家桶吗?本文手把手带你用 ClickHouse + DolphinScheduler 搭建一套轻量离线数仓,从建库到调度一条龙,中小团队也能拥有自己的数据底座。

前言:你是不是也被"全家桶"劝退过?

提到离线数仓,很多人脑子里自动弹出一套豪华套餐:

Hadoop + Hive + Spark + DolphinScheduler + Sqoop/DataX + 可能还要再来个 OLAP 引擎……

光是把这些组件部署完,运维同学的头发已经掉了一半,项目还没正式开始。

但现实是:不是每个团队都需要日均 PB 级的处理能力,也不是每个数仓都要搞得像 NASA 指挥中心一样复杂。对于日增数据量在千万级以下的中小团队来说,有一条"轻装上阵"的路线 —— ClickHouse + DolphinScheduler,两个组件,就能撑起一套五脏俱全的离线数仓。

两条路线对比

维度 全家桶方案 轻量方案
技术栈 Hadoop + Hive + Spark + 调度 + ETL + OLAP ClickHouse + DolphinScheduler
部署复杂度 ⭐⭐⭐⭐⭐(建议先买防脱洗发水) ⭐⭐(Docker 一把梭)
运维成本 高(N 个组件 = N 种报错姿势) 低(两个组件,心里有数)
适用场景 大规模离线计算、数据湖 中小规模数仓、快速交付的 BI 场景
查询性能 依赖引擎组合 ClickHouse 原生 OLAP,天然快

不是说全家桶不好,而是杀鸡别用牛刀 —— 如果你的数据量还没大到需要 Hadoop 来扛,先试试轻量方案,省下来的时间可以多写几个需求(好吧,这可能不算好处 😅)。


一、前置准备

在开始之前,你需要先把两个核心组件部署好:

组件 参考文档
ClickHouse ClickHouse 25.4 基于 Docker 单机部署实战指南
DolphinScheduler DolphinScheduler 单机部署实战:Docker 一把梭,中小团队的调度神器

部署完这两个,我们就可以正式"搬砖"了。


二、数仓分层:先把"房间"隔好

数仓的经典分层模型,即使是轻量方案也不能含糊。在 ClickHouse 中,我们直接用 Database 来划分各层:

CREATE DATABASE IF NOT EXISTS ods;   -- 原始数据层(贴源层)
CREATE DATABASE IF NOT EXISTS dwd;   -- 明细数据层
CREATE DATABASE IF NOT EXISTS dws;   -- 汇总数据层
CREATE DATABASE IF NOT EXISTS dim;   -- 维度层
CREATE DATABASE IF NOT EXISTS ads;   -- 应用数据层(直接给 BI 用)

五句 SQL,数仓的"骨架"就搭好了。是的,就这么朴实无华。

💡 小贴士:分层不是为了好看,而是为了让数据流转有据可循 —— ODS 管"搬进来",DWD 管"洗干净",DWS 管"聚合好",ADS 管"端上桌"。每一层各司其职,后续排查问题的时候你会感谢自己。


三、数据同步:把数据"搬"进 ClickHouse

3.1 同步方案选型

数据从 MySQL、MongoDB 等业务库同步到 ClickHouse,常见方案分两大类:

方案 思路 代表工具 特点
ETL/ELT 中间件 部署独立的同步工具 DataX、Sqoop、Flink CDC 功能强大,但多一个组件多一份运维
CK 原生表引擎 ClickHouse 直接连数据源 MySQL 引擎、MongoDB 引擎、JDBC 引擎等 零额外部署,SQL 即同步

ClickHouse 内置了丰富的集成表引擎,支持 MySQL、MongoDB、PostgreSQL、Kafka、S3、HDFS 等几十种数据源,基本覆盖了常见的数据接入场景。

我们的选择:既然目标是"轻量",那就贯彻到底 —— 直接用 ClickHouse 原生 MySQL 表引擎,不引入额外组件,SQL 写完数据就过来了。

🎯 原则:能少一个组件就少一个,少一个组件就少一种凌晨三点被叫醒的可能。

3.2 创建数据源映射(MySQL 表引擎)

在 ClickHouse 中,通过 MySQL 表引擎直接"挂载"远端 MySQL 表:

CREATE TABLE source_mapping.member_profile
ENGINE = MySQL(
    'your-mysql-host:3306',
    'your_database',
    'member_profile',
    'your_username',
    'your_password'
);

⚠️ 注意:这里只是创建了一个"映射",数据仍然存储在 MySQL 中。每次查询这张表,ClickHouse 都会实时去 MySQL 拉数据。所以它的定位是同步的"桥梁",而不是最终存储。

3.3 创建 ODS 层目标表

接下来,在 ODS 层创建真正存储数据的本地表:

CREATE TABLE ods.ods_member_profile (
    id UInt64 COMMENT '主键ID',
    member_id String COMMENT '会员编号',
    avatar_url Nullable(String) COMMENT '头像地址',
    login_key String COMMENT '登录唯一标识',
    phone Nullable(String) COMMENT '手机号码',
    member_type Int8 DEFAULT 1 COMMENT '会员类型: 1-普通, 2-内部',
    status Nullable(Int8) COMMENT '状态: 0-禁用, 1-启用',
    secret_key Nullable(String) COMMENT '密钥(已加密)',
    nickname Nullable(String) COMMENT '昵称',
    avatar_large Nullable(String) COMMENT '大头像地址',
    _version DateTime DEFAULT now() COMMENT '版本字段,用于数据更新'
)
ENGINE = ReplacingMergeTree(_version)
PARTITION BY toYYYYMM(create_time)
ORDER BY (member_id)
SETTINGS
    index_granularity = 256
COMMENT 'ODS层 - 会员信息表';

几个关键设计决策解释一下:

  • ReplacingMergeTree:这是重点!ODS 层会反复同步增量数据,同一条记录可能被多次写入。ReplacingMergeTree 会在后台 Merge 时自动按 _version 去重,保留最新版本。如果用普通 MergeTree,你的数据会越来越"膨胀",最终查出来一堆重复行,场面一度非常混乱 🤯
  • PARTITION BY toYYYYMM(create_time):按月分区,方便管理和清理历史数据
  • ORDER BY (member_id):按业务主键排序,查询效率拉满

3.4 初始化存量数据

首次同步,需要把 MySQL 中的全量数据灌进来:

INSERT INTO ods.ods_member_profile (
    id, member_id, avatar_url, login_key, phone,
    member_type, status, secret_key, nickname, avatar_large
)
SELECT
    id,
    member_id,
    ifNull(avatar_url, '')    AS avatar_url,
    login_key,
    ifNull(phone, '')         AS phone,
    member_type,
    ifNull(status, 0)         AS status,
    ifNull(secret_key, '')    AS secret_key,
    ifNull(nickname, '')      AS nickname,
    ifNull(avatar_large, '')  AS avatar_large
FROM source_mapping.member_profile;

执行完毕后,验证一下数据量:

SELECT count(*) FROM ods.ods_member_profile FINAL;

💡 为什么用 FINAL 因为 ReplacingMergeTree 的去重发生在后台 Merge 过程中,如果 Merge 还没完成,直接 count(*) 可能会看到重复数据。加上 FINAL 关键字,ClickHouse 会在查询时强制去重,得到准确结果。(当然,FINAL 有性能开销,生产环境大表慎用,验证的时候用用无妨。)

顺便说一下同步性能 —— 你可能会担心"全量灌数据会不会跑到天荒地老?"实测下来,完全不用担心:

数据源 同步速率 每分钟吞吐量
MySQL → ClickHouse ~35w 条/s 2000w+ 条/min
MongoDB → ClickHouse ~12w 条/s 720w+ 条/min

以上数据基于多张业务表的实际同步统计,具体性能受网络、字段数量、数据类型等因素影响,仅供参考。但总的来说,ClickHouse 的写入能力可以用"暴力"来形容 —— 千万级的表,几分钟就灌完了,泡杯咖啡的功夫都不用 ☕


四、增量调度:让 DolphinScheduler 帮你"打工"

存量数据搞定了,但数仓是要"活"的 —— 业务库每天都有新数据进来,我们需要定时增量同步。这时候就轮到 DolphinScheduler 登场了。

4.1 创建调度工作流

操作路径很简单:创建项目 → 新建工作流 → 添加 SQL 节点 → 上线定时,DolphinScheduler 的 UI 交互比较直观,这里不赘述基础操作(如有不熟悉的同学可留言评论,后续补充分享相关内容的文章)。

我们重点关注数仓场景下常用的三种节点类型:

模块 组件 用途
通用组件 SQL 执行 MySQL / ClickHouse SQL,数仓的主力
通用组件 SHELL 发通知(飞书/钉钉)、执行脚本等辅助操作
逻辑节点 DEPENDENT 依赖上游工作流,控制层级间的执行顺序

4.2 增量同步 SQL(核心)

在工作流中添加一个 SQL 节点,填写增量同步逻辑 —— 这才是本章的重头戏

INSERT INTO ods.ods_member_profile (
    id, member_id, avatar_url, login_key, phone,
    member_type, status, secret_key, nickname, avatar_large
)
SELECT
    id,
    member_id,
    ifNull(avatar_url, '')    AS avatar_url,
    login_key,
    ifNull(phone, '')         AS phone,
    member_type,
    ifNull(status, 0)         AS status,
    ifNull(secret_key, '')    AS secret_key,
    ifNull(nickname, '')      AS nickname,
    ifNull(avatar_large, '')  AS avatar_large
FROM source_mapping.member_profile
WHERE update_time >= toDateTime('$[yyyy-MM-dd HH:00:00-1/24]')
  AND create_time <  toDateTime('$[yyyy-MM-dd HH:00:00]');

📌 增量逻辑说明

  • $[yyyy-MM-dd HH:00:00-1/24] 是 DolphinScheduler 的内置时间参数,表示上一个整点
  • $[yyyy-MM-dd HH:00:00] 表示当前整点
  • 每次只同步过去一小时内有更新的数据,配合 ReplacingMergeTree 的去重机制,实现"幂等增量同步"

🔍 WHERE 条件拆解

  • update_time >= 开始时间:捞出该时段内新增或修改过的记录(新增时 update_time = create_time,修改时 update_time 会被刷新)
  • create_time < 结束时间:排除掉在当前整点之后才创建的数据,避免"抢跑"
  • 两个条件取交集,精准圈定"本小时内发生变更的数据"

性能提示:MySQL 源表务必创建 (update_time, create_time) 联合索引,否则每小时一次的增量查询会变成全表扫描,慢查询警告了解一下 🚨

4.3 上线定时调度

保存工作流后,上线并配置定时策略(比如每小时执行一次):

在这里插入图片描述

💡 调度时间小技巧:建议将执行时间比整点延后 3~5 分钟(比如每小时的第 5 分钟触发)。业务库通常有 MySQL 主从复制延迟,卡在整点跑可能漏掉最后几秒写入的数据。稍微"慢半拍",数据反而更完整。

4.4 验证同步结果

调度跑完后,查一下最新数据是否已经同步过来:

SELECT *
FROM ods.ods_member_profile FINAL
ORDER BY create_time DESC
LIMIT 10;

看到最新的业务数据已经出现在 ODS 层,说明整个链路已经打通 🎉


五、后续建设:从 ODS 到 ADS 的数据流转

ODS 层只是起点,完整的数仓还需要继续向上构建

每一层之间的 ETL 逻辑,同样通过 DolphinScheduler 的 SQL 任务节点来调度,利用 DEPENDENT 节点控制层级间的依赖关系,确保上游跑完下游才启动。

在这里插入图片描述

至此,一套轻量但完整的离线数仓就搭建完成了。


六、总结

回顾一下我们做了什么:

步骤 内容 关键技术点
1 部署 ClickHouse + DolphinScheduler Docker 部署,开箱即用
2 创建数仓分层(ODS/DWD/DWS/DIM/ADS) ClickHouse Database 划分
3 配置数据源映射 MySQL 表引擎,零组件接入
4 全量初始化 + 增量调度 ReplacingMergeTree + 时间窗口
5 逐层构建数仓 DolphinScheduler 工作流编排

这套方案的核心优势:

  • 极简部署:只有两个组件,Docker 一把梭,半天搞定
  • 零额外 ETL 组件:利用 ClickHouse 原生表引擎直连数据源,不用再折腾 DataX、Sqoop
  • 天然 OLAP 能力:ClickHouse 本身就是 OLAP 引擎,数仓查询不需要再套一层
  • 调度灵活:DolphinScheduler 支持 DAG 编排、依赖管理、失败重试、告警通知,麻雀虽小五脏俱全

当然,这套方案也有其适用边界 —— 如果你的数据量级已经到了 TB/PB 级别,或者需要复杂的流批一体处理,那还是老老实实上大数据全家桶吧。工具没有高低之分,只有合不合适。


📣 如果这篇文章帮你少踩了一个坑,或者让你对轻量数仓有了新的思路,欢迎 点赞 👍 收藏 ⭐ 关注,你的支持是我持续输出的动力!有任何问题也欢迎评论区交流,我们一起把数仓这件事搞得明明白白 💪

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐