本文对目前主流数仓架构及数据湖方案的不足之处进行分析,介绍了字节内部基于实时/离线数据存储问题提出的的湖仓一体方案的设计思路,并分享该方案在实际业务场景中的应用情况。最后还会为大家分享 LAS 团队对湖仓一体架构的未来规划。
文末更有专属彩蛋,新人优惠购福利,等着你来解锁!
文丨 火山引擎LAS团队
李铮
/ 主流数仓架构 /
目前主流的数仓架构—— Lambda 架构,能够通过实时和离线两套链路、两套代码同时兼容实时数据与离线数据,做到通过批处理提供全面及准确的数据、通过流处理提供低延迟的数据,达到平衡延迟、吞吐量和容错性的目的。在实际应用中,为满足下游的即席查询,批处理和流处理的结果会进行合并。
Lambda 架构的优势集中体现在职责边界明确、高容错性与复杂性隔离上,主要包含以下三方面:
● 职责边界清晰 :流处理专注于增量数据计算,批处理专注于全量数据计算;
● 容错性 :批处理 T+1 全量计算的结果会覆盖流处理的结果,意味着流处理假如有异常、可以被批处理计算时修复;
● 支持复杂性隔离 :批处理的是离线就绪数据,可以很好的掌控。流处理采用增量方式处理实时数据,复杂性要高很多。通过分开批处理和流处理两套链路,把复杂性隔离到流处理,可以很好的提高整个系统的鲁棒性和可靠性。
具有上述优点的同时,Lambda 架构同样存在一系列尚待优化的问题, 涉及到计算、运维、成本等方面 :
● 实时与批量计算结果不一致引起的数据口径对齐问题:由于批量和实时计算走的是两个计算框架和计算程序,计算结果往往不同,经常出现一个数字当天查看的数据与第二天的不同,数据校准困难;
● 开发和维护的复杂性问题:Lambda 架构需要在两个不同的 API 中对同样的业务逻辑进行两次编程:一次为批量计算,一次为流式计算。针对同一个业务问题产生了两套代码,形成了双倍的维护运维成本;
● 资源成本问题:两套链路的存储介质不同、计算引擎也不同,会造成数据存储和资源翻倍。
综上所述,主流数仓架构本质上有两个痛点:实时/离线计算层不统一;实时/离线存储层不统一。本文将聚焦于实时/离线存储层统一的实现能力上,希望能够有一套同时支撑实时场景下的增量处理和离线场景下的高效分析存储方案。
/ 数据湖方案 /
Hudi 作为数据湖框架的一种开源实现,其核心特性能够满足对于实时/离线存储层统一的诉求:
● 支持实时消费增量数据: 提供 Streaming Source/Sink 能力 ,数据分钟级可见可查;
● 支持离线批量更新数据:保留原有 Hive 的 Insert 和 Overwrite 能力,并且提供对历史数据的更新删除能力 Upsert/Update/Delete;
● 跟 Spark、Flink、Presto 等计算引擎集成比较好。
尽管 Hudi 解决方案已经能够实现一份存储同时包含实时和离线两种场景,但由于数据的分钟级可见,它依然存在一定的优化空间,无法作为实时数仓存储的标准方案。
/ 湖仓一体诉求 /
批流统一的湖仓一体存储需要满足更多的诉求,相匹配的就需要具备更强硬的核心能力,包括批式/流式读写能力与支持多种引擎的集成能力:批式读写提供不低于 Hive 表的吞吐,提供分区并发更新能力;流式读写能够端到端处理秒级低延迟,具备千万级 RPS 写入和消费能力,提供 ExactlyOnce 和 At Least Once 消费语义;支持多种引擎的集成能力,实现查询引擎集成化。
我们针对以上需求,提出了更加高效的湖仓一体服务方案。接下来将从 整体架构、数据分布、数据模型、数据读写 以及 BTS 架构 这 5 个方面,向大家介绍该方案的设计思路。
/ 整体架构 /
为解决实时性问题,字节内部在数据湖上自研了基于内存的服务,形成了一套高吞吐、高并发、秒级延迟可见的实时数据湖方案。整体架构如下:
架构底层为数据持久化层。复用 Hudi 的能力实现数据存储。文件分布和 Hudi 一致,通过列存的 base 文件与行存的 log 文件进行数据存储,基于时间戳维护数据版本。通过 filegroup 的方式对文件进行分组,相同逐渐的数据存储在同一个文件组内。后期结合数据构建索引能力,能够比较大幅度提升数据入湖和查询的性能。
架构的第二层是元数据层。对数据湖的元数据进行管理,包括表、分区以及 instant、timeline、snapshot 等这些数据湖特有的元数据。在 这一层不光实现了元数据的管理,还能够解决多并发写入的冲突检查和解决,保障 ACID 能力 。
架构的第三层是服务层。主要包含两个组件:BTS 和 TMS。BTS 是基于内存构建的服务层,通过内存加速数据读写操作,解决实时场景下数据生产消费的时效性问题。TMS 是聚焦在表优化的服务,会异步做一些 log 文件和 base 文件的compaction/小文件合并优化等操作。
/ 数据分布 /
基于上述湖仓一体存储架构,新增了中间的实时加速服务层,数据的物理分布整体采用 Hudi 的结构,如下图所示:
针对图中的分布情况,为了方便大家进一步的理解,图中涉及到的各部分含义如下:
● Table:对应一张 Hudi 表;
● Partition:可以按照指定字段进行分区,对应的是一个 Storage 的目录(类似 Hive 分区的概念);
● FileGroup:也是 Hudi 的一个概念,可以理解为一个文件组,这个文件组中包含列存的 base file 和行存的 log file,主键表中相同主键的数据会被分配到同一个 File Group 中;
● Block:Table Server 中的一块内存空间。对于主键表,会按照主键基于时间戳做排序后合并 Flush 成 Hudi 的 log file;对于非主键表,会按照 offset 有序进行 Flush;
● WAL Log:Block 对应的持久化存储,在 Block 遭驱逐后可用作流式回溯;
● 计算引擎中 Task 和 Block 是一对多的关系。
以上便是数据的物理分布情况,基于上述分布信息,我们接下来介绍数据模型的基本情况。
/ 数据模型 /
对于一张流批一体表,需要有两个视图,增量视图和快照视图:
增量视图对应的是一张 Append Only、记录数据完整变化明细的表,用于实时增量计算。无主键表时,按照 CommitId+Offset 有序;有主键表时,按照 CommitId+Offset 有序,同一个 Key 可能会存在多条数据;
快照视图对应的是一张给予时间动态变化的快照表,用于离线批量计算。无主键表时,按照 CommitId+Offset 有序,与增量视图等价;有主键表时,分区内 Key 是唯一的,只保存最新的数据;
基于增量试图可以计算出快照视图。快照视图中数据已经基于主键做了合并,因此无法复现出增量视图。
/ 数据读写 /
我们首先会基于流批的特性针对流批读写做负载分离。其中流作业延时敏感,吞吐稳定,通过 BTS 加速;批作业用于批量计算,注重吞吐,延迟不敏感,直接与底层文件存储交互。
在流批负载分离的前提下,会做数据准确性保障。流批并发,写入时保障数据一致性;批数据写入时互不阻塞,同时保障流作业的低延迟和批作业的成功率。
/ BTS 架构 /
BTS 架构主要分为 BTS Master 与 BTS Table Server 两部分:
BTS Master 由三部分组成。 Block Load Balancer 为 Client 分配 Block,负责 Block 级别的负载均衡;Block Metadata Manager 负责管理 Block 与 TableServer 的关系元信息;Transation Manager 负责创建和提交分布式事务。
BTS Table Server 由五部分组成。 Session Manager 负责维护客户端的会话和配置信息,比如读写的 Offset 信息;DataService 提供数据读写 RPC 接口,提供列裁剪、谓词下推查询接口;Transaction Manager 提PreCommit 信息,如插入行数、Block 节点信息、Start-End Offset 信息等;MemStore 内含多表共用的内存区,管理内存分配和清理,管理Block生命周期。具备提供内存中快速查找、列裁剪、过滤、排序等能力;WAL 能够实现内存数据持久化,用于异常恢复。此外,在写缓存遭驱逐时,可用于数据读取。
湖仓一体存储在不同场景下应用时展现出了不同的亮点,下面我们介绍三个经典场景:流式数据计算、实时多维分析、流批数据复用,以及在这些应用案例中可达成的收益。
/ 流式数据计算 /
针对实时数仓的流式数据计算场景,实时数仓链路中的数据都在 Kafka 这种 MQ 组件中,中间不会落地,而且在维表关联场景中还会引入其他的存储选型(比如 MySQL 或者高性能的 KV 存储)。 这种架构带来的痛点主要有三点:
● 首先,整体链路依赖组件环境复杂、运维成本高;
● 其次,中间数据不落地带来的开发调试和数据测试困难的问题,需要额外起一个 dump 任务将数据落到 hive 之后才能做数据验证,周期比较长;
● 最后,原始数据在 MQ 中,无法高效实现数据回溯。
我们将链路中的依赖组件使用 Hudi 的湖仓一体表做改造之后,可以得到明显收益:环境依赖变轻,组件依赖少,链路简单;表既支持 Flink 流式消费、又支持批式读取,简化了调试验证工作,单需求提效明显;长期未来实现批流计算统一之后,实时离线状态可复用,也就低成本实现了历史数据回溯计算。
/ 实时多维分析 /
针对实时数仓的实时多维分析场景,运营可以基于已有的数据表动态组合维度去做分析,由于 MQ 中的数据不可查、会额外冗余一份数据到 ClickHouse 中,且为了节省资源,会对 ClickHouse 表数据设置 TTL 只保存近期数据,通过 OLAP 组件的方式对外提供查询能力。
使用 Hudi 的湖仓一体表做改造之后,首先不再需要 ClickHouse 组件,且 Hudi 表的存储成本非常低,可以全量存储,最终通过 Presto 引擎对外提供查询能力。由此得到两方面收益:节省了 ClickHouse 这种昂贵的 OLAP 资源;能够支持数据全量查询。
/ 流批数据复用 /
针对流批数据复用场景,实时数仓和离线数仓在原始数据层其实是依赖相同数据源的,以埋点数据为例,实时数仓和离线数仓都会基于客户端全量埋点数据,做依赖埋点、过滤产出 DWD 层,然后再基于埋点 DWD 数据做指标加工,埋点 DWD 层数据的建设需要两份计算和存储资源投入,且离线任务的计算集中在凌晨,资源被大量任务抢占时很难对任务按时拉起及保障数据产出时效性。
通过将实时数仓中埋点 DWD 层数据的存储方式改成 Hudi 湖仓一体表,将表提供给离线数仓使用,此时收益体现在离线数仓的埋点 DWD 层数据不再需要额外投入计算和存储资源,此外,还能提升数据就绪时间。
对于未来规划主要分引擎性能、稳定性和业务功能诉求三方面。
首先,在引擎性能方面,计划实现能够支持多任务并发写入。通过多路 WAL 合并、异步 Flush、内存管理优化等手段不断提升写入/消费吞吐性能;稳定性方面,需要能够更好的感知服务节点,处理客户端读写请求的压力,提升服务负载 balance 的能力。实现服务支持多机房部署,支持数据多机房备份做容灾恢复;最后针对业务功能诉求,计划实现支持对标 Kafka partition 概念及 group consume 的能力。
以上就是字节跳动内部自研的湖仓一体方案及其实践情况,目前均已通过火山引擎 湖仓一体分析服务 LAS 产品对外服务,欢迎对这方面有需求、感兴趣的用户都可以积极地来体验一下我们的 LAS 湖仓一体分析服务 。
产品介绍
火山引擎湖仓一体分析服务 LAS
湖仓一体分析服务 LAS(Lakehouse Analytics Service)是面向湖仓一体架构的 Serverless 数据处理分析服务,提供字节跳动最佳实践的一站式 EB 级海量数据存储计算和交互分析能力,兼容 Spark、Presto 生态,帮助企业轻松构建智能实时湖仓。 新人优惠来袭! 赠送给所有新人用户的专属福利来啦,LAS 数据中台新人特惠 1 元秒杀活动最新上线! 更有超多叠加优惠等你来抢! 感谢大家一直以来对我们的支持与厚爱,我们会一如既往地为您带来更好的内容。 (点击文末“阅读原文”,可顺滑体验)
--推荐阅读--