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. 表引擎語法
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 在所有節點上執行):

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. 表引擎語法
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 只保留最新的狀態記錄。

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 是去重的鍵

插入測試數據:

-- 第一次插入
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');

立即查詢(合併可能尚未發生):

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 查詢或等待合併後查詢:

-- 方式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 強大的分佈式數據處理能力。