更多技能交流、求职时机,欢迎关注字节跳动数据平台微信大众号,回复【1】进入官方交流群
ByteHouse 是火山引擎上的一款云原生数据仓库,为用户带来极速剖析体验,能够支撑实时数据剖析和海量离线数据剖析;便捷的弹性扩缩容才能,极致的剖析功能和丰富的企业级特性,助力客户数字化转型。
本文将从需求动机、技能完结及实际运用等视点,介绍根据不同架构的 ByteHouse 实时导入技能演进。
内部事务的实时导入需求
ByteHouse 实时导入技能的演进动机,起初于字节跳动内部事务的需求。
在字节内部,ByteHouse 首要仍是以 Kafka 为实时导入的首要数据源(本文都以 Kafka 导入为例展开描绘,下文不再赘述)。关于大部分内部用户而言,其数据体量偏大;所以用户更看重数据导入的功能、服务的稳定性以及导入才能的可扩展性。而关于数据延时性,大多数用户只需是秒级可见就能满意其需求。根据这样的场景,ByteHouse 进行了定制性的优化。
分布式架构下的高可用
社区原生分布式架构
ByteHouse 首要沿用了 Clickhouse 社区的分布式架构,但分布式架构有一些天然性架构层面的缺陷,这些痛点首要表现在三个方面:
- 节点毛病:当集群机器数量抵达必定规模今后,基本每周都需求人工处理节点毛病。关于单副本集群在某些极端 case 下,节点毛病甚至会导致数据丢掉。
- 读写抵触:由于分布式架构的读写耦合,当集群负载到达必定程度今后,用户查询和实时导入就会出现资源抵触——尤其是 CPU 和 IO,导入就会受到影响,出现消费 lag。
- 扩容成本:由于分布式架构数据基本都是本地存储,在扩容今后,数据无法做 Reshuffle,新扩容的机器几乎没有数据,而旧的机器上磁盘或许现已快写满,造成集群负载不均的状况,导致扩容并不能起到有用的作用。
这些是分布式架构天然的痛点,可是由于其天然的并发特性,以及本地磁盘数据读写的极致功能优化,能够说有利有弊。
社区实时导入规划
- High-Level 消费方式:依托 Kafka 自身的 rebalance 机制做消费负载均衡。
- 两级并发
根据分布式架构的实时导入核心规划其实便是两级并发:
一个 CH 集群通常有多个 Shard,每个 Shard 都会并发做消费导入,这便是榜首级 Shard 间的多进程并发;
每个 Shard 内部还能够运用多个线程并发消费,然后到达很高的功能吞吐。
- 攒批写入
就单个线程来说,基本消费方式是攒批写入——消费必定的数据量,或许必定时刻之后,再一次性写入。攒批写入能够更好地完结功能优化,查询功能提高,并下降后台 Merge 线程的压力。
无法满意的需求
上述社区的规划与完结,仍是无法满意用户的一些高档需求:
- 首要部分高档用户对数据的分布有着比较严厉的要求,比方他们关于一些特定的数据有特定的 Key,期望相同 key 的数据落盘到同一个 Shard(比方唯一键需求)。这种状况下,社区 High Level 的消费方式是无法满意的。
- 其次是 High level 的消费方式 rebalance 不可控,或许最终会导致 Clickhouse 集群中导入的数据在各个 Shard 之间分配不均。
- 当然,消费使命的分配不可知,在一些消费反常情景下,想要排查问题也变得非常困难;关于一个企业级运用,这是难以承受的。
自研分布式架构消费引擎 HaKafka
为了解决上述需求,ByteHouse 团队根据分布式架构自研了一种消费引擎——HaKafka。
高可用(Ha)
HaKafka 承继了社区原有 Kafka 表引擎的消费长处,再重点做了高可用的 Ha 优化。
就分布式架构来谈,其实每个 Shard 内或许都会有多个副本,在每个副本上都能够做 HaKafka 表的创立。可是 ByteHouse 只会经过 ZK 选一个 Leader,让 Leader 来真实地履行消费流程,其他节点坐落 Stand by 状况。当 Leader 节点不可用了,ZK 能够在秒级将 Leader 切到 Stand by 节点持续消费,然后完结一种高可用。
Low—Level 消费方式
HaKafka 的消费方式从 High Level 调整到了 Low Level 方式。Low Level 方式能够确保 Topic Partition 有序和均匀地分配到集群内各个 shard;与此一起,Shard 内部能够再一次用多线程,让每个线程来消费不同 Partition。然后完全承继了社区 Kafka 表引擎两级并发的长处。
在 Low-Level 消费方式下,上游用户只需在写入 Topic 的时分,确保没有数据倾斜,那么经过 HaKafka 导入到 Clickhouse 里的数据肯定也是均匀分布在各个 shard 的。
一起,关于有特殊数据分布需求——将相同 Key 的数据写到相同 Shard——的高档用户,只需在上游确保相同 Key 的数据写入相同 Partition,那么导入 ByteHouse 也就能完全满意用户需求,很好地支撑唯一键等场景。
场景一:
根据上图可见,假设有一个双副本的 Shard,每个副本都会有一张相同的 HaKafka 表处于 Ready 的状况。可是只要经过 ZK 选主成功的 leader 节点上,HaKafka 才会履行对应的消费流程。当这个 leader 节点宕机今后, 副本 Replica 2 会主动再被选为一个新的 Leader,持续消费,然后确保高可用。
场景二:
在节点毛病场景下,一般需求履行替换节点流程。关于分布式节点替换有一个很深重的操作——复制数据。
如果是一个多副本的集群,一个副本毛病,另一个副本是无缺的。咱们很天然期望在节点替换阶段,Kafka 消费放在无缺的副本 Replica 2 上,由于其上旧数据是齐备的。这样 Replica 2 就始终是一个齐备的数据集,能够正常对外供给服务。这一点 HaKafka 是能够确保的。HaKafka 选主的时分,如果确认有某一个节点在替换节点流程当中,会避免将其选为 Leader。
导入功能优化:Memory Table
HaKafka 还做到了 Memory Table 的优化。
考虑这样一个场景:事务有一个大宽表,或许有上百列的字段 或许上千的 Map-Key。由于 ClickHouse 每一个列都会对应落盘为一个详细的文件,列越多,每次导入写的文件也就越多。那么,相同消费时刻内,就会频频地写许多的碎文件,关于机器的 IO 是很沉重的担负,一起给 MERGE 带来很大压力;严峻时甚至导致集群不可用。为了解决这种场景,咱们规划了 Memory Table 完结导入功能优化。
Memory Table 的做法便是每一次导入数据不直接刷盘,而是存在内存中;当数据到达必定量今后,再集中刷盘,削减 IO 操作。Memory Table 能够供给对外查询服务的,查询会路由到消费节点地点的副本去读 memory table 里边的数据,这样确保了不影响数据导入的延时性。从内部运用经历来看,Memory Table 不只很好地解决了部分大宽表事务导入需求,而且导入功能最高能够提高 3 倍左右。
云原生新架构
鉴于上文描绘的分布式架构的天然缺陷,ByteHouse 团队一向致力于对架构进行升级。咱们选择了事务干流的云原生架构,新的架构在 2021 年初开端服务字节内部事务,并于 2023 年初进行了代码开源(ByConity)。
云原生架构自身有着很天然的主动容错才能以及轻量级的扩缩容才能。一起,由于它的数据是云存储的,既完结了存储核算别离,数据的安全性和稳定性也得到了提高。当然,云原生架构也不是没有缺陷,将原来的本地读写改为远端读写,必然会带来必定的读写功能损耗。可是,以必定的功能损耗来交换架构的合理性,下降运维成本,其实是利大于弊的。
上图是 ByteHouse 云原生架构的架构图,本文针对实时导入这块介绍几个重要的相关组件。
- Cloud Service
首要,总架构分为三层,榜首层是 Cloud Service,首要包含 Server 和 Catlog 两个组件。 这一层是服务进口,用户的一切恳求包含查询导入都从 Server 进入。 Server 只对恳求做预处理,不详细履行;在 Catlog 查询元信息后,把预处理的恳求和元信息下发到 Virtual Warehouse 履行。
- Virtual Warehouse
Virtual Warehouse 是履行层。不同的事务,能够有独立的 Virtual Warehouse,然后做到资源隔离。现在 Virtual Warehouse 首要分为两类,一类是 Default,一类是 Write,Default 首要做查询,Write 做导入,完结读写别离。
- VFS
最底层是 VFS(数据存储),支撑 HDFS、S3、aws 等云存储组件。
根据云原生架构的实时导入规划
在云原生架构下,Server 端不做详细的导入履行,只做使命办理。因此在 Server 端,每个消费表会有一个 Manager,用来办理一切的消费履行使命,并将其调度到 Virtual Warehouse 上履行。
由于承继了 HaKafka 的 Low Level 消费方式,Manager 会根据装备的消费使命数量,将 Topic Partition 均匀分配给各个使命;消费使命的数量是可装备的,上限是 Topic Partition 数目。
根据上图,大家能够看到左边是 Manager ,从 catalog 拿到对应的 Offset,然后根据指定的消费使命数目,来分配对应的消费 Partition、并调度到 Virtual Warehouse 的不同节点来履行。
新的消费履行流程
由于云原生新架构下是有事务 Transaction 确保的,一切操作都期望在一个事务内完结,也愈加的合理化。
依托云原生新架构下的 Transaction 完结,每个消费使命的消费流程首要包含以下过程:
-
消费开端前,Worker 端的使命会先经过 RPC 恳求,向 Server 端恳求创立一个事务;
-
履行 rdkafka::poll(),消费必定时刻(默许 8s)或许足够大的 block;
-
将 block 转化为 Part 并 Dump 到 VFS(此时数据不可见);
-
经过 RPC 恳求向 Server 发起事务 Commit 恳求
(事务中 Commit 的数据包含:dump 完结的 part 元数据以及对应 Kafka offset)
-
事务提交成功(数据可见)
容错确保
从上述消费流程里能够看到,云原生新架构下的消费,容错确保首要是根据 Manager 和 Task 的双向心跳以及快速失利策略:
- Manager 自身会有一个定时的探活,经过 RPC 查看调度的 Task 是否在正常履行;
- 一起每个 Task 会在消费中凭借事务 RPC 恳求来校验自己的有用性,一旦校验失利,它能够主动 kill;
- 而 Manager 一旦探活失利,则会立即拉起一个新的消费使命,完结秒级的容错确保。
消费才能
关于消费才能的话,上文提到它是一个可扩展性的,消费使命数量能够由用户来装备,最高能够到达 Topic 的 Partition 数目。如果 Virtual Warehouse 中节点负载高的话,也能够很轻量地扩节点。
当然,Manager 调度使命完结了基本的负载均衡确保——用 Resource Manager 来做使命的办理和调度。
语义增强:Exactly—Once
最终,云原生新架构下的消费语义也有一个增强——从分布书架构的 At-Least-Once 升级到 Exactly—Once。
由于分布式架构是没有事务的,只能做到一个 At-Least-Once,便是任何状况下,确保不丢数据,可是一些极端状况或许会有重复消费产生。到了云原生架构,得益于 Transaction 的完结,每一次消费都能够经过事务让 Part 和 Offset 完结原子性提交,然后到达 Exactly—Once 的语义增强。
Memory buffer
对应 HaKafka 的 memory table,云原生架构相同完结了导入内存缓存 Memory Buffer。
与 Memory Table 不同的是,Memory Buffer 不再绑定到 Kafka 的消费使命上,而是完结为存储表的一层缓存。这样 Memory Buffer 就更具有通用性,不只是 Kafka 导入能够运用,像 Flink 小批量导入的时分也能够运用。
一起,咱们引入了一个新的组件 WAL 。数据导入的时分先写 WAL,只需写成功了,就能够认为数据导入成功了——当服务发动后,能够先从 WAL 康复未刷盘的数据;之后再写 Memory buffer,写成功数据就可见了——由于 Memory Buffer 是能够由用户来查询的。Memory Buffer 的数据相同定时刷盘,刷盘后即可从 WAL 中清除。
事务运用及未来思考
最终简略介绍实时导入在字节内部的运用现状,以及下一代实时导入技能的或许优化方向。
ByteHouse 的实时导入技能是以 Kafka 为主,每天的数据吞吐是在 PB 级,导入的单个线程或许说单个消费者吞吐的经历值在 10-20MiB/s。(这里之所以强调是经历值,由于这个值不是一个固定值,也不是一个峰值;消费吞吐很大程度上取决于用户表的复杂程度,跟着表列数增加,导入功能或许会明显下降,无法运用一个准确的核算公式。因此,这里的经历值更多的是字节内部大部分表的导入功能经历值。)
除了 Kafka,字节内部其实还支撑一些其他数据源的实时导入,包含 RocketMQ、Pulsar、MySQL(MaterializedMySQL)、 Flink 直写等。
关于下一代实时导入技能的简略思考:
- 更通用的实时导入技能,能够让用户支撑更多的导入数据源。
- 数据可见延时和功能的一个折衷。
点击跳转 ByteHouse云原生数据仓库 了解更多