博客 / 用户案例

Bonree 使用 ClickHouse Keeper 替换 ZooKeeper,大幅提升性能并降低成本

author avatar
Huachen Li 和 Kangzhi Lv
2024年7月22日 - 14 分钟阅读

Bonree_01.png

“最初,我们没有预料到 ClickHouse 本身会如此强大,而 ClickHouse Keeper 的性能也同样令人印象深刻!

“从 ZooKeeper 迁移到 ClickHouse Keeper 后,CPU 和内存使用率降低了 75% 以上。”

“切换到 ClickHouse Keeper 后,性能提升了近 8 倍。”

背景

Bonree ONE 是由 Bonree Data Technology 提供的集成智能可观测平台。

在将所有 Bonree ONE 可观测数据迁移到 ClickHouse 集群后,随着数据量的增长,对 ZooKeeper 的性能和稳定性要求也随之提高。

ZooKeeper 是共享信息存储库和共识系统,用于协调和同步 ClickHouse 中的分布式操作。我们在使用 ZooKeeper 时遇到了一些痛点,如果不能及时解决,将会影响业务运营。

最终,我们决定用 ClickHouse Keeper 替换 ZooKeeper,以解决与写入性能、维护成本和集群管理相关的问题。ClickHouse Keeper 是 ZooKeeper 的快速、更高效且功能丰富的直接替代品,用 C++ 实现。

为了平稳迁移,我们必须找到以下方法:

  • 自动化迁移
  • 身份验证
  • 数据迁移
  • 数据验证

本文详细介绍了这些方法。

ZooKeeper 成为瓶颈

随着数据量和数据类型的不断扩展,ClickHouse 集群给 ZooKeeper 带来的压力也随之增加。例如,在需要高实时性能的警报数据存储和查询场景中,使用 ZooKeeper 时遇到了以下问题:

  1. 容量有限:在我们的云部署中,Keeper 配置为 4C8G(每个节点 4 个 CPU 核心和 8 GB 内存)资源,单个 ZooKeeper 集群(3 个节点)最多可支持 5 个分片(每个分片节点 16 个 CPU 核心和 32 GB 内存,每个分片 2 个副本),每个分片大约 20,000 个 parts,总共约 100,000 个 parts。随着数据量和表数量的增加,集群规模迅速扩大。因此,每次集群扩展 5 个分片时,我们都必须添加另一个 ZooKeeper 集群,导致了巨大的维护成本。

  2. 性能瓶颈:最初,ClickHouse 存储了 1TB 的数据,总共少于 12,000 个 parts,数据可以在毫秒内成功插入。但是,随着数据量的增加,parts 数量达到 20,000 个,数据插入的响应时间明显变慢,需要几十秒才能成功插入。这导致数据无法及时查询的问题,影响用户体验。

  3. 资源使用率高:ZooKeeper 消耗大量内存和 I/O 资源,并且成本随着数据量的增长而增加。

在相同的可观测数据环境中的对比测试中,ZooKeeper 的内存消耗是 ClickHouse Keeper 的 4.5 倍,I/O 使用率是 ClickHouse Keeper 的 8 倍。

  1. 稳定性问题:ZooKeeper 使用 Java 开发,可能会频繁经历 Full GC 周期,导致 ClickHouse 服务中断和性能波动。此外,还发生了 zxid overflow 等问题。当 ClickHouse 在后台执行副本之间的 parts 同步时,它必须首先向 ZooKeeper 注册。ZooKeeper 性能不佳可能会导致后台线程增加,从而导致副本之间的数据同步延迟。在我们的云平台中,使用 ZooKeeper 进行元数据存储导致超过 10,000 个 parts 的延迟队列,严重影响了业务查询的准确性。

基于以上痛点以及写入密集型数据分析场景的特点,我们选择了性能更优的 ClickHouse Keeper 作为 ZooKeeper 的替代品。主要考虑因素包括:

  1. 兼容性:ClickHouse Keeper 兼容 ZooKeeper 客户端协议。任何标准的 ZooKeeper 客户端都可以与 ClickHouse Keeper 交互,支持使用 ClickHouse 客户端命令。这意味着连接到 ClickHouse Keeper 不需要进行服务器端修改。

  2. 历史数据迁移:我们可以使用 clickHouse-keeper-converter 工具转换 ZooKeeper 中的历史数据,并将转换后的快照文件导入到 ClickHouse Keeper 集群中。转换效率通过选择五分钟块处理数百万个路径来控制,从而减少了整体集群停止写入时间,并将对在线产品的影响降至最低。

  3. 服务稳定性:ClickHouse-Keeper 使用 C++ 编写,通过从根本上解决 ZooKeeper 引起的 Full GC 问题,并通过服务器参数调整来提高服务性能,从而增强了整体服务稳定性。

  4. 资源和性能:压缩元数据存储减少了资源使用。它还更有效地利用 CPU、内存和磁盘 I/O。通过更有效地利用资源,ClickHouse Keeper 的性能更优越。

解决方案演进

基于 ZooKeeper 的原始解决方案

随着我们的可观测平台业务的扩展,平台的数据分为关键数据和一般数据。关键数据的优先级性能和查询要求需要物理资源隔离。大型集群的逻辑资源隔离很麻烦,因为添加新的类别数据需要重新分配和调整资源。物理隔离不同的资源更有效,使运营部门可以只专注于为特定领域提供资源支持。

最初,我们使用多个通过 ZooKeeper 协调的 ClickHouse 集群来处理不同数据类别的复杂性和不同的 SLA。但是,随着 ClickHouse 的扩展,ZooKeeper 的压力也随之增加,导致延迟和任务累积。为了解决这个问题,我们使用了官方的 ClickHouse 多 ZooKeeper 架构:每组 5 个分片的 parts 元数据存储在每个组的专用 ZooKeeper 集群中(每个集群由 3 个节点组成);表元数据集中存储在另一个 ZooKeeper 集群中(由 3 个节点组成),用于所有组

Bonree-Migrating_to_Keeper.001.png

上图说明了业务 A 和业务 B 需要物理隔离,业务 A 使用 ClickHouse 集群 1(15 个分片,每个分片 2 个副本),业务 B 使用 ClickHouse 集群 2(5 个分片,每个分片 2 个副本)。查询根据集群名称映射到不同物理资源上的 ClickHouse 实例,从而确保资源隔离。ClickHouse 集群 1 中每组 5 个分片的 parts 元数据由专用 ZooKeeper 集群(ZK1ZK2ZK3)管理,而 ZK4 管理 ClickHouse 集群 2 的 5 个分片的 parts 元数据。ClickHouse 集群 1 和 ClickHouse 集群 2 的所有表元数据都存储在共享的 ZooKeeper (ZK5) 集群中,以确保稳定性。维护五个 ZooKeeper 集群(5 次 3 个节点 = 15 个节点)很麻烦,并且性能和稳定性随着时间的推移而下降。

基于 ClickHouse Keeper 的新解决方案

测试验证了使用 ClickHouse Keeper 提高了数据插入速度、ClickHouse 集群的稳定性和副本同步速度。由于 ClickHouse Keeper 的出色性能,我们不再需要多个 ZooKeeper 节点集,而是选择维护一个 ClickHouse Keeper 节点集来支持 15 个以上的分片,从而大大简化了原始解决方案。

Bonree-Migrating_to_Keeper.002.png

迁移过程

迁移前准备

ClickHouse 不断更新 ZooKeeper 中的数据。即使没有数据摄取,后台任务(例如,后台 parts 合并)仍然会更改 ZooKeeper 中的数据,因此很难确保迁移前后数据的一致性。我们彻底研究了导致 ZooKeeper 中数据修改的 ClickHouse 后台任务,并使用命令(例如 SYSTEM STOP MERGES)停止了这些任务。这确保了迁移前后 ZooKeeper 和 ClickHouse Keeper 之间的数据一致性,从而便于数据比较和验证。自动化迁移过程确保了更改在 30 分钟内完成,而不会严重影响业务。

自动化迁移

迁移过程涉及在多台机器上运行多个命令和评估结果,存在人为错误风险并增加了迁移时间。为了解决这个问题,我们开发了一个基于 Ansible 的自动化迁移工具,Ansible 是一种 IT 自动化工具,用于配置管理、软件部署和高级任务编排,例如无缝滚动更新。迁移步骤如下:

  1. 停止数据摄取。
  2. 停止 ClickHouse 的合并和其他后台任务。
  3. 记录比较指标。
  4. 重启每个 ZooKeeper 集群以获取最新的快照文件。
  5. 将快照文件从 ZooKeeper 复制并转换为 ClickHouse Keeper。
  6. 将快照文件加载到 ClickHouse Keeper 中,并抽样比较 ZooKeeper 和 ClickHouse-Keeper 节点内容。
  7. 将 ClickHouse 元数据存储从 ZooKeeper 切换到 ClickHouse Keeper。
  8. 将指标与步骤 3 进行比较。
  9. 启动 ClickHouse 的合并和后台任务以及数据摄取。

自动化迁移避免了操作失误,大大缩短了迁移时间,从手动 2-3 小时缩短到几分钟,最大限度地减少了迁移期间对业务查询的影响。核心自动化流程包括:

// Stop ClickHouse schema creation and table operations
stopClickHouseManagers()
// Stop ClickHouse data write operations
stopClickHouseConsumers()
// Stop ClickHouse merge and other background tasks
ClickHouseStopMerges()
// Obtain the latest snapshot information from ZooKeeper clusters
getNewZookeeperSnap()
// Convert ZooKeeper snapshots to ClickHouse-Keeper snapshots
createAndExecConvertShell(housekeeperClusterName)
// Start ClickHouse clusters
startClickHouses()
// Compare data before and after the migration
checkClickHouseSelectData()

挑战

上述迁移过程非常基础。但是,环境更加复杂,涉及多个集群、各种 ZooKeeper 节点集以及多 ZooKeeper 到单 ClickHouse Keeper 过渡(社区版仅支持一对一)、加密和身份验证问题以及数据验证效率挑战等问题。

将多个 ZooKeeper 集群迁移到单个 ClickHouse Keeper 集群

ClickHouse Keeper 在性能测试中明显优于 ZooKeeper,并且需要的资源少得多。因此,必须将多个 ZooKeeper 集群减少到一个集群,以避免资源浪费。官方 clickHouse-keeper-converter 工具仅支持 ZooKeeper 到 ClickHouse Keeper 的一对一转换。我们通过以下方式解决了这个问题:

  1. 在迁移前对一些 ZooKeeper 节点进行抽样,保存此信息以在迁移后进行比较,确保迁移前后数据的一致性。
  2. 修改 clickHouse-keeper-converter 源代码以支持将多个 ZooKeeper 节点集的快照合并到一个 ClickHouse Keeper 节点集中。例如:
// Deserialize all snapshot files in a loop
for (const auto &item : existing_snapshots) {
  deserializeKeeperStorageFromSnapshot(storage item.second log);
}
// Modify numChildren property acquisition method to avoid using incremental IDs
storage.container.updateValue(parent_path [path = itr.key] (KeeperStorage::Node &value) {
  value.addChild(getBaseName(path));
  value.stat.numChildren = static_cast<int32_t>(value.getChildren().size());
});
// File output stream when creating the conversion snapshot file, for quick reading by the subsequent diff tool
int flags = (O_APPEND | O_CREAT | O_WRONLY);
std::unique_ptr<WriteBufferFromFile> out = std::make_unique<WriteBufferFromFile>("pathLog", DBMS_DEFAULT_BUFFER_SIZE, flags);

加密身份验证

Bonree 有许多 B2B(企业对企业)私有客户,其中一些客户历史上要求对 ZooKeeper 内容进行加密身份验证,而另一些客户则没有,导致情况各异。必须考虑所有场景以确保平稳迁移。ZooKeeper 可以执行 ACL(访问控制列表)加密。以下是如何根据不同的策略处理已加密的 ZooKeeper 集群、部分加密的 ZooKeeper 集群和未加密的 ZooKeeper 集群的转换:

  • 完全加密或完全未加密: 如果一切都已加密,并且加密信息一致,这意味着 ZooKeeper 集群中的每个节点都使用相同的 ACL 策略,并且策略的内容相同,并且 ClickHouse Keeper 的 ACL 与 ZooKeeper 的 ACL 兼容,则我们可以保留原始加密信息并直接执行转换。ClickHouse 配置文件不需要修改。如果根本没有加密,即 ClickHouse 在使用 ZooKeeper 时未配置 ACL 信息,则也可以直接转换此场景,并且 ClickHouse 配置文件也不需要修改。

  • 部分加密或加密信息不一致: 在这种情况下,我们需要删除原始加密信息并修改 ClickHouse 配置文件,以确保加密方法一致。移除 ZooKeeper 加密的步骤如下:

    1. 添加超级管理员帐户。启动 ZooKeeper 集群时,在 JAVA 启动命令中添加以下内容:Dzookeeper.DigestAuthenticationProvider.superDigest=zookeeper:{XXXXXX}

    2. 重启 ZooKeeper 集群。

    3. 使用 ZooKeeper 客户端进入集群:zkCli.sh -server ${zookeeper_cluster_address, format as ip1:port,ip2:port}

    4. 使用超级管理员帐户登录:addauth digest zookeeper:#{XXXXXX}

    5. 执行命令以移除节点身份验证(目标路径下的节点越多,所需时间越长):setAcl -R ${zookeeper_znode_path} world:anyone:cdrwa

    6. 移除步骤 (1) 中添加的超级管理员帐户,并重启 ZooKeeper 集群。

    7. 迁移后,决定是否需要在 ClickHouse-Keeper 中再次启用加密。

验证过程

为了确保迁移成功,我们在验证过程中面临了三个重大挑战:

  1. 数据检索的局限性:使用单个命令检索 ZooKeeper 中存储的所有现有路径是不可能的。
  2. 集群整合:我们将多个 ZooKeeper 集群合并到一个 ClickHouse Keeper 集群中。
  3. 数据比较的准确性和速度:在自动化迁移过程中,快速准确地执行数据比较至关重要。否则可能会成倍增加迁移持续时间,从而增加误判和潜在的迁移失败的风险。

为了快速检索和比较路径,我们实施了以下策略:

  1. 在将 ZooKeeper 快照转换为 ClickHouse Keeper 快照时,我们将所有转换后的路径打印到 pathlog 目标文件中。在比较期间,我们对该文件的内容进行抽样,将 ZooKeeper 查询转换为文件读取。最初,读取 900 万个 ZooKeeper 路径大约需要 9 个小时,但经过优化后,现在只需几秒钟(例如,通过将 pathlog 加载到内存中进行读取)。

  2. 我们根据迁移关系按顺序比较 znode 路径。我们确保在比较之前关闭合并等任务,进一步确保不会出现临时目录,从而保证比较的有效性。比较分为两种情况:

    1. 差异化路径: 这些路径仅存在于 /clickhouse/tables 路径下,并且仅存在于一个 ZooKeeper 集群中。如果它们通过比较验证,则验证成功。相反,如果它们不存在于多个 ZooKeeper 集群中,或者存在于两个或多个集群中,则验证失败。这确保了差异化路径数据的正确性。
    2. 公共路径: 路径的内容在所有 ZooKeeper 集群中都相同,通过比较验证。这确保了公共路径数据的正确性。

调优

我们对 ClickHouse Keeper 应用了以下参数调优:

  • max_requests_batch_size: 这表示发送到 raft 之前批处理的最大请求大小。集群越大,此值应向上调整越多,以方便批处理和性能提升。在我们的场景中,我们将该值设置为 10,000。

  • force_sync: 表示请求是否同步写入日志。优化的设置为 false。

  • compress_logs: 表示是否压缩日志。日志文件大小会影响启动速度和磁盘使用量。优化的设置为 true。

  • compress_snapshots_with_zstd_format: 表示是否压缩快照。日志文件大小会影响启动速度和磁盘使用量。优化的设置为 true。

结果

用 ClickHouse Keeper 替换 ZooKeeper 后,资源节省和写入延迟的改进非常显著,解决了 ZooKeeper 中存储元数据的性能瓶颈,同时也降低了维护门槛。

以前,ZooKeeper 中的 IO 瓶颈会直接影响 ClickHouse 存储的整体写入延迟。在我们的云平台上,随着数万亿事件的摄取,从 ZooKeeper 迁移到 ClickHouse Keeper 后,CPU 和内存使用率节省了 75% 以上。

切换到 ClickHouse Keeper 后,IO 开销降低了 8 倍,性能提升了近 8 倍。

最初,我们没有预料到 ClickHouse 本身会如此强大,而 ClickHouse Keeper 的性能也同样令人印象深刻!

语言资源CPU 使用率内存使用率I/O 使用率摄取持续时间失败率
ZooKeeperJava12 节点36 核81.6 GB4%P99=15 秒
ClickHouse KeeperC++3 节点9 核18 GB<0.5%P99=2 秒几乎为零
节省75%75%78%87%86%>90%
分享这篇文章

订阅我们的新闻资讯

随时了解功能发布、产品路线图、支持和云服务信息!
正在加载表单...
关注我们
X imageSlack imageGitHub image
Telegram imageMeetup imageRss image
©2025ClickHouse, Inc. 总部位于加利福尼亚州湾区和荷兰阿姆斯特丹。