前面写了 ClickHouse 实时 OLAP、Kafka 消息、Spark/Flink 计算、Hive 离线数仓、HBase 存储、ZooKeeper 协调——这套”采-存-算-协”链路里,还差一块:既能扛高并发点查、又能做实时多维分析的”服务化数仓”。ClickHouse 擅长大宽表聚合,但高并发点查和频繁更新是短板;Apache Doris 正好补上这个位置。本文把 Doris 的核心架构、数据模型、导入与查询优化一次讲透。
一、Doris 解决什么问题
传统数仓有两个老大难:
| 场景 |
离线数仓(Hive) |
纯 OLAP 列存(ClickHouse) |
| 即席多维分析 |
慢(分钟级) |
快,但高并发点查易抖 |
| 数据更新 / 覆盖 |
不便(重写分区) |
弱(Mutation 异步) |
| 高并发(几百 QPS 点查) |
扛不住 |
非设计目标 |
| 上手成本 |
需 Hive/Spark 栈 |
需独立运维 |
Doris 的定位:一个兼容 MySQL 协议、MPP 架构、既适合实时多维分析又适合高并发点查 / 实时更新的实时数仓。它把”数仓”做成了一个可以直接被业务系统(报表、API、BI)查询的服务,而不是一套批处理任务。
一句话区分:ClickHouse 是”分析型列存引擎”,Doris 是”服务化的实时数仓”。前者极致单表聚合,后者兼顾分析、更新、点查与生态(MySQL 协议、物化视图自动路由)。两者常组合使用,而非互相替代。
二、整体架构:FE 与 BE
Doris 采用存算一体的 MPP 架构,只有两类进程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| MySQL Client / BI / JDBC (port 9030) │ ┌───────▼───────┐ │ FE (Frontend)│ ← 元数据 + 查询规划 + 接入层 │ Leader/Follower│ │ /Observer │ └───────┬───────┘ ┌───────────┼───────────┐ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ BE 0 │ │ BE 1 │ │ BE 2 │ ← 存储 + 执行 └─────────┘ └─────────┘ └─────────┘ │ │ │ Tablet Tablet Tablet (数据分片,三副本)
|
- FE(Frontend):负责元数据管理、查询解析与规划、客户端接入。通过 Raft(BDBJE) 保证元数据高可用,角色分
Leader / Follower(参与选主、奇数部署)/Observer(只同步、扩展查询能力)。
- BE(Backend):负责数据存储与查询执行,每个 Tablet 默认三副本,副本间通过分桶键哈希分布。
- 接入零改造:客户端用标准 MySQL 协议(默认 9030 端口)连 FE,直接用
mysql 命令行或任意 JDBC/ORM 就能查,这是它”服务化”的关键。
Doris 早期依赖 ZooKeeper/MySQL 存元数据,现代版本(1.x 之后)已内置 BDBJE 做元数据一致性,外部组件极少,部署比 Hive+Spark 轻得多。
三、核心概念:Tablet、分区、分桶
| 概念 |
含义 |
作用 |
| Partition(分区) |
按时间/枚举范围切分大表 |
分区裁剪,删旧数据只 drop 分区 |
| Bucket(分桶) |
分区内按哈希分桶键再切 |
数据均匀分布,Join/聚合本地化 |
| Tablet |
分桶后的最小数据分片 |
调度、均衡、备份的基本单位 |
| Rowset / Version |
每次导入生成一个 Rowset |
多版本,Compaction 合并 |
| Compaction |
小 Rowset 合并 |
控制读放大与空间放大 |
建表示例(明细模型 + 天级分区 + 城市分桶):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| CREATE TABLE dwd_order_detail ( dt DATE NOT NULL, order_id BIGINT NOT NULL, city VARCHAR(32) NOT NULL, user_id BIGINT NOT NULL, amount DECIMAL(10,2), status TINYINT ) ENGINE=OLAP DUPLICATE KEY(dt, order_id) PARTITION BY RANGE(dt) ( PARTITION p20261001 VALUES LESS THAN ('2026-10-02'), PARTITION p20261002 VALUES LESS THAN ('2026-10-03') ) DISTRIBUTED BY HASH(city) BUCKETS 16 PROPERTIES ( "replication_num" = "3", "dynamic_partition.enable" = "true", "dynamic_partition.time_unit" = "DAY", "dynamic_partition.end" = "3" );
|
四、三种数据模型
Doris 的”数据模型”决定了一行数据写入后如何被处理,是选型的命门:
| 模型 |
写入语义 |
适用场景 |
典型用法 |
| Duplicate Key |
保留所有明细 |
原始日志、订单流水 |
最常用,明细层 DWD |
| Aggregate Key |
相同 Key 列预聚合 |
指标累加(SUM/MAX) |
轻度汇总 DWS |
| Unique Key |
相同 Key 覆盖更新 |
维表、状态、CDC 结果 |
用户画像、订单状态 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| CREATE TABLE agg_sku ( dt DATE NOT NULL, sku VARCHAR(32) NOT NULL, amount DECIMAL(10,2) SUM, pv BIGINT MAX ) ENGINE=OLAP AGGREGATE KEY(dt, sku) DISTRIBUTED BY HASH(sku) BUCKETS 16;
CREATE TABLE dim_user ( user_id BIGINT NOT NULL, name VARCHAR(32) REPLACE, level TINYINT REPLACE ) ENGINE=OLAP UNIQUE KEY(user_id) DISTRIBUTED BY HASH(user_id) BUCKETS 16;
|
Unique Key 在 1.2 之前是 Merge-on-Read(读时合并,点查慢),2.0 起默认 Merge-on-Write(写时合并,点查接近行存性能)。做高并发点查务必确认版本与 enable_unique_key_merge_on_write=true。
五、分区 vs 分桶(与 Hive 对照)
| 维度 |
Doris |
Hive |
| 分区 |
RANGE 为主,支持动态分区 |
静态/动态分区,文件目录 |
| 分桶 |
HASH 分桶,Tablet 均匀分布 |
同 HASH/分桶,但偏文件级 |
| 裁剪 |
分区+分桶双重裁剪 |
分区裁剪为主 |
| 更新 |
Unique Key 原生 UPSERT |
需重写分区/ACID 表 |
| 小文件 |
Compaction 自动合并 |
易产生大量小文件 |
经验:分区按查询最常用的时间列,分桶键选高基数且常做 Join/Group 的列(如 user_id、city),桶数按数据量评估(每桶 1~10GB 较健康)。
六、物化视图与 Rollup
Doris 支持异步物化视图,对上层的复杂聚合做预计算,并能在查询时自动路由到命中的物化视图:
1 2 3 4 5 6
| CREATE MATERIALIZED VIEW mv_sku_daily AS SELECT dt, sku, SUM(amount) sum_amt, COUNT(*) cnt FROM agg_sku GROUP BY dt, sku;
|
物化视图的”透明加速”是 Doris 区别于手算汇总表的最大便利:业务 SQL 不用改,优化器自动选最优物化视图,避免 Hive 里维护一堆派生表的痛苦。
七、导入方式全景
| 导入方式 |
触发 |
数据来源 |
场景 |
| Stream Load |
HTTP 推 |
本地 CSV/JSON 文件 |
小批量、手动/程序推送 |
| Routine Load |
常驻 |
Kafka 主题 |
实时增量消费 |
| Insert Into |
SQL |
内部表/外部表 |
ETL 内部流转 |
| Broker Load |
异步 |
HDFS / S3 / OSS |
大数据量离线导入 |
| Spark Load |
异步 |
Hive 外部表 |
超大规模、借助 Spark 预处理 |
Stream Load 示例(从文件推):
1 2 3 4
| curl -u root: -H "label:order_20261030_01" \ -H "column_separator:," \ -T order.csv \ http://fe-host:8030/api/db1/dwd_order_detail/_stream_load
|
八、实时入仓链路(Kafka / Flink)
结合本博客已有的 Kafka、Flink 文章,Doris 是它们的实时落地点:
1 2 3
| Kafka Topic ──Routine Load──► Doris 明细表 ──► 物化视图 ──► BI/API │ └──Flink Job──► Doris(Flink Connector,两阶段提交 Exactly-Once)
|
Routine Load(纯 SQL 消费 Kafka):
1 2 3 4 5 6 7 8 9 10 11
| CREATE ROUTINE LOAD db1.rl_order ON dwd_order_detail COLUMNS(dt, order_id, city, user_id, amount, status) PROPERTIES ( "desired_concurrent_number" = "3", "max_batch_interval" = "10" ) FROM KAFKA ( "kafka_broker_list" = "k1:9092,k2:9092", "kafka_topic" = "order_event", "property.group.id" = "doris_consumer" );
|
Flink 写入(两段提交保证 Exactly-Once,与 Flink 篇的 Checkpoint 对齐):
1 2 3 4 5 6 7 8 9 10 11
| CREATE TABLE doris_sink ( dt DATE, order_id BIGINT, amount DECIMAL(10,2) ) WITH ( 'connector' = 'doris', 'fenodes' = 'fe-host:8030', 'table.identifier' = 'db1.dwd_order_detail', 'username' = 'root', 'sink.enable-2pc' = 'true', 'sink.label-prefix' = 'flink_order' );
|
九、查询优化要点
| 优化手段 |
原理 |
适用 |
| 前缀索引 |
对排序键前 36 字节建稀疏索引 |
按排序键过滤的点查/范围 |
| 倒排索引 |
2.0+ 对任意列建索引 |
等值/范围/全文检索 |
| 高并发点查 |
Unique Key + 行存 + 主键索引 |
几百~几千 QPS 的单行查 |
| Colocate Join |
同分桶键表同分布,免 Shuffle |
大表 Join 大表 |
| Broadcast Join |
小表广播 |
小维表 Join 事实表 |
1 2 3 4 5 6 7
| ALTER TABLE dwd_order_detail ADD INDEX idx_status (status) USING INVERTED;
CREATE TABLE fact_a (...) DISTRIBUTED BY HASH(user_id) BUCKETS 16 PROPERTIES("colocate_with" = "group_user");
|
十、Doris vs ClickHouse(定位对照)
| 维度 |
Apache Doris |
ClickHouse |
| 接入协议 |
MySQL 协议 |
自有 HTTP/TCP |
| 高并发点查 |
强(行存+主键) |
一般 |
| 数据更新 |
Unique Key UPSERT |
Mutation 异步 |
| 大宽表聚合 |
强 |
极强(向量化) |
| 物化视图 |
自动路由 |
手动/物化较弱 |
| 运维复杂度 |
低(FE+BE) |
中(需 ZooKeeper 等) |
结论:分析型重聚合用 ClickHouse,服务化实时数仓 / 高并发点查 / 频繁更新用 Doris,二者可在同一架构并存。
十一、生产避坑清单
- 分桶键别选低基数列(如性别),否则数据严重倾斜、BE 热点。
- Unique Key 点查先确认 Merge-on-Write(2.0+),否则读放大严重。
- 动态分区要显式开启,否则历史分区堆积、磁盘爆满。
- Routine Load 注意 label 唯一与并发,消费堆积时调
desired_concurrent_number。
- 一次导入的 Tablet 版本别太多,否则 Compaction 跟不上、查询变慢。
- 高并发点查走 PreparedStatement + 连接池,别每次建连。
- Colocate Join 需同桶数同键,否则退化成 Shuffle Join 反而更慢。
- FE Follower 必须奇数(3/5),Observer 可任意扩展查询。
- 大批量导入优先 Broker/Spark Load,别用 Stream Load 推超大文件。
- 监控 Compaction Score 与 BE 磁盘水位,是稳定性的两大先行指标。
本文补上了本博客大数据”采-存-算-协”链路里服务化实时数仓这块拼图:Kafka 负责采集、Spark/Flink 负责计算、Hive 负责离线、HBase/ClickHouse 负责存储与分析,Doris 则把分析结果直接变成可被业务高并发查询的服务。下一篇可延伸到 Doris 与 Hive/Iceberg 湖仓一体 或 Flink CDC 实时同步入 Doris,敬请期待。