ClickHouse 中至关重要的两类复制表引擎:ReplicatedMergeTree 和 ReplicatedReplacingMergeTree。它们是构建 ClickHouse 高可用、高可靠分布式集群的基石。
核心概念:复制与复制表引擎
首先,要理解 "Replicated" 前缀的含义。它指的是 表级别的复制,基于 ZooKeeper(或 ClickHouse Keeper)进行协调,能够在多个 ClickHouse 节点(副本)之间自动同步数据,从而实现:
- 高可用性:如果一个副本宕机,其他副本仍然可以提供服务。
- 数据可靠性:数据在多个节点上有备份,防止单点故障导致数据丢失。
- 读扩展:可以将读查询负载分布到所有副本上。
重要提示 :ReplicatedMergeTree 和 ReplicatedReplacingMergeTree 本身并不是独立的"引擎",而是 MergeTree 引擎系列的 复制变体。它们必须基于相应的基引擎。
一、ReplicatedMergeTree
这是最基础、最常用的复制表引擎。它本质上是为 MergeTree 表引擎加上了复制能力。
1. 工作原理
- 依赖协调服务 :创建
ReplicatedMergeTree表时,必须指定一个在 ZooKeeper 中的路径。这个路径作为该表所有副本的"协调中心"。 - 日志与队列 :当向任何一个副本插入数据时,该副本不会直接插入数据,而是先在 ZooKeeper 的指定路径下创建一个 日志条目,包含"插入哪些数据"的信息。
- 数据同步 :该表的所有其他副本都会 监听 这个 ZooKeeper 路径。它们会发现新的日志条目,然后主动从发起插入的副本 拉取 对应的数据分区(part)。
- 一致性保证 :通过 ZooKeeper 的原子性和顺序性保证,所有副本最终都会以相同的顺序执行相同的插入操作,从而保证数据的最终一致性。
- 去重:如果在复制过程中发生网络中断,导致同一个数据块被多次记录到日志中,副本在拉取数据时会通过块的哈希值进行去重,确保数据不会重复插入。
2. 表引擎语法
sql
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
`column1` Type1,
`column2` Type2,
...
)
ENGINE = ReplicatedMergeTree('zk_path', 'replica_name')
[PARTITION BY expr]
[ORDER BY expr]
[PRIMARY KEY expr]
[SAMPLE BY expr]
[SETTINGS ...];
zk_path(核心参数 ):- 在 ZooKeeper 中的唯一路径,用于标识该表。
- 约定俗成的格式是:
/clickhouse/tables/{shard}/{table_name}。 - 同一个表的所有副本,此路径必须相同。这是它们能互相识别的依据。
replica_name(核心参数 ):- 每个副本的唯一标识符。
- 同一个表的不同副本,此名称必须不同。
- 通常可以使用主机名,如
'replica01',或者通过宏配置{replica}来自动获取。
3. 示例
假设我们有一个名为 analytics 的集群,包含两个分片,每个分片有两个副本。
在一个节点上创建本地表(通常通过 ON CLUSTER 在所有节点上执行):
sql
CREATE TABLE default.page_views_local ON CLUSTER analytics
(
`event_time` DateTime,
`user_id` Int32,
`page_url` String
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/page_views', '{replica}')
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_time, user_id);
{shard}和{replica}是 ClickHouse 的宏,通常在配置文件中定义。例如:- 在分片1的副本1上,宏可能被展开为:
zk_path = '/clickhouse/tables/shard1/page_views',replica_name = 'replica1'。 - 在分片1的副本2上,宏被展开为:
zk_path = '/clickhouse/tables/shard1/page_views'(与副本1相同),replica_name = 'replica2'(与副本1不同)。
- 在分片1的副本1上,宏可能被展开为:
这样,分片1的两个副本就通过相同的 zk_path 组成了一个复制组。
4. 特点与适用场景
- 特点 :
- 数据在多个副本间自动同步。
- 支持
INSERT,ALTER,OPTIMIZE等操作的复制。 - 如果某个副本宕机后恢复,它能自动从其他健康的副本同步缺失的数据。
- 适用场景 :绝大多数需要数据可靠性和高可用的场景,如日志存储、用户行为数据、监控指标等。
二、ReplicatedReplacingMergeTree
这个引擎是 ReplicatedMergeTree 和 ReplacingMergeTree 的结合。它在具备复制能力的基础上,增加了 "主键去重" 的功能。
1. 核心功能:去重
- 问题 :在
ReplicatedMergeTree中,如果对同一主键(由ORDER BY子句定义)插入了多行数据,所有行都会被保留。这在处理流式数据时,可能会遇到同一数据被多次摄入的情况。 - 解决方案 :
ReplacingMergeTree会在后台合并数据分区时,根据ORDER BY键保留最后插入(或指定版本)的一行。 - 重要警告 :去重只发生在后台合并时 ,这是一个不确定的时间点。因此,
SELECT查询可能看到重复的数据。要获得最终去重后的结果,必须使用FINAL关键字或进行聚合查询。
2. 表引擎语法
sql
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
`column1` Type1,
`column2` Type2,
`version_column` UInt32, -- 用于指定版本的列
...
)
ENGINE = ReplicatedReplacingMergeTree('zk_path', 'replica_name' [, version_column])
[PARTITION BY expr]
ORDER BY (primary_key_columns) -- 去重的依据
[PRIMARY KEY expr]
[SAMPLE BY expr]
[SETTINGS ...];
version_column(可选但关键 ):- 一个数值类型的列(如
Date,DateTime,UInt*)。 - 在合并时,对于具有相同
ORDER BY键的多行数据,会保留version_column值最大的那一行。 - 如果未指定版本列,则默认保留最后插入的一行(依赖插入顺序,不可靠)。
- 一个数值类型的列(如
3. 示例
创建一个用户状态表,我们希望每个 user_id 只保留最新的状态记录。
sql
CREATE TABLE default.user_status_local ON CLUSTER analytics
(
`user_id` Int32,
`status` String,
`last_updated` DateTime
)
ENGINE = ReplicatedReplacingMergeTree('/clickhouse/tables/{shard}/user_status', '{replica}', last_updated)
ORDER BY (user_id); -- user_id 是去重的键
插入测试数据:
sql
-- 第一次插入
INSERT INTO user_status_local VALUES (1, 'active', '2023-10-01 10:00:00');
-- 第二次插入同一用户,状态更新
INSERT INTO user_status_local VALUES (1, 'inactive', '2023-10-01 11:00:00');
立即查询(合并可能尚未发生):
sql
SELECT * FROM user_status_local;
可能结果(看到重复):
┌─user_id─┬─status──┬─────────last_updated─┐
│ 1 │ active │ 2023-10-01 10:00:00 │
│ 1 │ inactive│ 2023-10-01 11:00:00 │
└─────────┴─────────┴─────────────────────┘
使用 FINAL 查询或等待合并后查询:
sql
-- 方式1:使用FINAL,强制在查询时合并,性能有损耗
SELECT * FROM user_status_local FINAL;
-- 方式2:使用聚合,模拟最终结果
SELECT
user_id,
argMax(status, last_updated) AS latest_status,
max(last_updated) AS latest_updated
FROM user_status_local
GROUP BY user_id;
最终结果(去重后):
┌─user_id─┬─latest_status─┬─────────latest_updated─┐
│ 1 │ inactive │ 2023-10-01 11:00:00 │
└─────────┴───────────────┴───────────────────────┘
4. 特点与适用场景
- 特点 :
- 具备
ReplicatedMergeTree的所有复制特性。 - 在后台合并时,根据
ORDER BY键和版本列对数据进行去重,保留最新版本。 - 查询需要特殊处理(
FINAL或GROUP BY)才能立即看到去重结果。
- 具备
- 适用场景 :
- 维表更新:如存储用户的最新属性、商品的最新信息。
- 数据去重:从 Kafka 等消息队列中消费数据时,可能遇到"至少一次"交付导致的重复数据,可以使用此引擎进行最终去重。
- 状态记录:如设备在线状态、会话最新状态等。
总结与对比
| 特性 | ReplicatedMergeTree | ReplicatedReplacingMergeTree |
|---|---|---|
| 核心能力 | 数据复制、高可用 | 数据复制 + 主键去重 |
| 数据模型 | 追加式(Append-Only) | 更新式(Upsert-style,最终一致) |
| 去重逻辑 | 仅对数据块(Block)级复制去重 | 对 相同排序键 的数据行进行去重 |
| 版本控制 | 不支持 | 支持通过 version_column 选择保留哪一行 |
| 查询结果 | 总是反映所有插入的数据 | 可能包含重复数据 ,需用 FINAL 或聚合 |
| 典型场景 | 日志、事件流、监控指标 | 用户画像、商品信息、状态表等需要"最新值"的场景 |
选择指南:
- 如果你的数据是 仅追加的、不可变的 (如日志、事件),使用
ReplicatedMergeTree。 - 如果你的数据有 主键概念,且新数据会覆盖旧数据 ,使用
ReplicatedReplacingMergeTree。
最后,无论是哪种复制表,它们通常不作为直接查询的表,而是作为 分布式表(Distributed Table) 的底层本地表,与分片(Sharding)技术结合,共同构建起 ClickHouse 强大的分布式数据处理能力。