返回首页

Kafka Engine ClickHouse:无损失原子性

本文分析了 ClickHouse MergeTree 中频繁 INSERT 的问题以及通过 Kafka Engine 的解决方案。演示了原子性:仅在成功写入后提交 offset。与 buffer 表的比较、管道代码和测试。

Kafka Engine 防止 ClickHouse 中的数据丢失
Advertisement 728x90

ClickHouse 中的 Kafka 引擎:确保无数据丢失的原子插入

频繁向 MergeTree 表执行单个插入(每秒 10,000 次 INSERT)会导致性能下降至每秒 59 行。对于 ReplicatedMergeTree,降至每秒 6 行,使用 quorum 时仅为每秒 2 行。这是由于产生了大量小数据部分,这些部分会根据 max_merge_selecting_sleep_ms 设置定期合并。合并过程会过度占用 CPU 和 I/O,从而降低整体速度。

Buffer 表看似是一个解决方案:数据在内存中累积,然后按时间、大小或行数批量刷新到目标表。这可以最小化文件数量和合并负载。然而,如果 ClickHouse 崩溃,内存中未刷新的数据就会丢失。

Buffer 表:机制与风险

Buffer 表使用 Buffer 引擎,在内存中累积数据,直到满足刷新条件:max_time_to_flush(例如 100 秒)、min_time_to_flush、max_rows_to_flush(100 万行)、max_bytes_to_flush(1 GB)。刷新会原子性地写入 MergeTree,但仅在写入磁盘后完成。

Google AdInline article slot

关键风险: 如果服务器崩溃(kill -9、OOM),内存中的 Buffer 内容将永久丢失。Buffer 没有复制或 WAL,这使得该方法不适合高负载流。

切换到 Kafka 引擎即可解决问题:数据在 Kafka 中持久化,ClickHouse 仅在成功处理后提交 offset。

设置 Kafka 引擎管道

简单栈:Kafka + ZooKeeper + ClickHouse 在 Docker 中。创建 test_topic_json 主题,包含 JSON 消息:

Google AdInline article slot
{"message": "qq"}
{"message": "bb"}

在 ClickHouse 中,管道由三个组件组成:

  • 用于读取的 Kafka 引擎表:
drop table if exists test_kafka_json_parse;
create table test_kafka_json_parse
(
    message String
)
engine = Kafka
SETTINGS kafka_broker_list = 'kafka:29092'
    , kafka_topic_list = 'test_topic_json'
    , kafka_group_name = 'my-group4'
    , kafka_format = 'JSONEachRow'
    , kafka_max_block_size = 2097152;
  • 作为消费者的物化视图:
drop view if exists test_kafka_json_parse_view;
create materialized view test_kafka_json_parse_view
TO default.message_json_parse
(
 `message` String
)
AS SELECT message FROM test_kafka_json_parse;
  • 目标 MergeTree:
create table message_json_parse
(
    message String
)
engine = MergeTree
order by message;

演示原子性

发送消息后,my-group4 的消费者 offset 为 2(End=2)。现在将 message_json_parse 中的列类型改为 UInt64 —— 解析 JSON 字符串 "gg" 将失败:

drop table if exists message_json_parse;
create table message_json_parse
(
    message UInt64
)
engine = MergeTree
order by message;

发送 {"message": "gg"}。offset 保持在 2(End=3),ClickHouse 日志:Cannot parse string 'gg' as UInt64offset 仅在成功写入 MV → MergeTree 后提交。

Google AdInline article slot

ClickHouse 将消息读入内存,但在出错时不提交 offset。新消息将被阻塞,直到问题修复。类型改回 String 后:

  • "gg" 消息被插入。
  • offset 更新为 3。
  • 即使 ClickHouse 重启,也无数据丢失。

关键点

  • 原子性: Kafka offset 在插入物化视图后提交,确保精确一次语义。
  • 恢复: 故障/错误后,从最后一个已提交的 offset 恢复处理,无重复或跳过。
  • 性能: 从 Kafka 批量处理,最小化 MergeTree 中的小数据部分。
  • 可靠性: Kafka 持久存储数据;ClickHouse 作为无状态消费者。
  • 限制: 需要 Kafka 基础设施;为严格处理设置 kafka_skip_broken_messages=0。

实现推荐

  • 使用 kafka_num_consumers >1 以实现并行。
  • 设置 max_block_size=2MB+ 以进行批量处理。
  • 监控 offset:SELECT * FROM system.kafka_consumers
  • 对于 JSON:使用 JSONEachRow 并严格解析。

— Editorial Team

Advertisement 728x90

继续阅读