腾讯云 CDW-ClickHouse 云原生实践
1. 前言
开源列式数据库 ClickHouse 以极致的性能、超高的性价比获得了广泛好评。在 PB 级查询分析场景下 ClickHouse 是最佳解决方案之一。开源 ClickHouse 集群采用 SHARED-NOTHING 架构,增加计算节点非常容易。
图 1:开源 ClickHouse 架构
但是,开源 ClickHouse 也有明显的不足之处:
采用存算一体架构,计算与存储耦合。存储与计算资源无法独立扩展。用户对计算与存储资源非对称需求越发强烈,并且希望云服务商能够提供更为灵活的资源编排能力。
不具备弹性能力。开源 ClickHouse 集群没有数据均衡功能(rebalance)。在云托管 ClickHouse 阶段,通过业务层来均衡数据,代价大,耗时长。弹性效率十分低。
运维成本高。开源 ClickHouse 运维成本高。例如,当集群扩容后,增量计算节点无法自动同步存量节点的 SCHEMA 信息,需要人工介入。
随着云原生理念深入人心,利用云原生架构对开源 ClickHouse 进行改造,计算资源池化,存储与计算分离,势在必行。业界对云原生 ClickHouse 并没有明确的定义。云原生 ClickHouse 至少需要具备以下特征:
采用存算分离架构,计算资源与存储资源独立扩展,按需付费;
高效弹性,计算资源扩容时数据 Zero-copy;
计算资源池化,根据业务需求灵活编排计算资源;
易运维,甚至免运维,只关注业务本身;
腾讯云数仓服务 CDW-ClickHouse 已从云托管演进为云原生服务,下文简称云原生 ClickHouse。本文叙述开源 ClickHouse 云原生改造过程中的难点、方案设计、关键技术、以及未来规划。
2. 云原生架构
为了解决开源 ClickHouse 的痛点,腾讯云 CDW-ClickHouse 采用了全新存算分离架构,将服务分为元数据服务层、计算层 和存储资源层。
图 2:云原生 ClickHouse 架构图
元数据服务层:元数据服务层包含集群管理节点 clickhouse-admin 以及 元数据持久化存储。元数据管理节点 clickhouse-admin 负责管理集群的元数据元数据包括集群的数据分布表、SCHEMA 信息等。同时,clickhouse-admin 还负责计算节点的保活功能。集群管理节点 clickhouse-admin 为无状态设计,可水平扩展。
计算层:云原生 ClickHouse 计算层由计算组构成,计算组为计算节点集合。在计算层计算节点可以分组隔离。部署 clickhouse-server 节点为无状态存在,数据存储剥离到分布式存储系统或对象存储之中。用户可以根据业务合理编排计算资源。
存储资源层:云原生 ClickHouse 对存储资源做了统一抽象,用户无需关注底层存储。架构层方便集成更多云上分布式存储服务。可以集成低成本无限容量的对象存储,也可以集成低延迟高吞吐的分布式文件系统。
目前,已经集成了对象存储 COS,以及分布式文件系统 CFS。
接下来章节分别对架构中各部分展开叙述。
3. 集群元数据管理
开源 ClickHouse 采用的是 SHARED-NOTHING 架构,节点之间并不同步一些关键的信息。用户向集群新增节点后,增量节点并不会自动从存量节点上继承 SCHEMA 信息,导致易用性极差。云原生 ClickHouse 引入 clickhouse-admin 角色,用于管理集群全局信息:
数据分布表:共享存储中数据与计算节点映射关系数据。
SCHEMA 信息:ClickHouse 集群中的 schema 对象。
配置信息:包括计算节点配置,共享存储配置,以及计算分组的配置等。
该角色为集群的管理节点,无状态设计,具体数据存储在持久化系统中。在具体部署中,clickhouse-admin 节点可以根据情况进行多节点部署,共同分担负载。
3.1 处理 DDL 请求
云原生 ClickHouse 引入了新的管理节点。为了简化部署,也为了方便集群元数据统一管理,clickhouse-admin 接管了 DDL 功能。云原生 ClickHouse 不再依赖 ZooKeeper 集群。
图 3:统一处理 DDL 请求示意图
如图 3 所示,在一个简单的部署环境中,DDL 请求执行流程。客户端发给 clickhouse-server 的请求,会转发到 clickhouse-admin。接着,clickhouse-admin 统一将请求分发到对应计算组的节点。当前请求完成后,结果再原路转发给客户端。
在 DDL 被接管后,集群 SCHEMA 信息会被统一管理。这是后续集群扩容,节点重启,确保 SCHEMA 一致的基础。
3.2 元数据分发
开源 ClickHouse 采用的 SHARED-NOTHING 架构。集群的节点之间并不同步元数据。当集群新增节点,或者节点重启后,节点无法获取到集群最新的 SCHEMA 信息。这也是 ClickHouse 用户的痛点之一。
云原生 ClickHouse 集群在节点启动时,会向 clickhouse-admin 注册服务。如果不属于该集群的节点,会注册失败。完成注册后,clickhouse-server 从 clickhouse-admin 获取最新的元数据,包括 schema 信息、数据分布表信息。随后,clickhouse-server 进入数据加载阶段。
计算节点 clickhouse-server 启动完成后,会与管理节点 clickhouse-admin 进行心跳同步。心跳信息中包含了元数据的版本信息。若版本变化,则同步更新最新元数据。从而在集群范围内,计算节点获取的元数据最终一致。
图 4:原数据分发:启动和心跳
元数据分发功能为弹性伸缩奠定基础。
3.3 失败节点检测
在云原生 ClickHouse 集群中,计算节点 clickhouse-server 与元数据管理节点 clickhouse-admin 之间保持心跳。clickhouse-admin 通过心跳信息踢出异常节点、以及感知新节点。
失败检测(Failure detector)通常采用心跳(Heartbeat)模型。在云原生 ClickHouse 的实现中,并没有单纯根据心跳超时来判断节点失效。由于网络延迟抖动,以及节点负载的变化,单纯根据超时来判断会有很大概率误判。参考 Cassandra 中失效检测模块的实现,算法细节见。
3.4 计算节点分组隔离
所谓计算组,就是一组计算节点的集合。也就是云原生 ClickHouse 集群支持部署多计算分组,满足业务按需资源编排。不同资源组可以共享相同数据,实现容灾以及读写分离功能。主要的应用场景:
容灾:同集群中,将不同计算分组部署在不同可用区,实现计算层容灾;
读写分离:可以规划 2 个计算分组,一个用户数据写入,一个用于数据的查询,避免数据写入与查询相互干扰。
按业务需求合理编排计算资源:对于测试,可以分配小规模低配置计算组,对应重要业务分配更高配置计算资源。
目前,还不支持多计算组。这是云原生 ClickHouse 后续重要功能之一。
4. 自研表引擎
云原生 ClickHouse 中集成了自研表引擎:CloudMergeTree 以及 CloudDistributed。前者基于分布式存储系统或对象存储系统实现数据读写、后台合并、以及数据自均衡。后者职责为 ClickHouse 分布式数据写入以及查询。
云原生 ClickHouse 自研表引擎在与开源社区代码级兼容的前提下,具备如下能力:
基于分布式存储系统/对象存储实现数据读写;
接口保持语义兼容,完整支持 ClickHouse-SQL;
以 Zero-Copy 方式实现数据重分布;
弹性扩缩时有限影响服务;
虽是自研表引擎,在工程实践过程中,尽可能重用现有代码。关键逻辑都在 CloudMergeTree/CloudDistributed 中实现。之所以这样做,一个核心因素是为了保持云原生 ClickHouse 与开源 ClickHouse 能够同步升级。云原生的代码相对对立,不会耦合在开源 ClickHouse 现有逻辑里,从而能够确保兼容与升级。
4.1 统一存储视图
云原生 ClickHouse 的自研表引擎提供了统一的抽象视图,并不绑定在具体的分布式存储系统或者对象存储。
图 5 统一存储视图模型
统一抽象存储层屏蔽了底层物理层次的细节。一方面,极大简化了自研表引擎的逻辑;另一方面,方便集成更多分布式存储系统或者对象存储。
统一抽象存储层将底层存储系统划分为固定数量的逻辑单位,简称为桶(Bucket)。具体桶的数量,在创建表是指定。抽象存储层的桶会均衡的分布在计算组内计算节点中。桶与计算节点之间的映射关系,称为数据分布表,由管理节点维护。划分桶的核心原因在于,简化弹性伸缩时数据重分布:
当扩容时,存量节点上的桶会重新分配给增量节点;
当缩容,或节点被踢出时,一部分桶会重新分配给存量节点;
显然,弹性伸缩时,只有数据桶重新分布,出发数据加载与卸载,而没有数据复制。极大地提升了弹性效率。
4.2 数据重定向
在统一抽象存储视图前提下,为了更好支持 ClickHouse 分布式 JOIN 计算,用户可以通过 CloudDistribyted 的SHARD BY expr
子句自定义表的数据分布。用户通过对参与 JOIN 的表作统一数据分布,可以实现 COLOCATE JOIN。
举例:
基于表 test 创建分布式表。
与开源 ClickHouse 不同之处在于,创建分布式表时,可以指定数据分布的规则,即SHARD BY expr
。如上例中,当数据写入时候,会评估每一行数据在xxHash32(i) % 99
的值,该值与桶的总数量取模运算,获取桶 ID,数据将被分发到对应的桶中。
4.3 数据写入
云原生 ClickHouse 与开源 ClickHouse 写入流程类似,基于统一抽象存储层实现。
在开源 ClickHouse 中,同一个表在每一个节点上独立分配递增的 BLOCK ID(可以理解为逻辑时间戳,用于标记数据写入的时序关系)。很明显,拥有较大的 BLOCK ID 的数据要后于拥有较小 BLOCK ID 的数据。
在云原生 ClickHouse 中,同一个表为每一个桶中维护了递增的 BLOCK ID。这个 BLOCK ID 维护在管理节点中。正因为如此,一些后台任务(MERGE/MUTATE),是以桶为单位进行的。
为了避免弹性伸缩期间影响数据写入,每个节点写入数据时,需要满足如下要求:
向数据桶提交数据全局有序;
任意计算节点提交数据前,需要加载其他节点已提交数据(若有);
持有数据桶的计算节点,需要周期性加载其他节点已提交的数据(若有);
为此,实现了数据多节点并发提交机制。由于多个节点并发向桶中提交数据(data part),每个节点看到的数据不一定是最新的。在具体实现中,节点会周期性检查是否有新的数据需要加载。从而确保任意节点写入数据的最终一致性。更重要的时,需要确保不同节点看到数据提交的顺序是一致的。
图 6 多节点并发写入以及冲突解决示意图
如图所示,节点 clickhouse-server-2 第一次提交数据时,它本地感知到的 ID 是 98,它用 ID 99 来向 clickhouse-admin 提交数据。clickhouse-admin 看到当前 ID 为 100(其中 ID99-100 的数据为 clickhouse-server-1 提交),因此拒绝了该节点的数据提交。clickhouse-server-2 启动加载流程,加载 ID99-100 的数据后,以 ID101 重新提交数据,这次由于没有其他节点成功提交,故提交成功。
计算节点提交数据时候,会携带本地维护的 ID。clickhouse-admin 收到提交请求后,若请求携带的 ID 落后于全局 ID,则拒绝提交。如果提交的 ID 与全局 ID 吻合,则接受提交。当提交拒绝时,说明其他节点已经提交了数据,需要加载这部分数据后,再用新的 BLOCK ID 来提交数据,直到提交成功。
当弹性伸缩时,数据分布表在不同节点上可能不一致。存在多个节点同时写入同一个桶的情况。多节点并发机制确保了弹性伸缩阶段写入数据的正确性。也就是说,弹性伸缩期并不影响集群数据写入。
并发写入机制是后续跨可用区容灾,计算组节点容灾的基础。
4.4 数据查询
云原生 ClickHouse 与开源 ClickHouse 查询流程类似,基于统一抽象存储层实现。
在弹性伸缩过程中,数据桶发生迁移,即从一个节点调整到另外一个节点。在这个过程中,集群中多个计算节点看到的数据桶分布表不一致。在不一致期间,分布式查询的结果一定是错误的。
为了避免将错误的结果返回给客户端,云原生 ClickHouse 执行分布式查询时,会检查数据分布表,若分布表不同,则抛出异常。客户端收到异常后,需要重试。
图 7 桶迁移示意图
如图所示,在数据加载阶段,也就是 T1 时间内,查询服务仍然走原有节点。而在数据桶生效或者释放阶段,也就是 T2 时间内,数据桶分布表不一致,在该时间段内拒绝查询。
由于数据桶生效或者释放是非常短的时间,数据均衡对数据查询服务影响时间控制在较小时间段内。
5. 高效弹性
云原生 ClickHouse 实现了存储与计算资源分离。在弹性能力方面:
存储资源层:无论是分布式存储系统或对象存储,都天然具备弹性能力;
计算资源层:计算节点分组隔离,可以动态增加或减少计算分组。计算分组内可以动态增加和减少计算节点。
云原生 ClickHouse 对存储资源弹性扩展时,不会附带任何计算资源成本。在计算资源弹性时,只存在数据桶的所属关系变迁,无数据复制,弹性效率极大提升。
6. 未来工作
目前,云原生 ClickHouse 已经具备做到完整的弹性伸缩能力。用户可以按需购买计算资源与存储资源。在运维方面,云原生 ClickHouse 依赖云上运维管控系统,为用户提供开箱即用的服务。
云原生 ClickHouse 与开源 ClickHouse 有明显区别:
未来工作包括:
增强对象存储查询效率,包括冷热数据管理、缓存管理等。
支持计算节点容灾,包括支持多计算组实现跨可用区容灾,也支持计算组内计算节点容灾。
自研存储引擎支持 UPSERT 能力。
敬请期待。
版权声明: 本文为 InfoQ 作者【腾讯云大数据】的原创文章。
原文链接:【http://xie.infoq.cn/article/fe237a4180cfbb531bc556cb9】。文章转载请联系作者。
评论