DoubleCloud 即将停止服务。在限定时间内,可享受免费迁移服务,迁移至 ClickHouse。立即联系我们。 ->->

博客 / 用户案例

Bonree 替换 ZooKeeper 为 ClickHouse Keeper,显著提升性能并降低成本。

author avatar
李华晨 和 吕康志
2024 年 7 月 22 日

Bonree_01.png

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

"从 ZooKeeper 迁移到 ClickHouse Keeper 使**CPU 和内存使用量减少了 75% 以上**。"

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

背景

Bonree ONE 是由Bonree 数据科技 提供的一体化智能可观测性平台。

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

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

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

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

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

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

ZooKeeper 成为瓶颈

随着数据量和数据类型的不断扩展,ClickHouse 集群对 ZooKeeper 的压力也随之增加。例如,在需要为告警数据存储和查询提供高实时性能的场景中,我们遇到了以下 ZooKeeper 相关问题:

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

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

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

在相同可观测性数据环境下的对比测试中,ZooKeeper 的内存消耗高出 4.5 倍,I/O 使用量高出 8 倍。

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

基于上述痛点以及写入密集型数据分析场景的特点,我们选择了 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 个分片的 Part 元数据存储在一个专用的 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 个分片的 Part 元数据由一个专用的 ZooKeeper 集群(ZK1ZK2ZK3)管理,而 ZK4 管理 ClickHouse 集群 2 的 5 个分片 Part 元数据。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 中的数据。即使没有数据导入,后台任务(例如后台数据合并)也会更改 ZooKeeper 中的数据,这使得在迁移前后难以保证数据一致性。我们深入研究了 ClickHouse 中导致 ZooKeeper 数据修改的后台任务,并使用命令(例如 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 配置文件不需要修改。如果没有加密,即在使用 ZooKeeper 时 ClickHouse 没有配置 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。

结果

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

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

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

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

语言资源CPU 利用率RAM 利用率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%
分享此文章

订阅我们的时事通讯

随时了解功能发布、产品路线图、支持和云服务信息!
加载表单...
关注我们
Twitter imageSlack imageGitHub image
Telegram imageMeetup imageRss image