火山引擎ByteHouse基于云原生架构的实时导入探索与实践

原创
2023/12/21 14:31
阅读数 51

更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群

随着企业降本增效、智能化数据决策需求的增强,传统的商业数据库已经难以满足和响应快速增长的业务诉求。在此背景下,云原生数据库成为大势所趋。云原生数据库基于云平台构建、部署和分发,具有高可用性、高性能、高可靠等特点,可以帮助企业更好地实现数据智能化决策。

近期,火山引擎ByteHouse技术专家受邀参加DataFunCon2023(深圳站)活动,并以“火山引擎ByteHouse基于云原生架构的实时导入探索与实践”为题进行了技术分享。在分享中,火山引擎ByteHouse技术专家以Kafka和物化MySQL两种实时导入技术为例,介绍了ByteHouse的整体架构演进以及基于不同架构的实时导入技术实现。

架构整体的演进过程

分布式架构概述

ByteHouse是基于社区ClickHouse数据分析管理系统(下文简称社区)来做的产品集成和开发。ClickHouse在开源以后,因为其实时分析方面极致的性能表现在业界被追捧。目前其开源社区的star活跃度非常高,国内很多公司都有针对ClickHouse开源社区做的产品集成和上云服务。

由于ClickHouse是基于OLAP实时分析而生的列存的数据库,其本身是一个分布式数据库,加之其底层设计和实现让它在性能方面非常优秀,具体表现为单机可以达到每秒上亿行的读取速度以及GiB级的数据吞吐。由于社区官方不会做云服务的限制,所以社区开源的只是分布式架构。

社区的开源实现是一个经典的分布式架构。首先它是无中心的多节点集群,有分片(shard)的概念:每个集群有多个shard,每个shard相互独立;集群内每张表的数据划分为不同子集存储在不同shard上。由于分布式架构具有数据分片和本地存储的特性,所以它具有天然的并发性且高吞吐的优势。

当然,分布式架构也有其明显缺陷。首先,当集群达到一定规模后,再小的节点故障率也会导致一定量的故障处理单,而本地存储的运维门槛加剧了故障处理成本,尤其对于单副本集群,节点故障甚至会导致丢数据的风险;其次,分布式架构的读写耦合导致查询和导入存在资源竞争的问题;另外,由于本地存储reshuffle功能的成本问题,分布式架构的扩容成本非常高,而且容易导致线上服务IO热点,进而影响整个集群的稳定性。最后,由于无中心化节点以及事务的缺失,一致性问题是目前社区最为人吐槽的缺陷。

picture.image

分布式架构下的实时导入

我们再来了解一下社区分布式架构下的实时导入实现,这里以Kafka导入为例。由于分布式架构多shard,每个shard可以独立消费一部分topic partition,可以有天然的并发优势;每个shard内部可以再通过多线程并发执行消费任务,进一步提高消费并发;加上本地写入的优势,使得导入任务可以有很高的吞吐。

社区Kafka消费实现采用high level的消费模式。high level 消费任务完全由broker分配和rebalance,基本无法对数据分配做控制,也就无法满足对数据分配有需求的业务场景;同时也难以保证数据均衡。针对这个问题,ByteHouse在开始引入ClickHouse时就做了优化——实现了low-level消费模式,使得数据分布以及均匀性能够得到保障。

由于架构缺陷,分布式架构下的Kafka导入存在类似痛点。首先由于没有事务保证,无法保证一致性,消费只能做到At-Least-Once 或者 At-Most-Once;其次,查询高峰会导致读写资源的竞争,从而造成消费堆积;当存在扩容需求的时候,数据分布会存在一些冲突。最后,由于中心节点缺失导致需要去每个独立节点排查问题,运维成本随着集群规模线性增长。

picture.image

从分布式到云原生

基于以上痛点,ByteHouse进行了业界主流的架构升级和演进——从分布式架构到云原生架构的改造。火山引擎ByteHouse云原生架构分为三层:

  • 第一层是服务接入层,负责服务接入以及状态管理,包括整体服务入口、所有元数据信息、事务实现等。
  • 第二层是执行计算层(Virtual WareHouse,以下简称VW),设计为无状态执行层可以轻量级扩缩容;负责执行具体的查询和导入任务,由于查询和导入可以下发到不同Virtual WareHouse 从而实现读写分离。
  • 第三层是数据存储层(VFS),支持远端HDFS存储以及对象存储等多种存储方式,实现了存算分离。

状态管理层有一个元数据管理组件叫做Catalog service,这里存储了包括表的schema以及用户数据的所有元数据信息;另一个重要组件是Server,它的功能是承接整个集群的服务入口,用户的查询需求都会在Server进行预处理;在查询具体计算执行阶段,由于VFS数据存储导致的读数据开销,ByteHouse在计算层实现了DISK cache功能——将频繁查询的数据缓存到计算节点的local disk,以避免频繁远端数据读取的性能损耗。

为了解决社区饱受吐槽的一致性问题,ByteHouse设计和实现了Transaction,几乎所有任务(包括所有用户查询请求、DDL请求、导入请求、后台实时导入任务等)的执行都会封装在一个事务中执行,保证一致性和原子性。

由于架构的升级和演进,实时导入技术也在新架构下做了适配和优化。下面仍旧以Kafka导入为例,看看ByteHouse云原生新架构下的实时导入的实现。

当用户创建一张Kafka表消费时,集群会在Server上为这张表创建一个唯一的任务管理器:管理器负责获取Kafka topic的元信息,并根据用户配置的consumer数据将topic-partition均匀分配给每个consumer任务;然后将每个consumer任务调度到合适的VW节点执行。

在VW节点,每个consumer会从Kafka拉取自身分配到的topic-partition对应的数据,并将这些数据写入到VFS;每次写入数据后,系统会通过事务将写入数据的元信息以及最新Kafka offset提交到Catalog中;然后重复执行下一轮。

这个过程确保了数据从Kafka到ByteHouse引擎的完整导入,包括元信息的获取、后台任务的调度、数据的拉取与写入,以及offset的管理。通过这种方式,系统能够持续不断地从Kafka拉取数据并导入到ByteHouse中,形成一个不断的导入的实时数据流,满足用户的实时写入需求。

下面的表格简单比较了不同架构下实时导入技术的功能支持。除了上述提到的优化和改进,ByteHouse还自研了唯一键引擎,并从bytehouse的分布式架构开始支持,完全适配到云原生架构上,配合Kafka low-level消费模式,可以完美解决用户实时导入唯一键场景需求。同时,ByteHouse云原生架构通过独立的事务实现,在实时导入上消费语义升级支持Exactly-Once,满足了部分用户对数据准确性的要求。这些改进使得团队能够更好地满足用户的需求,提供更加稳定和高效的服务。

picture.image

基于云原生架构上Kafka和MySQL的设计和实现

下面以Kafka和物化MySQL为例,简单分享一下ByteHouse云原生新架构下的实时导入技术的具体实现细节。

Kafka

如上文所述,在Kafka导入中,每个表在Server端都有一个对应的任务管理器Manager,这是一个后台常驻线程,负责从Kafka broker中获取topic的元信息,并从Catalog中获取上次成功提交的offset;然后,根据任务映射规则,将partition分配给不同的consumer,并将最新的消费offset一同下发到VW节点进行执行。

每个下发的任务都是作为一个常驻线程处理的。一旦任务被调度,如果没有异常,它会不断循环执行:先消费一批数据,然后写入ByteHouse;然后再消费下一批,直到上游停止操作或节点宕机。

picture.image

此外,为了优化消费,我们引入了一个名为memory buffer的功能。这个功能是为了解决某些业务场景下对延时性要求较高的需求。比如用户需要在1-2秒内查询到数据,但ClickHouse的攒批消费设计实现方式很难做到如此低的数据延时——频繁地小批量写入会导致底层数据文件增长以及查询性能降低。因此,我们引入了memory buffer功能,将消费的数据先写入wal中(持久化存储),然后缓存在内存提供查询,当缓存足够量再一次性刷盘。这样既能提高查询性能,又能解决底层存储问题。

picture.image

在VW上,每个消费任务的执行流程如下:首先,向Server端发起RPC请求,创建一个事务来进行消费;然后,根据分配的任务,从topic中poll数据;当消费足够量的数据,对数据进行处理和转换,写入VFS;最后将写入VFS的数据元信息和对应的消费offset通过事务提交回Server端,完成一次消费流程。

MySQL:

物化MySQL是社区目前的一个实验性功能,它的作用是把用户的一个MySQL的数据库库同步到ClickHouse里来帮助用户做一些OLAP分析。

物化MySQL的同步原理比较简单,当创建同步任务的时候,初始化阶段会把这个库里需要同步的表的数据全量拉取;当然,这里会有一个加锁和快照的操作,用以记录全量同步的位置,后续增量数据同步会从这个位置开始,通过实时同步MySQL的binlog并进行回放来实现。

对于底层存储,因为MySQL的数据表都有唯一性的需求,所以应用上ByteHouse自研的唯一键引擎就可以完美匹配。BinLog消费跟上文提到Kafka消费原理基本一致。MySQL有一个GTID的功能,可以充当类似于Kafka的offset角色,配合ByteHouse云原生架构的事务功能,每次在回放完以后同步提交数据元信息以及对应的GTID,保证做到不丢不重的消费导入。

目前ByteHouse支持的MySQL数据源包括官方的MySQL版本以及AWS和GCP这两个使用比较多的rds,因为目前ByteHouse物化MySQL更多地服务一些海外的业务,在AWS以及GCP上会更普遍一下。具体支持的功能列表可以参见下图。

云原生架构和导入技术使用现状和未来规划

目前,火山引擎ByteHouse云原生架构已经全面服务内、外部多种业务场景,实时导入已支持超过2500个服务节点,每天实时导入数据规模超过30PB,行数超过10万亿,每天的平均吞吐量是350GB每秒,算到每个消费线程大约18MB每秒。未来,火山引擎ByteHouse团队还将持续探索更通用的实时导入技术解决方案,进一步提升数据导入的性能和通用性,并持续推进开源社区建设。

点击跳转ByteHouse了解更多

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部