随着大数据应用的不断深入,企业不再满足离线数据加工计算的时效,实时数据需求已成为数据应用新常态。伴随着实时分析需求的不断膨胀,传统的数据架构面临的成本高、实时性无法保证、组件繁冗、运维难度高等问题日益凸显。为了适应业务快速迭代的特点,帮助企业提升数据生产和应用的时效性、进一步挖掘实时数据价值,实时数仓的构建至关重要。
本文将分享如何基于Apache Doris,快速构建一个极速易用的实时数仓。
实时架构
Lambda架构
Lambda是比较经典的一款架构,以前实时的场景不是很多,以离线为主,当附加了实时场景后,由于离线和实时的时效性不同,导致技术生态是不一样的。而Lambda架构相当于附加了一条实时生产链路,在应用层面进行一个整合,双路生产,各自独立。在业务应用中,顺理成章成为了一种被采用的方式。
Lambda的架构分为三层:
- Batch Layer:离线数仓和离线计算
- Speed Layer:实时数仓和实时计算
- Serving Layer:统一查询入口,屏蔽各个数据存储系统的差异
Lambda架构的缺点:
- 需要维护两套计算逻辑:一般来说Spark,MapReduce主要用于离线计算逻辑,Flink用于实时计算逻辑。
- 需要维护两套分布式存储架构:离线数仓一般用HDFS,S3,Hive等构建。实时数仓会采用Clickhouse,Doris来构建。
- 最后,我们无法保障实时流的数据和离线数据的一致性。此时,只能通过离线数据定时清洗,保证数据的一致性。
为了解决Lambda架构遗留的问题,又引入了Kappa架构。
Kappa架构
Kappa从架构设计来讲,比较简单,生产统一,一套逻辑同时生产离线和实时。与Lambda架构的区别就是采用流批一体数仓,Kappa架构虽说目前很火,但也会产生一些问题:
- 消息中间件缓存的数据量和回溯数据有性能瓶颈。通常算法需要过去180天的数据,如果都存在消息中间件,无疑有非常大的压力。同时,一次性回溯订正180天级别的数据,对实时计算的资源消耗也非常大。
- 在实时数据处理时,遇到大量不同的实时流进行关联时,非常依赖实时计算系统的能力,很可能因为数据流先后顺序问题,导致数据丢失。
- Kappa在抛弃了离线数据处理模块的时候,同时抛弃了离线计算更加稳定可靠的特点。Lambda虽然保证了离线计算的稳定性,但双系统的维护成本高且两套代码带来后期运维困难。
痛点与需求
| 优点 | 缺点 | |
|---|---|---|
| Lambda | 1.架构简单 2.兼顾了离线批处理和实时流处理的优点 3.稳定,实时计算成本可控 4.离线数据易于修正 | 1.需要维护两套计算逻辑 2.需要维护两套分布式存储系统 3.很难保证离线,实时数据一致性 |
| Kappa | 1. 去掉了Lambda架构中Batch Layer模块,只需要维护实时模块 2.无需离线实时数据Merge | 1.依赖MQ的能力 2.实时数据处理时存在丢失数据的可能 |
为了兼顾两种架构的优点,一个统一的实时架构呼之欲出,。“统一”是指数据结构的统一,我们希望半结构化和结构化数据能够统一存储在一起。同时也指的是在一个平台中能完成多种分析和计算场合(实时查询,交互式分析,批量处理等)。
基于Apache Doris与Apache Flink构建实时数仓
Apache Doris介绍
首先先介绍一下Doris。Apache Doris 是一个基于 MPP 架构的高性能、实时的分析型数据库,以极速易用的特点被人们所熟知,仅需亚秒级响应时间即可返回海量数据下的查询结果,不仅可以支持高并发的点查询场景,也能支持高吞吐的复杂分析场景。
因此,Apache Doris 能够较好的满足报表分析、即席查询、统一数仓构建、数据湖联邦查询加速等使用场景,用户可以在此之上构建用户行为分析、AB 实验平台、日志检索分析、用户画像分析、订单分析等应用。
从上图中我们可以看到,数据源经过各种数据集成和加工后,通常会入库到实时数仓 Doris 之中。同时,部分数据会入到湖仓架构的 Hive 或 Iceberg 中,Doris会通过外表的方式联邦分析位于Hive、Iceberg、Hudi中的数据,在避免数据拷贝的前提下,查询性能大幅提升,然后,基于 Doris 构建一个统一的数仓,对外提供服务。
Doris的易用性
Doris整体架构如下图所示,Doris 架构非常简单
-
高度兼容MySQL协议
-
主从架构,不依赖其他组件
-
Frontend(FE) ,主要负责用户请求的接入、查询解析规划、元数据的管理、节点管理相关工作。
-
Backend(BE) ,主要负责数据存储、查询计划的执行。
-
任何节点,都可以线性扩展
- 多个FE节点之间通过BerkeleyDB java Edition进行Leader选举
- Leader负责元数据的写入,Follower节点会同步元数据信息,如果Leader宕机,其余Follower会参与选举Leader
- Observer不参与选举,只负责从Leader中同步元数据信息
- 部署多个BE节点,可以使数据根据分片规则和副本数分配到各个BE节点上,实现高可用
- 如果新增或者删除BE节点,Doris会自动完成数据迁移和副本均衡
Doris高性能
主要从三个方面来讲
- 存储引擎
- 查询引擎
- 优化器
- Doris支持丰富的索引类型
- 前缀稀疏索引:基于排序列查询的快速过滤
- ZoneMap Index :Segment 和每个列对应每个 Page 的统计信息
- Bloom Filter :对高基数列的等值过滤裁剪非常有效,Page级别
- Invert Index :基于BitMap,能够对任意字段实现快速检索
-
Doris提供了三种存储模型
- Aggregate Key 模型:相同 Key 的 Value 列合并,通过提前聚合大幅提升性能
- Unique Key 模型:Key 唯一,相同 Key 的数据覆盖,实现行级别数据更新
- Duplicate Key 模型:明细数据模型,满足事实表的明细存储
- Doris 也支持强一致的物化视图,物化视图的更新和选择都在系统内自动进行,不需要用户手动选择,从而大幅减少了物化视图维护的代价。
-
Doris在查询引擎方面,采用了MPP模型,节点间和节点内都并行执行,也支持多个大表的分布式 Shuffle Join。查询引擎向量化。
-
分区,分桶。SQL/Partition/Page Cache。
-
Doris 采用了 Adaptive Query Execution 技术, 可以根据 Runtime Statistics 来动态调整执行计划,比如通过 Runtime Filter 技术能够在运行时生成 Filter 推到 Probe 侧,并且能够将 Filter 自动穿透到 Probe 侧最底层的 Scan 节点,从而大幅减少 Probe 的数据量,加速 Join 性能。Doris 的 Runtime Filter 支持 In/Min/Max/Bloom Filter。
-
在优化器方面 Doris 使用 CBO 和 RBO 结合的优化策略
-
RBO(基于规则)支持
-
常量折叠
where event_date > str_to_date("2023-04-06", "%Y-%m-%d %H:%i:%s") -> where event_date > "2023-04-06 00:00:00" -
子查询改写
select * from t1 where t1.col1 in (select col2 from t2) -> select a.* from t1 a join t2 b on a.col1 = b.col2 -
提取公因式
select * from t where (a > 1 And b = 2) or (a > 1 and b = 3) or (a > 1 and b = 4) -> select * from t where a > 1 and (b = 2 or b = 3 or b = 4) -
智能预过滤
select * from t where (1 < a < 3 and b in ('a')) or (2 < a < 4 and b in ('b')) -> select * from t where (1 < a < 4) and (b in ('a', 'b')) and ((1 < a < 3 and b in ('a')) or (2 < a < 4 and b in ('b'))) -
谓词下推(先Filter再Join)
select * from t1 a join t2 b on a.id = b.id where a.col1 > 100 and b.col2 < 300 -> select * from (select * from t1 where t1.col1 > 100) a join (select * from t2 where t2.col2 < 300) b on a.id = b.id
-
-
CBO(基于代价)
- Join Reorder
- Colocation Join
- Bucket Join
-
Doris数据导入导出
数据导入:
- Binlog Load: 官方文档上只写了MySQL导入的方式,需要使用Alibaba Canal组件配合使用。
- Broker Load: 是一个异步的导入方式,支持的数据源取决于Broker进程支持的数据源。
- Routine Load: 仅支持从 Kafka 进行例行导入。
- Spark Load:主要用于初次迁移,大数据量导入 Doris 的场景。
- Stream Load: 使用的是HTTP接口。
- MySQL Load: MySQL标准的LOAD DATA语法。
- S3 Load:导入方式与Broker Load基本相同。
数据导出:
- mysqldump
- select ... into outfile ...
Doris数据湖分析
多源数据目录(Multi-Catalog)是 Doris 1.2.0 版本中推出的功能,旨在能够更方便对接外部数据目录,以增强Doris的数据湖分析和联邦数据查询能力。
原来版本的Doris只有Database -> Table的两层元数据层级,新版本多了一个Catalog层级。Catalog -> Database -> Table 的三层元数据层级。其中,Catalog 可以直接对应到外部数据目录。目前支持的外部数据目录包括:
- Hive
- Iceberg
- Hudi
- Elasticsearch
- JDBC: 对接数据库访问的标准接口(JDBC)来访问各式数据库的数据。
该功能将作为之前外表连接方式(External Table)的补充和增强,帮助用户进行快速的多数据目录联邦查询。
Doris生态扩展
Doris提供了非常丰富的生态扩展,用户可以通过这些扩展,操作Doris,例如:
-
Spark Doris Connector
- 代码地址:github.com/apache/incu…
- 支持从
Doris中读取数据 - 支持
Spark DataFrame批量/流式 写入Doris - 可以将
Doris表映射为DataFrame或者RDD,推荐使用DataFrame。 - 支持在
Doris端完成数据过滤,减少数据传输量。
-
Flink Doris Connector
- 代码库地址:github.com/apache/dori…
- 可以支持通过 Flink 操作(读取、插入、修改、删除) Doris 中存储的数据。
- 可以将
Doris表映射为DataStream或者Table。 - 修改和删除只支持在 Unique Key 模型上
- 目前的删除是支持 Flink CDC 的方式接入数据实现自动删除,如果是其他数据接入的方式删除需要自己实现。
-
DataX doriswriter
- 代码地址:github.com/apache/dori…
- 需要配合 DataX 服务一起使用。
-
Seatunnel
- Seatunnel Connector Flink Doris
- Seatunnel Connector Spark Doris
如何构建实时数仓
简单了解Doris的基本概念,接下来,看一下如何基于 Apache Doris 构建极速易用的实时数仓的架构?
因为 Doris 既可以承载数据仓库的服务,也可以承载 OLAP 等多种应用场景。因此,它的实时数仓架构变得非常简单。
-
我们通过Flink CDC/Canal/Debeziunm将RDS的数据或者通过Logstash/Filebeat将日志类数据实时同步到Kafka中。
-
通过Doris的Routine load 将 Kafka 等消息系统中的数据,实时同步到 Doris。当然,也可以使用Flink Doris Connector/Seatunnel Connector Flink Doris实时同步到Doris。
-
在 Doris 内部,基于 Doris 不同的表模型、Rollup、以及物化视图的能力,构建实时数仓。
- ODS 层通常会使用明细模型构建
- DWD 层通过 SQL 调度任务,对 ODS 数据抽取并获取
- DWS 和 ADS 层则可以通过 Rollup 和物化视图的技术手段构建。
-
Doris 还支持基于 Iceberg、Delta Lake 和 Hudi 的数据湖服务,提供一些联邦分析和湖仓加速的能力。
这样我们便完成了基于 Doris 构建一个实时数仓。在实时数仓之上,我们可以构建 BI 服务、Adhoc 查询、多维分析等应用。
如何保证一致性
Flink CDC 通过 Flink Checkpoint 机制结合 Doris 两阶段提交可以实现端到端的 Exactly Once 语义。具体过程分为四步:
- 事务开启(Flink Job 启动及 Doris 事务开启):当 Flink 任务启动后, Doris 的 Sink 会发起 Precommit 请求,随后开启写⼊事务。
- 数据传输(Flink Job 的运⾏和数据传输):在 Flink Job 运⾏过程中, Doris Sink 不断从上游算⼦获取数据,并通过 HTTP Chunked 的⽅式持续将数据传输到 Doris。
- 事务预提交:当 Flink 开始进⾏ Checkpoint 时,Flink 会发起 Checkpoint 请求,此时 Flink 各个算⼦会进⾏ Barrier 对⻬和快照保存,Doris Sink 发出停⽌ Stream Load 写⼊的请求,并发起⼀个事务提交请求到 Doris。这步完成后,这批数据已经完全写⼊ Doris BE 中,但在 BE 没有进⾏数据发布前对⽤户是不可⻅的。
- 事务提交:当 Flink 的 Checkpoint 完成之后,将通知各个算⼦,Doris 发起⼀次事务提交到 Doris BE ,BE 对此次写⼊的数据进⾏发布,最终完成数据流的写⼊。
当预提交成功,但 Flink Checkpoint 失败时,该怎么办?这时 Doris 并没有收到事务最终的提交请求,Doris 内部会对写入数据进行回滚(rollback),从而保证数据最终的一致性。
如何DDL 和 DML 同步
随着业务的发展,部分⽤户可能存在 RDS Schema 的变更需求。当 RDS 表结构变更时,⽤户期望 Flink CDC 不但能够将数据变化同步到 Doris,也希望将 RDS 表结构的变更同步到 Doris,⽤户则无需担⼼ RDS 表结构和 Doris 表结构不⼀致的问题。
Hive如果对数据表的Schema进行新增或者删除,都会触发数据同步,就会导致修改Schema的效率非常低。
Doris引入了Light Schema Change来解决这个问题,Light Schema Change 只修改了 FE 的元数据,并没有同步给 BE。但这样就有可能产生FE和BE的Schema不一致,我们对 BE 的写出流程进⾏了修改,具体包含三个⽅⾯。
- 数据写⼊:FE 会将 Schema 持久化到元数据中,当 FE 发起导⼊任务时,会把最新的 Schema 一起发给 Doris BE,BE 根据最新的 Schema 对数据进⾏写⼊,并与 RowSet 进⾏绑定。将该 Schema 持久化到 RowSet 的元数据中,实现了数据的各⾃解析,解决了写⼊过程中 Schema 不⼀致的问题。
- 数据读取:FE ⽣成查询计划时,会把最新的 Schema 附在其中⼀起发送给 BE,BE 拿到最新的 Schema 后对数据进⾏读取,解决读取过程中 Schema 发⽣不⼀致的问题。
- 数据 Compaction:当数据进⾏ Compaction 时,我们选取需要进⾏ Compaction 的 RowSet 中最新的 Schema 作为之后 RowSet 对应的 Schema,以此解决不同 Schema 上 RowSet 的合并问题。
经过对 Light Schema Change 写出流程的优化后, 单个 Schema Chang 从 310 毫秒降低到了 7 毫秒,整体性能有近百倍的提升,彻底的解决了海量数据的 Schema Change 变化难的问题。
有了 Light Schema Change 的保证, Flink CDC 能够同时⽀持 DML 和 DDL 的数据同步。那么是如何实现的呢?
- 开启 DDL 变更配置:在 Flink CDC 的 MySQL Source 侧开启同步 MySQL DDL 的变更配置,在 Doris 侧识别 DDL 的数据变更,并对其进⾏解析。
- 识别及校验:当 Doris Sink 发现 DDL 语句后,Doris Sink 会对表结构进⾏验证,验证其是否⽀持 Light Schema Change。
- 发起 Schema Change :当表结构验证通过后,Doris Sink 发起 Schema Change 请求到 Doris,从⽽完成此次 Schema Change 的变化。
解决了数据同步过程中源数据⼀致性的保证、全量数据和增量数据的同步以及 DDL 数据的变更后,一个完整的数据同步⽅案就基本形成了。
如何选择数据模型和数仓分层
一般来说,数仓分层主要分为四层:ODS,DWD,DWS,ADS
- ODS为原始数据层:存放未经过处理的原始数据至数据仓库,结构上与源系统保持一致,是数据仓库的数据准备区。对于Doris而言,ODS层通常会采用Duplicate Key模型。
- DWD为数据明细层:该层一般保持和ODS层一样的数据粒度,并且提供一定的数据质量保证。该层可能也会做一部分的聚合操作,提高数据可用性。从ODS 到 DWD 层数据的抽取,一般会采用微批调度调度的方法,需要借助外部的调度工具例如 DolphinScheduler 或 Airflow 等来对 ETL SQL 进行调度。
- DWS为数据服务层:又称数据集市或宽表。按照业务划分,如流量、订单、用户等,生成字段比较多的宽表,用于提供后续的业务查询,OLAP分析,数据分发等。这一层,我们可以选择Aggregate Key模型或者在Base表上,创建不同的Rollup或物化视图对Base表进行聚合计算
- ADS为数据应用层:存放数据产品个性化的统计指标数据。根据DM层与ODS层加工生成。这一层,我们可以选择Aggregate Key模型或者在Base表上,创建不同的Rollup或物化视图对Base表进行聚合计算