ClickHouse 中至关重要的两类复制表引擎——ReplicatedMergeTree和 ReplicatedReplacingMergeTree

ClickHouse 中至关重要的两类复制表引擎:ReplicatedMergeTreeReplicatedReplacingMergeTree。它们是构建 ClickHouse 高可用、高可靠分布式集群的基石。

核心概念:复制与复制表引擎

首先,要理解 "Replicated" 前缀的含义。它指的是 表级别的复制,基于 ZooKeeper(或 ClickHouse Keeper)进行协调,能够在多个 ClickHouse 节点(副本)之间自动同步数据,从而实现:

  • 高可用性:如果一个副本宕机,其他副本仍然可以提供服务。
  • 数据可靠性:数据在多个节点上有备份,防止单点故障导致数据丢失。
  • 读扩展:可以将读查询负载分布到所有副本上。

重要提示ReplicatedMergeTreeReplicatedReplacingMergeTree 本身并不是独立的"引擎",而是 MergeTree 引擎系列的 复制变体。它们必须基于相应的基引擎。


一、ReplicatedMergeTree

这是最基础、最常用的复制表引擎。它本质上是为 MergeTree 表引擎加上了复制能力。

1. 工作原理
  1. 依赖协调服务 :创建 ReplicatedMergeTree 表时,必须指定一个在 ZooKeeper 中的路径。这个路径作为该表所有副本的"协调中心"。
  2. 日志与队列 :当向任何一个副本插入数据时,该副本不会直接插入数据,而是先在 ZooKeeper 的指定路径下创建一个 日志条目,包含"插入哪些数据"的信息。
  3. 数据同步 :该表的所有其他副本都会 监听 这个 ZooKeeper 路径。它们会发现新的日志条目,然后主动从发起插入的副本 拉取 对应的数据分区(part)。
  4. 一致性保证 :通过 ZooKeeper 的原子性和顺序性保证,所有副本最终都会以相同的顺序执行相同的插入操作,从而保证数据的最终一致性。
  5. 去重:如果在复制过程中发生网络中断,导致同一个数据块被多次记录到日志中,副本在拉取数据时会通过块的哈希值进行去重,确保数据不会重复插入。
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的两个副本就通过相同的 zk_path 组成了一个复制组。

4. 特点与适用场景
  • 特点
    • 数据在多个副本间自动同步。
    • 支持 INSERT, ALTER, OPTIMIZE 等操作的复制。
    • 如果某个副本宕机后恢复,它能自动从其他健康的副本同步缺失的数据。
  • 适用场景绝大多数需要数据可靠性和高可用的场景,如日志存储、用户行为数据、监控指标等。

二、ReplicatedReplacingMergeTree

这个引擎是 ReplicatedMergeTreeReplacingMergeTree 的结合。它在具备复制能力的基础上,增加了 "主键去重" 的功能。

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 键和版本列对数据进行去重,保留最新版本。
    • 查询需要特殊处理(FINALGROUP BY)才能立即看到去重结果。
  • 适用场景
    • 维表更新:如存储用户的最新属性、商品的最新信息。
    • 数据去重:从 Kafka 等消息队列中消费数据时,可能遇到"至少一次"交付导致的重复数据,可以使用此引擎进行最终去重。
    • 状态记录:如设备在线状态、会话最新状态等。

总结与对比

特性 ReplicatedMergeTree ReplicatedReplacingMergeTree
核心能力 数据复制、高可用 数据复制 + 主键去重
数据模型 追加式(Append-Only) 更新式(Upsert-style,最终一致)
去重逻辑 仅对数据块(Block)级复制去重 相同排序键 的数据行进行去重
版本控制 不支持 支持通过 version_column 选择保留哪一行
查询结果 总是反映所有插入的数据 可能包含重复数据 ,需用 FINAL 或聚合
典型场景 日志、事件流、监控指标 用户画像、商品信息、状态表等需要"最新值"的场景

选择指南

  • 如果你的数据是 仅追加的、不可变的 (如日志、事件),使用 ReplicatedMergeTree
  • 如果你的数据有 主键概念,且新数据会覆盖旧数据 ,使用 ReplicatedReplacingMergeTree

最后,无论是哪种复制表,它们通常不作为直接查询的表,而是作为 分布式表(Distributed Table) 的底层本地表,与分片(Sharding)技术结合,共同构建起 ClickHouse 强大的分布式数据处理能力。

相关推荐
RPA机器人就选八爪鱼1 小时前
RPA财务机器人:重塑财务效率,数字化转型的核心利器
大数据·数据库·人工智能·机器人·rpa
k***12171 小时前
从 SQL 语句到数据库操作
数据库·sql·oracle
ITVV2 小时前
flink CDC 3.5.0
大数据·flink
Elastic 中国社区官方博客3 小时前
Elasticsearch:如何创建知识库并使用 AI Assistant 来配置 slack 连接器
大数据·人工智能·elasticsearch·搜索引擎·全文检索·信息与通信
XIAOYU6720133 小时前
中专学历,在服装设计行业真的没出路吗?
大数据
TDengine (老段)3 小时前
TDengine IDMP 赋能新能源:光伏电站智能运维实践
大数据·运维·数据库·物联网·时序数据库·tdengine·涛思数据
努力的光头强5 小时前
《智能体设计模式》从零基础入门到精通,看这一篇就够了!
大数据·人工智能·深度学习·microsoft·机器学习·设计模式·ai
w***37515 小时前
【SQL技术】不同数据库引擎 SQL 优化方案剖析
数据库·sql
小园子的小菜5 小时前
深度剖析Elasticsearch数据写入与读取:从分片同步到核心组件协同
大数据·elasticsearch·搜索引擎