将 Kafka + 物化视图(MV) + ReplacingMergeTree 结合使用,是构建 ClickHouse 实时去重数据管道 的经典架构。它广泛应用于日志去重、用户行为分析、状态更新、CDC(变更数据捕获)等场景,能够实现 高吞吐、低延迟、最终一致性去重。
本篇将带你一步步构建一个完整的 实时去重管道,从 Kafka 消费消息,通过物化视图写入去重表,最终提供高效查询。
🎯 一、目标架构
[Kafka]
│
↓ (消息流)
[ClickHouse Kafka Engine 表] → 消费消息
│
↓
[物化视图 MV] → 转换 & 写入
│
↓
[ReplacingMergeTree 表] → 按主键去重,保留最新
│
↓
[查询层] → 使用 argMax 获取最终状态
✅ 实现效果:
- 实时摄入 Kafka 数据
- 自动去重(相同主键保留最新)
- 支持高效聚合查询
🧰 二、环境准备
| 组件 | 要求 |
|---|---|
| ClickHouse | ≥ v20.8(推荐 v22+) |
| Kafka | ≥ v1.0(支持 JSON/Protobuf) |
| 网络 | ClickHouse 能访问 Kafka 集群 |
| 数据格式 | 推荐 JSONEachRow 或 Protobuf |
📦 三、步骤 1:创建 Kafka 引擎表(消息接口)
这个表不存储数据,仅作为 Kafka 消息的“入口”。
CREATE TABLE kafka_user_events (
user_id UInt32,
event_type String,
page String,
device_id String,
event_time DateTime,
update_version UInt64 -- 用于去重版本(如 Kafka offset 或业务版本号)
) ENGINE = Kafka
SETTINGS
kafka_broker_list = 'kafka1:9092,kafka2:9092',
kafka_topic_list = 'user-events-topic',
kafka_group_name = 'clickhouse-consumer-group',
kafka_format = 'JSONEachRow', -- 每行一个 JSON 对象
kafka_num_consumers = 4; -- 每个表 4 个消费者,提升吞吐
✅ 示例 Kafka 消息:
{"user_id":1001,"event_type":"pageview","page":"/home","device_id":"dev123","event_time":"2024-04-01 10:00:00","update_version":1}
🧩 四、步骤 2:创建去重目标表(ReplacingMergeTree)
该表用于存储去重后的数据。
CREATE TABLE user_events_dedup (
user_id UInt32,
event_type String,
page String,
device_id String,
event_time DateTime,
update_version UInt64
) ENGINE = ReplacingMergeTree(update_version) -- 以 update_version 为版本列
PARTITION BY toYYYYMM(event_time)
ORDER BY (user_id, event_time); -- 按 user_id 去重,按时间排序
✅ 关键点:
ReplacingMergeTree(update_version):合并时保留update_version最大的记录ORDER BY (user_id, ...):user_id是去重主键PARTITION BY:按月分区,便于管理
🚀 五、步骤 3:创建物化视图(自动写入管道)
物化视图监听 kafka_user_events,自动将数据写入 user_events_dedup。
CREATE MATERIALIZED VIEW mv_kafka_to_dedup
TO user_events_dedup
AS SELECT
user_id,
event_type,
page,
device_id,
event_time,
update_version
FROM kafka_user_events;
✅ 效果:
- Kafka 中每新增一条消息,物化视图自动触发
- 数据写入
user_events_dedup,等待后台合并去重
🔄 六、工作流程详解
1. 数据流入
Kafka 生产者
│
↓
user-events-topic
│
↓
kafka_user_events(Kafka Engine)
│
↓
物化视图 mv_kafka_to_dedup
│
↓
user_events_dedup(ReplacingMergeTree)
2. 去重机制
假设 Kafka 中有以下消息:
| user_id | event_type | update_version |
|---|---|---|
| 1001 | login | 1 |
| 1001 | login | 1 |
| 1001 | logout | 2 |
后台 Merge 任务执行后:
- 相同
(user_id, event_time)的记录被合并 - 保留
update_version = 2的logout记录
📊 七、查询去重结果(推荐方式)
❌ 避免使用 FINAL
-- 性能极差,全表扫描,不推荐
SELECT * FROM user_events_dedup FINAL WHERE user_id = 1001;
✅ 推荐:GROUP BY + argMax
SELECT
user_id,
argMax(event_type, update_version) AS latest_event,
argMax(page, update_version) AS latest_page,
argMax(device_id, update_version) AS device_id,
max(event_time) AS last_active
FROM user_events_dedup
WHERE event_time >= '2024-04-01'
GROUP BY user_id
HAVING latest_event != 'logout' -- 可继续过滤
ORDER BY last_active DESC
LIMIT 100;
✅ 优势:
- 利用
ORDER BY和分区裁剪- 并行执行,性能高
- 可扩展为多维度聚合
📈 八、性能优化建议
| 优化项 | 建议 |
|---|---|
| Kafka 消费 | kafka_num_consumers = CPU 核数 |
| 写入批次 | Kafka 消息批量发送(>1000 条/批) |
| 物化视图逻辑 | 尽量简单,避免复杂 JOIN |
| 分区策略 | 按时间分区,便于 TTL 和删除 |
| TTL | 自动清理过期数据 |
TTL event_time + INTERVAL 90 DAY
| 监控 | 监控 system.kafka_consumers, system.parts, query_log |
⚠️ 九、常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 数据未去重 | 合并未触发 | 手动 OPTIMIZE TABLE user_events_dedup |
| Kafka 消费延迟 | 消费者慢 | 增加 kafka_num_consumers |
| 物化视图卡住 | 源表无数据或错误 | 检查 system.tables 和日志 |
argMax 结果不准 | update_version 不单调 | 确保版本号递增 |
| 内存不足 | 查询大表 | 增加 max_memory_usage 或分页查询 |
🛠️ 十、扩展:支持多主键去重
如果去重键是多个字段(如 user_id + session_id):
ORDER BY (user_id, session_id, event_time)
查询时:
SELECT
user_id,
session_id,
argMax(page, update_version) AS last_page
FROM user_events_dedup
GROUP BY user_id, session_id;
🎯 十一、总结:Kafka + MV + ReplacingMergeTree 的核心价值
| 组件 | 职责 | 优势 |
|---|---|---|
Kafka Engine | 实时消费消息 | 解耦生产与消费,高吞吐 |
Materialized View | 自动写入管道 | 无需外部 ETL,实时处理 |
ReplacingMergeTree | 最终一致性去重 | 存储高效,查询快 |
🎯 这套组合是 ClickHouse 构建“实时数仓”的“黄金三角”:
- 实时性:秒级延迟
- 可靠性:Kafka 保证不丢
- 去重:最终一致性
- 可扩展:支持 PB 级数据
832

被折叠的 条评论
为什么被折叠?



