这是《ClickHouse 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 分布式表是分片集群的统一入口。它本身不保存业务数据,而是负责把写入路由到分片,把查询下发到各分片本地表,再合并结果。
17.1 架构
Distributed table: events_all
|
+----------------+----------------+
| | |
shard-1 shard-2 shard-3
events_local events_local events_local
|
replica-1
replica-2
本地表是真实存储,分布式表是路由和查询协调层。
17.2 创建本地表
在每个分片上创建:
CREATE TABLE analytics.events_local
(
event_date Date,
event_time DateTime,
user_id UInt64,
event_type LowCardinality(String),
city_id UInt32,
amount Decimal64(2)
)
ENGINE = ReplicatedMergeTree(
'/clickhouse/tables/{shard}/events',
'{replica}'
)
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, event_type, city_id, user_id);
可以使用 ON CLUSTER:
CREATE TABLE analytics.events_local
ON CLUSTER analytics_cluster
(
event_date Date
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/events', '{replica}')
ORDER BY event_date;
集群 DDL 要先在测试环境验证宏配置和权限。
17.3 创建分布式表
CREATE TABLE analytics.events_all
AS analytics.events_local
ENGINE = Distributed(
analytics_cluster,
analytics,
events_local,
cityHash64(user_id)
);
参数含义:
| 参数 | 含义 |
|---|---|
| analytics_cluster | 集群名 |
| analytics | 本地数据库 |
| events_local | 本地表 |
| cityHash64(user_id) | 分片键表达式 |
常用分片函数:
rand()
cityHash64(user_id)
cityHash64(user_id, event_date)
sipHash64(order_id)
17.4 写入路径
通过 Distributed 写入:
应用 -> 任一 ClickHouse 节点
-> 计算目标分片
-> 异步或同步转发
-> 各分片本地表
优点是使用简单;缺点是写入节点会承担转发压力,并可能出现本地 buffer 数据。写入端直连分片更可控,但需要应用或代理层维护拓扑。
17.5 查询路径
客户端
-> Distributed 表
-> 每个 shard 选择副本
-> 本地表并行执行
-> 返回中间结果
-> 发起节点合并
查看集群:
SELECT cluster, shard_num, replica_num, host_name, is_local
FROM system.clusters
WHERE cluster = 'analytics_cluster';
17.6 聚合语义
可下发聚合:
SELECT event_date, city_id, count(), sum(amount)
FROM analytics.events_all
WHERE event_date = today()
GROUP BY event_date, city_id;
每个分片先算局部结果,再由发起节点合并。
需要注意:
countDistinct(user_id)跨分片需合并集合;uniq(user_id)可以合并近似状态;avg不能直接对各分片平均值求平均;min/max/sum可合并;LIMIT可能需要各分片返回局部 TopN。
17.7 分布式 DDL
CREATE TABLE analytics.events_local ON CLUSTER analytics_cluster
(
event_date Date,
user_id UInt64
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/events', '{replica}')
ORDER BY event_date;
查看队列:
SELECT cluster, query, host, status, exception_code
FROM system.distributed_ddl_queue
ORDER BY entry DESC
LIMIT 20;
集群 DDL 超时或部分节点失败时,要检查各节点实际表结构,不能只看返回结果。
17.8 负载均衡与副本
常用做法:
- 应用通过负载均衡访问 ClickHouse;
- Distributed 表选择分片副本;
- 查询节点和存储节点分离;
- 长查询和短查询隔离;
- 限流和排队。
如果发起节点承担所有合并和结果输出,它可能先成为瓶颈。大规模报表可以引入独立查询入口和资源池。
17.9 运维操作
查看远程文件系统缓存、分布式队列和 part:
SELECT database, table, is_blocked, error_count,
data_files, bytes_to_send
FROM system.distribution_queue;
修复分布式表:
SYSTEM STOP DISTRIBUTED SENDS analytics.events_all;
SYSTEM START DISTRIBUTED SENDS analytics.events_all;
当写入积压时,先检查目标分片、网络、磁盘、Too many parts 和权限,而不是盲目重启。
17.10 常见错误
| 问题 | 原因 |
|---|---|
| 查询缺少数据 | 分片路由变化 |
| 数据倾斜 | 分片键选择不当 |
| 某分片慢 | 副本延迟或磁盘热点 |
| DDL 部分失败 | 节点不可达或权限异常 |
| 写入队列积压 | 异步转发受阻 |
| 结果不一致 | 查询到延迟副本 |
本章小结
分布式表把多个分片的本地表组织成统一查询入口,分片键决定数据分布,副本提供可用性。使用时要理解写入转发、查询合并、分布式 DDL 和故障语义。分布式表不是高可用本身,稳定的集群来自分片、副本、监控和容量规划共同作用。
思考题
- Distributed 表保存业务数据吗?
- 如何选择分片键?
- 跨分片 avg 为什么不能直接平均?
- 分布式 DDL 部分失败如何排查?
- 应用直连分片和写 Distributed 表各有什么取舍?