

火山引擎ByteHouse基于云原生架构的实时导入探索与实践
source link: https://blog.51cto.com/bytedata/8923875
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

火山引擎ByteHouse基于云原生架构的实时导入探索与实践
精选 原创更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群
随着企业降本增效、智能化数据决策需求的增强,传统的商业数据库已经难以满足和响应快速增长的业务诉求。在此背景下,云原生数据库成为大势所趋。云原生数据库基于云平台构建、部署和分发,具有高可用性、高性能、高可靠等特点,可以帮助企业更好地实现数据智能化决策。
近期,火山引擎ByteHouse技术专家受邀参加DataFunCon2023(深圳站)活动,并以“火山引擎ByteHouse基于云原生架构的实时导入探索与实践”为题进行了技术分享。在分享中,火山引擎ByteHouse技术专家以Kafka和物化MySQL两种实时导入技术为例,介绍了ByteHouse的整体架构演进以及基于不同架构的实时导入技术实现。
架构整体的演进过程
分布式架构概述
ByteHouse是基于社区ClickHouse数据分析管理系统(下文简称社区)来做的产品集成和开发。ClickHouse在开源以后,因为其实时分析方面极致的性能表现在业界被追捧。目前其开源社区的star活跃度非常高,国内很多公司都有针对ClickHouse开源社区做的产品集成和上云服务。
由于ClickHouse是基于OLAP实时分析而生的列存的数据库,其本身是一个分布式数据库,加之其底层设计和实现让它在性能方面非常优秀,具体表现为单机可以达到每秒上亿行的读取速度以及GiB级的数据吞吐。由于社区官方不会做云服务的限制,所以社区开源的只是分布式架构。
社区的开源实现是一个经典的分布式架构。首先它是无中心的多节点集群,有分片(shard)的概念:每个集群有多个shard,每个shard相互独立;集群内每张表的数据划分为不同子集存储在不同shard上。由于分布式架构具有数据分片和本地存储的特性,所以它具有天然的并发性且高吞吐的优势。
当然,分布式架构也有其明显缺陷。首先,当集群达到一定规模后,再小的节点故障率也会导致一定量的故障处理单,而本地存储的运维门槛加剧了故障处理成本,尤其对于单副本集群,节点故障甚至会导致丢数据的风险;其次,分布式架构的读写耦合导致查询和导入存在资源竞争的问题;另外,由于本地存储reshuffle功能的成本问题,分布式架构的扩容成本非常高,而且容易导致线上服务IO热点,进而影响整个集群的稳定性。最后,由于无中心化节点以及事务的缺失,一致性问题是目前社区最为人吐槽的缺陷。

分布式架构下的实时导入
我们再来了解一下社区分布式架构下的实时导入实现,这里以Kafka导入为例。由于分布式架构多shard,每个shard可以独立消费一部分topic partition,可以有天然的并发优势;每个shard内部可以再通过多线程并发执行消费任务,进一步提高消费并发;加上本地写入的优势,使得导入任务可以有很高的吞吐。
社区Kafka消费实现采用high level的消费模式。high level 消费任务完全由broker分配和rebalance,基本无法对数据分配做控制,也就无法满足对数据分配有需求的业务场景;同时也难以保证数据均衡。针对这个问题,ByteHouse在开始引入ClickHouse时就做了优化——实现了low-level消费模式,使得数据分布以及均匀性能够得到保障。
由于架构缺陷,分布式架构下的Kafka导入存在类似痛点。首先由于没有事务保证,无法保证一致性,消费只能做到At-Least-Once 或者 At-Most-Once;其次,查询高峰会导致读写资源的竞争,从而造成消费堆积;当存在扩容需求的时候,数据分布会存在一些冲突。最后,由于中心节点缺失导致需要去每个独立节点排查问题,运维成本随着集群规模线性增长。

从分布式到云原生
基于以上痛点,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,满足了部分用户对数据准确性的要求。这些改进使得团队能够更好地满足用户的需求,提供更加稳定和高效的服务。

基于云原生架构上Kafka和MySQL的设计和实现
下面以Kafka和物化MySQL为例,简单分享一下ByteHouse云原生新架构下的实时导入技术的具体实现细节。
Kafka
如上文所述,在Kafka导入中,每个表在Server端都有一个对应的任务管理器Manager,这是一个后台常驻线程,负责从Kafka broker中获取topic的元信息,并从Catalog中获取上次成功提交的offset;然后,根据任务映射规则,将partition分配给不同的consumer,并将最新的消费offset一同下发到VW节点进行执行。
每个下发的任务都是作为一个常驻线程处理的。一旦任务被调度,如果没有异常,它会不断循环执行:先消费一批数据,然后写入ByteHouse;然后再消费下一批,直到上游停止操作或节点宕机。

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

在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了解更多
- 赞
- 收藏
- 评论
- 分享
- 举报
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK