做数据平台的同学,这两年应该没少听说 Doris。不管是实时数仓、大数据分析,还是 BI 报表加速,Doris 几乎都会出现在候选名单里。我第一次在一个几十亿行明细的网约车订单场景里跑 Doris 时,说实话是被它的查询速度吓了一跳的——一个需要聚合多维指标、原来在 Hive 上要跑二十多分钟的任务,切到 Doris 之后秒级返回。这篇文章不打算写官方文档翻译,而是从大数据从业者的视角,把 Doris 的核心特性、架构设计、部署实战和坑位都过一遍,帮助正在做技术选型或者准备入手 Doris 的同学快速判断它适不适合自己的场景。
Doris 真正的价值不在于单点性能有多夸张,而在于它把“离线批量分析”和“实时交互查询”揉进了同一个系统里。你不需要再维护一套复杂的 Lambda 架构,不需要在 HBase、ES、Presto、ClickHouse 之间来回兜圈子,一张表既能接实时流,又能批量导历史数据,查询还不需要提前预聚合。这种“一个引擎打天下”的思路,在大数据工程里是很奢侈的,但 Doris 确实做到了。下面我从原理讲到实战,把重点都拆开说清楚。
1. 先盘清楚:Doris 解决的是大数据分析里的哪些问题
1.1 大数据架构里的典型痛点
传统的大数据分析链路,很多团队是这么搭的:Kafka 接日志,Flink 做实时 ETL,结果落到 HBase 或者 ES 里供实时查询;历史数据进 Hive,定时跑 Spark 任务出报表;再挂一个 Presto 或者 Impala 做交互式查询。这套架构本身没什么问题,但维护成本很高,而且链路一旦长了,数据口径容易对不上。
我见过太多团队,实时链路和离线链路各做一套数仓,同一个“订单金额”指标,实时算出来是 100 万,离线算出来是 98 万,两边业务方天天扯皮。根源就在于实时数据和离线数据分别存在不同的存储引擎里,查询引擎也不一样,很难做到一份数据、统一口径。还有一个更直接的痛点:明细查询慢。业务方经常要看某一天某个用户的所有行为明细,这种点查在 Hive 上就是灾难,在 HBase 上又要提前设计 RowKey,灵活性极差。
Doris 一开始就是冲着这些问题设计的。它的定位是 MPP(大规模并行处理)分析型数据库,既有 ClickHouse 那种列式存储的查询性能,又有 Presto 那种灵活做复杂 Join 的能力,还提供了类似 Greenplum 的分布式事务能力。对大数据工程师来说,它最大意义在于把实时和离线两条链路合并成了一条,查询引擎、存储引擎、数据模型完全是同一套,不再有口径割裂的问题。
1.2 Doris 的定位:一个 MPP 分析型数据库
Doris 的架构核心是两个角色:FE(Frontend)和 BE(Backend)。FE 负责元数据管理、SQL 解析、生成执行计划、查询调度,类似于一个“指挥官”;BE 负责数据存储和查询计算,类似于“一线工人”。FE 和 BE 都可以水平扩展,节点挂掉之后有自动故障转移机制,不需要额外依赖 ZooKeeper 之类的第三方组件。
从使用感受上说,Doris 更像是“分布式 MySQL + ClickHouse 的结合体”。它兼容 MySQL 协议,你写 SQL 的习惯可以完全保留,直接用 Navicat、DBeaver 这类 MySQL 客户端就能连上去。但底层存储又完全是列式存储加 MPP 并行处理,所以分析型查询的性能远远超过普通 MySQL。很多团队从 MySQL 迁移到 Doris 之后,报表接口的耗时直接从几秒降到了几十毫秒,体感非常明显。
1.3 为什么是 Doris,而不是其他 OLAP 引擎
每和一个团队聊 Doris,都会有人问:为什么不用 ClickHouse?为什么不用 StarRocks?为什么不用 Greenplum?我的看法是:选 Doris 不是因为别的引擎不行,而是 Doris 在“易用性”和“功能完整性”之间找到了一个很舒服的平衡点。
ClickHouse 的单表查询确实极快,但多表 Join 支持得不好,并发能力也比较弱,更适合做单表大宽表的分析。Presto 的交互查询很强,但它没有自己的存储层,底层还是要靠 Hive 或者对象存储,做不到实时写入。Doris 自己管存储、自己管计算、自己管事务,还支持标准的 Join、子查询、窗口函数,这一点在开源 OLAP 引擎里非常有优势。再加上 Doris 已经捐给了 Apache 软件基金会,社区活跃度稳定,版本迭代也快,企业用起来放心。
2. 核心技术拆解:为什么它快得不像传统数仓
2.1 MPP 并行计算框架
MPP 架构是 Doris 高性能的基础。一个查询发到 FE 之后,FE 会把它拆成多个执行计划片段,分发到不同的 BE 节点上并行执行。每个 BE 只处理自己本地的那部分数据,最后再汇总结果。因为数据就在本地,扫描过程不需要把全量数据拉到一台机器上,网络开销被压到了最低。
对比一下 Hive 的 MapReduce 模型就明白了。Hive 跑一个聚合任务,要经历 Map 阶段、Shuffle 阶段、Reduce 阶段,中间还有大量落盘,每一层都有网络和磁盘 IO。Doris 的 MPP 引擎把所有计算尽量下推到数据所在的节点,扫描完直接做部分聚合,只有中间结果才需要传输。这个策略让 Doris 在几十亿行数据的聚合场景下,依然能做到秒级返回。
2.2 列式存储、前缀索引与数据裁剪
Doris 底层是列式存储,每列单独存文件。分析型 SQL 往往只需要读少数几个字段,列式存储天然就能做到“要什么读什么”。举一个我实际见过的例子:一张订单表有 80 个字段,每天新增 2000 万行,业务方只查“城市、订单量、GMV”三个维度。列式存储只需要读取这三个列的文件,I/O 量比行式存储少了十倍不止,查询能不快的道理就在这。
Doris 每个表可以指定 Key 列,数据会按照 Key 列的顺序排序后存储。它给这层排序做了一个类似“目录”的前缀索引(Short Key Index),查询时如果条件命中了前缀列,就能直接跳过大量无关数据块。再加上每个数据块都会记录最大值、最小值的 ZoneMap 索引,以及可选的 BloomFilter 索引,三层索引下来,扫描的数据量会被裁剪得极少。很多人低估了 Doris 的索引能力,实际上在大数据量下,真正读出来的数据往往只有总量的百分之几。
2.3 向量化执行引擎:从逐行到批量
Doris 从 1.1 版本开始默认启用向量化执行引擎,这是一个质变。传统数据库执行 SQL 是一行一行地处理,每行都要经历函数调用、类型判断、分支跳转,CPU 浪费很大。向量化引擎的思路是一次处理一批数据(比如 4096 行),利用 CPU 的 SIMD 指令做批量计算,让内存带宽和 CPU 流水线都充分利用起来。
打个比方,传统执行方式就像快递员按个派件,一次拿一件跑一趟;向量化执行就像把几百个包裹一次性装车,再沿着小区一路投放。处理单行数据的开销被平摊到了上百行里,整体吞吐自然大幅提升。SSB 测试里 Doris 的查询性能可以跑到传统数仓引擎的几倍到十几倍,和这个向量化设计有直接关系。
2.4 CBO 优化器的实际价值
Doris 内置了基于代价的优化器(CBO,Cost-Based Optimizer)。它会收集表的统计信息,包括行数、数据大小、列基数等,然后估算不同执行路径的代价,自动决定最优的 Join 顺序、是否做谓词下推、是否选择 Colocate Join 等。
这个能力在复杂查询里特别关键。比如一张大表同时 Join 三张小表,Join 顺序不同,中间结果量差出几个数量级。CBO 能自动把小表作为驱动表,控制中间结果膨胀。我们内部有些 SQL 是直接从 Hive 迁移过来的,在一个多表 Join 的场景里,Doris 的优化器跑出来的执行计划比我们自己手工调优的还要稳。对使用者来说,这减少了大量手动调优成本。
3. 核心特性与业务优势:不止是快
3.1 实时与离线分析一体化
Doris 支持多种数据导入方式:Stream Load 适合小批量实时写入,Routine Load 可以直接订阅 Kafka 数据流做准实时导入,Broker Load 适合大批量从 HDFS、对象存储导入历史数据,Flink Doris Connector 则让实时计算链路无缝衔接。最妙的是,无论数据通过哪种方式写入,查询接口完全一致。
我们在实际项目中是把日志数据从 Kafka 通过 Routine Load 直接进 Doris,同时每天凌晨用 Broker Load 把前一天的历史分区数据补进同一张表。业务方看到的永远是一张完整的表,既查得到最近几秒的数据,也能查历史 180 天的数据。这个能力直接省掉了一套 HBase 实时链路,也省掉了“实时结果对不上离线结果”的争论。
3.2 高并发点查能力
很多 OLAP 引擎的单查询性能不错,一上并发就崩。Doris 在高并发场景的表现在 MPP 引擎里是很能打的,原因在于它的本地索引和数据分区分桶设计。点查只要命中 Key 列,查询可以直接定位到少数几个 Tablet,不需要全表扫描。
在线服务场景也可以用它,比如用户详情的多维查询、订单明细查询、标签命中查询。我们之前有个接口,QPS 要求 500,P99 延迟要求 300 毫秒以内,用 Doris 做存储后顺利过了压测。但要强调一点,Doris 是分析型数据库,不是 OLTP 数据库,它不适合做高频事务更新,单条数据更新也不是它的强项。把它放到“分析服务”的位置上,它会表现得很亮眼。
3.3 三种数据模型到底怎么选
Doris 提供了三种表模型,选错模型是很多新手最常见的坑。
Duplicate 模型适合明细数据,比如日志、行为流水,不去重、不更新,每行都会保留。Aggregate 模型会在导入时预聚合,比如把点击量按天、按用户预先 SUM 好,这样查询时数据量已经小了很多,速度会再上一个档次。但它有一个限制,只能查询预聚合的列。Unique 模型适合订单、用户档案等有更新需求的场景,主键相同的新数据会覆盖旧数据,保证数据唯一性。
举一个网约车场景的例子:订单明细表用 Duplicate 模型保存原始流水;订单聚合统计表用 Aggregate 模型按天、城市、司机聚合订单数和金额;订单状态是动态变更的,比如从“进行中”改到“已完成”,这种就要用 Unique 模型。实际建表前,先想清楚业务是“只进不出”“只查汇总”还是“频繁更新”,再定模型,能少走很多弯路。
3.4 周边生态和兼容性
Doris 的生态整合能力,在开源 OLAP 引擎里算做得比较完整的。对外兼容 MySQL 协议,几乎所有 BI 工具都能直接接入;对内提供了 Hive Catalog、Iceberg Catalog、Hudi Catalog,可以直接做联邦查询,不用把数据复制进来也能关联分析。Flink 连接器是官方维护的,支持 Exactly-Once 语义,Spark 读 Doris 也很方便。
有人会纠结 Doris 和 Hive 的关系。我的看法是:Hive 更适合做离线数仓的底层存储和 ETL 计算层,Doris 更适合做服务层和加速层。以校园大数据项目为例,可以用 Spark/Hive 做数据清洗和宽表计算,结果落到 Doris,再用 Flask + ECharts 做可视化展示,Doris 在里面承担的就是“数据服务中枢”的角色。清洗和计算交给数据湖生态,展示和查询交给 Doris,各干各擅长的事,配合起来效率最高。
4. 落地实战:从集群部署到查询优化
4.1 集群规划与部署步骤
Doris 部署极简,三台机器起步就能搭一个高可用集群。机器规格上,建议每一台至少 16 核 64G 内存,磁盘用 SSD,BE 节点数建议 3 个以上。FE 节点可以单独部署,也可以跟 BE 混合部署,小规模集群混合部署没问题,但生产环境建议 FE 独立节点,避免 BE 的内存抖动影响 FE 的稳定性。
部署步骤我一般这么走:
- 从 Doris 官网下载对应版本的二进制发行包,解压到
/opt/doris。 - 配置 FE:修改
fe/conf/fe.conf,重点设置priority_networks,因为多网卡机器如果不指定网段,FE 会取错 IP 导致节点无法通信。 - 启动 FE:执行
sh bin/start_fe.sh --daemon,然后通过mysql -h127.0.0.1 -P9030 -uroot登录验证。 - 配置 BE:修改
be/conf/be.conf,同样设置priority_networks,然后执行sh bin/start_be.sh --daemon。 - 在 FE 上执行
ALTER SYSTEM ADD BACKEND "be_ip:9050";,再用SHOW BACKENDS看状态是否变为Alive。
第一次部署常见的坑就是priority_networks没配好,FE 一直显示 BE 是 Dead 状态。检查这个配置的第一个位置,而不是去查什么复杂的原因。另外 FE 的 JVM 内存默认是 8G,如果小机器内存紧张,记得在fe.conf里调小JAVA_OPTS,否则集群可能因为内存不足频繁告警。
4.2 建表与查询调优的几条实战经验
建表的时候,分区和分桶策略直接影响查询性能。我常用的经验是:按日期做分区,按高频等值过滤字段(比如用户 ID、城市 ID)做分桶。分桶数不宜过小也不宜过大,每个桶的数据量控制在 1GB 左右是实践经验。一个常见错误是拿低基数字段分桶,比如按“省份”分桶,全省的数据都压到同一个桶里,并行度根本提不上来。
Key 列的顺序也很重要。前缀索引只对 Key 列的最前面几个字段生效,所以要把最常用于过滤的字段排在前面。比如订单场景,顺序应该是订单日期、城市、用户 ID,这样“查某一天某城市的所有订单”会被前缀索引直接优化。如果 Key 列顺序反了,索引就失效,查询全表扫描,性能天差地别。
Join 优化方面,Doris 支持 Colocate Join——如果两张表的分桶方式和分桶数完全一致,Join 可以在本地完成,不需要跨节点传输数据。把多张常用关联的大表设计成相同分桶规则,收益非常明显。另外小表 Join 大表时,Doris 会自动做广播,把小表复制到各节点,这种场景不用手工干预都很稳。高基数聚合时,建议近似去重函数BITMAP或HLL,误差很小但内存开销能降一个数量级,用来算 UV 这种指标非常合适。
4.3 我在实际项目中踩过的坑
没有哪个数据库能免于踩坑,Doris 也一样。把我遇到最多的问题整理成表格,给大家做一个速查:
| 问题现象 | 常见原因 | 排查思路 |
|---|---|---|
| BE 节点状态一直 Dead | 网络不通、priority_networks配置错误 | 检查网络和端口 9050、8040 是否通,查看 BE 日志 |
| 导入时报 “Too many open files” | 系统文件句柄限制太低 | 调大ulimit -n,BE 需要至少 65536 |
| 查询报 “Memory limit exceeded” | BE 内存不足或查询过大 | 调mem_limit,优化 SQL 减少数据扫描量 |
| Tablet 副本数异常告警 | 副本数设置不合理,或磁盘空间不均衡 | 分桶数重新规划,关注集群的磁盘均衡状态 |
最隐蔽的一个坑是:用 Aggregate 模型建表后,查询里对 Value 字段既没有求和也没做聚合,直接SELECT原生值,结果查出来是一堆没有意义的中间状态数据。Aggregate 模型不是普通的明细表,Value 列必须用聚合函数访问,建表时要想清楚查询场景,否则数据都堆进去了但查出来的结果完全对不上。
4.4 典型大数据场景怎么用它
校园大数据方向的案例里,Doris 经常被当作“数据可视化服务端”。前面用 Python 或 Spark 做数据清洗,把清洗后的数据导入 Doris,再用 Flask 提供 API,ECharts 画大屏。Doris 在这类项目中最大的好处是:建立一张汇总表之后,即使数据量到了百万级,按院系、年级、课程维度做统计,前端接口响应依然很快,不会出现图表转半天才出来一个圈的情况。
网约车大数据项目是我觉得最贴近 Doris 真实业务形态的场景。订单流水实时进 Doris,司机绩效、城市热力、时长分布这类指标直接在 Doris 里跑实时查询;历史数据按天分区归档,离线分析直接查同一张表。整个项目用 Spark 做清洗、Doris 做分析和存储、Flask + ECharts 做可视化,一条链路串下来,架构清爽,每个模块的分工也足够清晰,非常适合作为数据相关项目的技术骨架。
5. 常见问题速查与排障思路实录
5.1 客户端连接和数据导入问题
连接 Doris 失败,优先级最高的检查点有两个:一是 FE 的查询端口 9030 是否开放,二是用户名和密码是否匹配。如果使用 MySQL 客户端连 Doris,有时会报认证插件不支持的错,需要在客户端加--default-auth=mysql_native_password参数。这些细节不起眼,但在跨版本迭代的时候特别容易碰到。
数据导入是新手最容易踩雷的环节。Stream Load 导入如果报Label Already Exists,说明上一次导入任务的标签还在事务里,用SHOW LOAD查看该 Label 状态并清理后再重试。Routine Load 订阅 Kafka 时,如果报 offset 越界,大概率是 Kafka 的清理策略把旧消息清掉了,而 Doris 记录了旧的 offset,重置消费位点后一般能解决。养成“导入任务都要设置 Label”的好习惯,排查问题的时候会方便非常多。
5.2 一些 SQL 执行与性能问题的排查经验
一个典型的慢查询场景:SQL 没建好前缀索引,导致全分区扫描。排查的时候可以先看EXPLAIN输出,确认实际扫描分区数。如果显示所有分区都扫了,就要考虑调整过滤条件或表设计。Doris 的EXPLAIN非常好用,能把每个算子的行数和耗时都展示出来,大多数性能问题在执行计划里就能看出来端倪。
另一个常见问题是 Join 时内存被打爆。两张超大表做 Join,如果没有任何等值关联条件,会产生笛卡尔积,这是绝对要避免的。即使有等值条件,数据倾斜也容易导致某个 BE 节点内存暴涨。遇到这种情况,先看倾斜 Key 能不能通过加随机数打散,再考虑是否用 Broadcast 或 Colocate Join 来规避。还有一点要注意,LIMIT N语句里 N 很大时需要排序,排序量过大会占内存,尽量在 SQL 里用时间分区先把数据范围缩小。
5.3 与周边组件配合时的常见注意点
通过 Presto 连接 Doris 做联邦查询时,我遇到过类似 “Column missing” 的报错,排查到最后发现是列名大小写不匹配。Doris 的表结构如果定义了大小写混合的列名,Presto 端未加引号的标识符会被统一转成小写,两边元数据对不上,自然就报 missing。解决办法很简单:建表统一用小写列名,或者查询时把列名用双引号包起来。另一个容易踩的坑是 Hive Catalog 同步表结构之后,Hive 那边改了 schema 但 Doris 的元数据缓存还没刷新,需要执行REFRESH CATALOG刷新一下。
Flink 实时写入 Doris 时,如果任务并行度过高,且 BE 节点的小文件过多,会造成导入性能下降。官方连接器一般建议并行度不要超过 BE 节点数量的三倍。Spark 批量导入时,如果单线程写太慢,可以用 Broker Load 配合多副本并行,吞吐能提升好几倍。和周边组件配合时,先把边界职责搞清楚,再考虑调优,否则定位问题会非常费劲。
6. 学习路径建议:按这个顺序少走弯路
6.1 基础准备
入门 Doris 之前,最好有一些 SQL 基础,理解分组聚合、多表关联、窗口函数这些概念。大数据的整体知识最好也有一点,至少要知道 HDFS、Hive 和实时数仓是干嘛的。你可以没有二十年大数据经验,但得清楚 Doris 在整个链路里处于“查询服务层”这个位置,这样学起来才有坐标系。
如果身边没有现成集群,用单机版 Doris 就够了。官网有 Quick Start 文档,下载二进制包,配置好 FE 和 BE,导入官方示例数据,跑几个查询,核心体验基本就到位了。单机部署和集群部署的差异没有那么大,先把一套跑通,再扩展节点也来得及。
6.2 一套实操学习路线
第一步,理解 Doris 的 FE / BE 架构和数据模型,把 Duplicate、Aggregate、Unique 三种模型的区别用自己的话讲清楚。第二步,跟着官方文档完成部署,建一张订单表,用 Stream Load 和 Broker Load 两种方式导入数据,跑常见聚合查询。第三步,学习 Routine Load 接入 Kafka,尝试做一张实时更新的汇总表。第四步,开始看执行计划,对着慢查询做优化,把分区分桶、Key 列顺序、Colocate Join 这些概念一一验证一遍。第五步,接一个可视化工具,把查询结果展示出来,形成完整闭环。
这套路线走完,基本能应付大部分生产环境的使用场景了。之后如果碰到大数据 SQL 面试相关的问题,Doris 大概率会被问到:它和 Hive 有什么区别、和 ClickHouse 有什么区别、在什么场景下选它。这些问题的答案其实都在上面的实践里,不是靠背下来的,而是跑过之后自然就能说出来。
6.3 资源推荐
官方文档永远是第一手资料,部署配置、SQL 语法、导入导出都有很详细的说明。Doris 官方公众号和社区论坛也经常发布版本预告和性能调优案例,值得长期关注。如果你想系统性学习,可以看看 Doris 官网上的最佳实践栏目,里面有各大厂公开的架构演进和调优文章,参考价值很高。
我个人实际操作下来的体会是:Doris 的学习曲线在同类 OLAP 引擎里属于很平缓的,基本照着官方文档走一遍就能上手,真正难的是把表模型选对、把分区分桶设计好。还有一个小技巧想分享给正在做选型的人:不管网上评测怎么说,都先拿一份自己业务最复杂的 SQL,部署一个测试环境导入小批量数据对比一下,用真实查询验证,比看任何 PPT 都靠谱。Doris 到底适不适合你的场景,跑一次就知道了。