ClickHouse进阶篇-多表连接物化视图

本文介绍了ClickHouse物化视图支持多表Join的特性,通过示例展示了如何创建和使用物化视图进行数据重组。在Join中,物化视图仅触发最左侧表的更新,而其他表的更改不会直接触发物化视图的更新。文章还探讨了物化视图的潜在问题和注意事项,强调了测试和理解其工作原理的重要性。

简介

在写这篇文章的时候doris 1.2 的物化视图只是支持单表建立物化视图,现在说下ClickHouse多表的物化视图。

前言

本文翻译自 Altinity 针对 ClickHouse 的系列技术文章。面向联机分析处理(OLAP)的开源分析引擎 ClickHouse,因其优良的查询性能,PB 级的数据规模,简单的架构,被国内外公司广泛采用。

阿里云 EMR-OLAP 团队,基于开源 ClickHouse 进行了系列优化,提供了开源 OLAP 分析引擎 ClickHouse 的云上托管服务。EMR ClickHouse 完全兼容开源版本的产品特性,同时提供集群快速部署、集群管理、扩容、缩容和监控告警等云上产品功能,并且在开源的基础上优化了 ClickHouse 的读写性能,提升了 ClickHouse 与 EMR 其他组件快速集成的能力。访问https://help.aliyun.com/document_detail/212195.html
了解详情。

在 ClickHouse 物化视图中使用 Join

ClickHouse 物化视图提供了一种在 ClickHouse 中重组数据的强大方法。我们已经在网络研讨会、博客文章和会议讲座中多次讨论了其能力。我们收到的最常见的后续问题之一是:物化视图是否支持 Join。

答案是肯定的。这篇博客文章展示了具体方法。如果你想要简短的答案,那就是:物化视图会触发 Join 中最左侧的表。物化视图将从 Join 中的右侧表提取值,但如果这些表发生变化,则不会触发。

请继续阅读关于物化视图与 Join 行为的详细示例。我们还将解释底层的原理,帮助你在创建自己的视图时更好地理解 ClickHouse 行为。注:示例来自 ClickHouse 版本 20.3。

表定义

物化视图可以用各种有趣的方式转换数据,但我们只说简单的。我们将以 download 表为例,演示如何构建从几个维度表中提取信息的每日下载总数指标。该模式的摘要如下。

我们首先定义 download 表。这个表可能会变得非常大。

CREATE TABLE download (

  when DateTime,

  userid UInt32,

  bytes UInt64

) ENGINE=MergeTree

PARTITION BY toYYYYMM(when)

ORDER BY (userid, when)

接下来,我们定义一个维度表,该表将用户 ID 映射到每 GB 下载量的价格。这个表相对较小。

CREATE TABLE price (

  userid UInt32,

  price_per_gb Float64

) ENGINE=MergeTree

PARTITION BY tuple()

ORDER BY userid

最后,我们定义一个维度表,该表将用户 ID 映射到名称。这个表也同样很小。

CREATE TABLE user (

  userid UInt32,

  name String

) ENGINE=MergeTree

PARTITION BY tuple()

ORDER BY userid

物化视图定义

现在,让我们创建一个物化视图,该视图按用户 ID 汇总每日下载次数和字节数,并根据下载的字节数计算价格。我们需要直接创建目标表,然后使用一个带有 TO 关键字(指向我们的表)的物化视图定义。

目标表如下。

CREATE TABLE download_daily (

  day Date,

  userid UInt32,

  downloads U
在 **ClickHouse 分布式集群**中创建 Kafka ,目的是让整个集群能够协调地从 Kafka 消费数据,并将消息分发到各个分片(shard)进行处理或存储。 但由于 ClickHouse 的 `Kafka` 引擎是**非分布式的、本地引擎**,你不能直接在一个分布式节点上“全局消费”Kafka。因此,必须采用特定架构设计来实现高可用、负载均衡的 Kafka 数据接入。 --- ## ✅ 目标 在 ClickHouse 分布式集群中: - 从 Kafka 主题消费消息; - 将数据均匀写入各分片的本地(如 `ReplicatedMergeTree`); - 支持容错和扩展性; - 避免重复消费或副本重复写入。 --- ## 🧩 核心原理:Kafka + 物化视图 + 分布式架构 标准做法如下: ```text +------------------+ | Kafka Cluster | +--------+---------+ | v +---------------------------+ | Kafka Engine Table (Local)| | on ONE replica per shard| +------------+--------------+ | v +--------------------------+ | Materialized View | | → Distributed Table | | → Local MergeTree Tables | +--------------------------+ ``` > ⚠️ 关键点:**每个 shard 上只在一个 replica 上部署 Kafka 消费逻辑**,避免副本重复消费。 --- ## ✅ 步骤详解:在分布式集群中接入 Kafka ### 🔹 第一步:定义宏(metrika.xml) 确保每个节点配置了 `{shard}` 和 `{replica}` 宏,用于复制路径生成。 ```xml <!-- /etc/clickhouse-server/config.d/metrika.xml --> <yandex> <macros> <shard>wxr1</shard> <!-- 每个 shard 值不同 --> <replica>replica1</replica> <!-- 每个 replica 值不同 --> </macros> </yandex> ``` --- ### 🔹 第二步:为每个 shard 创建本地目标(使用 ReplicatedMergeTree) #### 在每个 shard 的所有 replica 上执行: ```sql CREATE TABLE db.hw_x_vhr_dtc_local ON CLUSTER 'your_cluster_name' ( timestamp DateTime, signal_id String, value Float64, device_id String ) ENGINE = ReplicatedMergeTree( '/clickhouse/tables/{shard}/hw_x_vhr_dtc_multi_signal_inspect_log', '{replica}' ) PARTITION BY toYYYYMMDD(timestamp) ORDER BY (timestamp, signal_id); ``` ✅ 这是一个可复制的本地,每个 shard 存一份。 --- ### 🔹 第三步:创建对应的 `Distributed` (跨 shard 查询用) ```sql CREATE TABLE db.hw_x_vhr_dtc_all ON CLUSTER 'your_cluster_name' AS db.hw_x_vhr_dtc_local ENGINE = Distributed('your_cluster_name', db, hw_x_vhr_dtc_local, rand()); ``` 这个用于跨所有 shard 查询。 --- ### 🔹 第四步:仅在每个 shard 的一个 replica 上创建 Kafka 消费物化视图 > ❗ 注意:**只能在一个 replica 上创建 Kafka 物化视图**,否则会重复消费! 我们可以借助 `{replica}` 判断是否为主消费者。 #### 方法一:手动选择主 replica(推荐用于生产) 比如你决定每个 shard 上 `replica1` 负责消费 Kafka。 ##### 在 `replica1` 上执行: ```sql -- 创建 Kafka 引擎(仅本地) CREATE TABLE db.kafka_queue ON CLUSTER 'your_cluster_name' ( timestamp DateTime, signal_id String, value Float64, device_id String ) ENGINE = Kafka SETTINGS kafka_broker_list = 'kafka1:9092,kafka2:9092,kafka3:9092', kafka_topic_list = 'iot_signals', kafka_group_name = 'clickhouse_iot_group', kafka_format = 'JSONEachRow', kafka_num_consumers = 1, kafka_skip_broken_messages = 10; ``` > `kafka_group_name` 必须唯一,确保消费者组只有一个实例在读取。 --- #### 第五步:创建物化视图,将 Kafka 数据写入本地 仍然在 `replica1` 上执行: ```sql CREATE MATERIALIZED VIEW db.consumer TO db.hw_x_vhr_dtc_local AS SELECT timestamp, signal_id, value, device_id FROM db.kafka_queue; ``` ✅ 效果: - Kafka 消息被 `replica1` 消费; - 自动插入本地 `hw_x_vhr_dtc_local` - 其他副本通过 ZooKeeper 同步该数据(由 `ReplicatedMergeTree` 保证); --- ## 📊 数据流图示 ```text +-------------+ +------------------+ +--------------------+ | | | | | | | Kafka |---->| kafka_queue |---->| Materialized View |-----> hw_x_vhr_dtc_local | Topic | | (on replica1) | | (inserts locally) | [每个 shard 一份] | | | | | | +-------------+ +------------------+ +--------------------+ | v Distributed Table (all shards) ``` --- ## ✅ 如何验证是否正常工作? ### 1. 查看 Kafka 消费状态 ```sql SELECT * FROM system.tables WHERE name = 'kafka_queue'; SELECT * FROM system.kafka_consumers; SELECT * FROM system.kafka_offsets; ``` ### 2. 检查是否有错误日志 ```sql SELECT * FROM system.errors WHERE name LIKE '%Kafka%' OR last_error_time > now() - INTERVAL 5 MINUTE; ``` ### 3. 查询本地是否有数据 ```sql SELECT count(*) FROM db.hw_x_vhr_dtc_local; SELECT * FROM db.hw_x_vhr_dtc_local LIMIT 5; ``` ### 4. 查询分布式(全集群汇总) ```sql SELECT count(*) FROM db.hw_x_vhr_dtc_all; ``` --- ## ⚠️ 常见问题与最佳实践 | 问题 | 解决方案 | |------|----------| | 个 replica 消费导致重复数据 | 只在一个 replica 上建 `kafka_queue` 和 `MV` | | 消费者宕机后无法恢复 | 使用稳定的 `kafka_group_name`,ZooKeeper 记录 offset | | 消费速度慢 | 增加 `kafka_num_consumers` 或分区数 | | 格式解析失败 | 使用 `kafka_skip_broken_messages` 跳过坏消息(调试用) | | Kafka 无法连接 | 检查网络、topic 是否存在、broker 地址 | --- ## ✅ 高可用建议:自动切换消费者(进阶) 如果你希望支持故障转移(例如 `replica1` 挂了,`replica2` 接管消费),可以结合外部监控工具(如 Consul、ZooKeeper、Prometheus + Alertmanager)动态控制物化视图启停,但这需要额外开发。 更简单的替代方案是使用 **Kafka Connect + ClickHouse Sink Connector**,实现更健壮的分布式消费。 --- ## ✅ 总结:完整脚本模板(适用于集群) ```sql -- Step 1: 创建本地复制(所有节点) CREATE TABLE IF NOT EXISTS db.hw_x_vhr_dtc_local ON CLUSTER 'your_cluster' ( timestamp DateTime, signal_id String, value Float64, device_id String ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/hw_x_vhr_dtc_log', '{replica}') PARTITION BY toYYYYMMDD(timestamp) ORDER BY (timestamp, signal_id); -- Step 2: 创建分布式 CREATE TABLE IF NOT EXISTS db.hw_x_vhr_dtc_all ON CLUSTER 'your_cluster' AS db.hw_x_vhr_dtc_local ENGINE = Distributed('your_cluster', db, hw_x_vhr_dtc_local, rand()); -- Step 3: 仅在主 replica 上创建 Kafka (如 replica1) CREATE TABLE IF NOT EXISTS db.kafka_queue ( timestamp DateTime, signal_id String, value Float64, device_id String ) ENGINE = Kafka SETTINGS kafka_broker_list = 'kafka1:9092,kafka2:9092', kafka_topic_list = 'iot_signals', kafka_group_name = 'ch_iot_group_v1', kafka_format = 'JSONEachRow', kafka_skip_broken_messages = 5; -- Step 4: 创建物化视图(仅主 replica) CREATE MATERIALIZED VIEW IF NOT EXISTS db.consumer TO db.hw_x_vhr_dtc_local AS SELECT timestamp, signal_id, value, device_id FROM db.kafka_queue; ``` > 💡 提示:可以用 Ansible/Jinja 判断 `{replica} == 'replica1'` 来条件执行最后两步。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

工作变成艺术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值