该引擎基于 Atomic 引擎。它支持通过将 DDL 日志写入 ZooKeeper,并在给定数据库的所有副本上执行这些日志来复制元数据。
一台 ClickHouse server 上可以同时运行并更新多个 Replicated 数据库。但同一个 Replicated 数据库不能有多个副本。
创建数据库
CREATE DATABASE testdb [UUID '...'] ENGINE = Replicated('zoo_path', 'shard_name', 'replica_name') [SETTINGS ...]引擎参数
zoo_path— ZooKeeper 路径。相同的 ZooKeeper 路径对应同一个数据库。shard_name— 分片名称。数据库副本按shard_name归入不同的分片。replica_name— 副本名称。同一分片中的所有副本,其副本名称必须各不相同。
这些参数可以省略,此时缺失的参数将使用默认值填充。
如果 zoo_path 包含宏 {uuid},则必须显式指定 UUID,或在 CREATE 语句中添加 ON CLUSTER,以确保该数据库的所有副本使用相同的 UUID。
对于 ReplicatedMergeTree 表,如果未提供参数,则会使用默认参数:/clickhouse/tables/{uuid}/{shard} 和 {replica}。这些值可以在服务器设置 default_replica_path 和 default_replica_name 中修改。宏 {uuid} 会展开为表的 UUID,{shard} 和 {replica} 会展开为服务器配置中的值,而不是数据库引擎参数中的值。不过,未来将可以使用 Replicated 数据库的 shard_name 和 replica_name。
此外,也支持使用辅助 ZooKeeper 集群来存储 Replicated 数据库的元数据,而不是使用默认的 ZooKeeper 集群。可以使用如下 SQL 通过辅助 ZooKeeper 集群创建 Replicated 数据库:
CREATE DATABASE database_name ENGINE = Replicated('zookeeper_name_configured_in_auxiliary_zookeepers:path', 'shard_name', 'replica_name')具体细节与建议
使用 Replicated 数据库的 DDL 查询与 ON CLUSTER 查询的工作方式类似,但也有一些细微差别。
首先,DDL 请求会先尝试在 initiator (最初接收用户请求的主机) 上执行。如果请求未能完成,用户会立即收到错误,其他主机不会再尝试执行。如果请求已在 initiator 上成功完成,那么其他所有主机都会自动重试,直到执行成功为止。initiator 会尝试等待其他主机上的查询执行完成 (等待时间不超过 distributed_ddl_task_timeout) ,并返回一张包含各主机查询执行状态的表。
发生错误时的行为由 distributed_ddl_output_mode 设置控制。对于 Replicated 数据库,最好将其设置为 null_status_on_timeout —— 也就是说,如果某些主机未能在 distributed_ddl_task_timeout 时间内执行完请求,则不要抛出异常,而是在表中将它们的状态显示为 NULL。
system.clusters 系统表中包含一个与该 Replicated 数据库同名的集群,它由该数据库的所有副本组成。这个集群会在创建/删除副本时自动更新,并且可用于 Distributed 表。
创建数据库的新副本时,该副本会自行创建表。如果某个副本长时间不可用,导致落后于复制日志,它会将本地元数据与 ZooKeeper 中的当前元数据进行比对,把带数据的多余表移动到一个单独的非复制表数据库中 (以免误删多余内容) ,创建缺失的表,并在表已重命名时更新表名。数据复制是在 ReplicatedMergeTree 级别进行的,也就是说,如果表不是复制表,数据就不会被复制 (数据库只负责元数据) 。
允许使用 ALTER TABLE FREEZE|ATTACH|FETCH|DROP|DROP DETACHED|DETACH PARTITION|PART 查询,但这些操作不会被复制。数据库引擎只会在当前副本上添加/拉取/移除分区/分片。不过,如果表本身使用的是 Replicated 表引擎,那么在使用 ATTACH 之后,数据仍会被复制。
如果你只需要配置集群,而不维护表复制,请参阅 Cluster Discovery 功能。
使用示例
创建一个包含三台主机的集群:
node1 :) CREATE DATABASE r ENGINE=Replicated('some/path/r','shard1','replica1');
node2 :) CREATE DATABASE r ENGINE=Replicated('some/path/r','shard1','other_replica');
node3 :) CREATE DATABASE r ENGINE=Replicated('some/path/r','other_shard','{replica}');在集群中使用隐式参数创建数据库:
CREATE DATABASE r ON CLUSTER default ENGINE=Replicated;执行 DDL 查询:
CREATE TABLE r.rmt (n UInt64) ENGINE=ReplicatedMergeTree ORDER BY n;┌─────hosts────────────┬──status─┬─error─┬─num_hosts_remaining─┬─num_hosts_active─┐
│ shard1|replica1 │ 0 │ │ 2 │ 0 │
│ shard1|other_replica │ 0 │ │ 1 │ 0 │
│ other_shard|r1 │ 0 │ │ 0 │ 0 │
└──────────────────────┴─────────┴───────┴─────────────────────┴──────────────────┘系统表如下:
SELECT cluster, shard_num, replica_num, host_name, host_address, port, is_local
FROM system.clusters WHERE cluster='r';┌─cluster─┬─shard_num─┬─replica_num─┬─host_name─┬─host_address─┬─port─┬─is_local─┐
│ r │ 1 │ 1 │ node3 │ 127.0.0.1 │ 9002 │ 0 │
│ r │ 2 │ 1 │ node2 │ 127.0.0.1 │ 9001 │ 0 │
│ r │ 2 │ 2 │ node1 │ 127.0.0.1 │ 9000 │ 1 │
└─────────┴───────────┴─────────────┴───────────┴──────────────┴──────┴──────────┘创建分布式表并插入数据:
node2 :) CREATE TABLE r.d (n UInt64) ENGINE=Distributed('r','r','rmt', n % 2);
node3 :) INSERT INTO r.d SELECT * FROM numbers(10);
node1 :) SELECT materialize(hostName()) AS host, groupArray(n) FROM r.d GROUP BY host;┌─hosts─┬─groupArray(n)─┐
│ node3 │ [1,3,5,7,9] │
│ node2 │ [0,2,4,6,8] │
└───────┴───────────────┘在另一台主机上添加副本:
node4 :) CREATE DATABASE r ENGINE=Replicated('some/path/r','other_shard','r2');如果在 zoo_path 中使用了宏 {uuid},则可在另一台主机上添加副本:
node1 :) SELECT uuid FROM system.databases WHERE database='r';
node4 :) CREATE DATABASE r UUID '<uuid from previous query>' ENGINE=Replicated('some/path/{uuid}','other_shard','r2');集群配置如下所示:
┌─cluster─┬─shard_num─┬─replica_num─┬─host_name─┬─host_address─┬─port─┬─is_local─┐
│ r │ 1 │ 1 │ node3 │ 127.0.0.1 │ 9002 │ 0 │
│ r │ 1 │ 2 │ node4 │ 127.0.0.1 │ 9003 │ 0 │
│ r │ 2 │ 1 │ node2 │ 127.0.0.1 │ 9001 │ 0 │
│ r │ 2 │ 2 │ node1 │ 127.0.0.1 │ 9000 │ 1 │
└─────────┴───────────┴─────────────┴───────────┴──────────────┴──────┴──────────┘分布式表也会从新增的主机获取数据:
node2 :) SELECT materialize(hostName()) AS host, groupArray(n) FROM r.d GROUP BY host;┌─hosts─┬─groupArray(n)─┐
│ node2 │ [1,3,5,7,9] │
│ node4 │ [0,2,4,6,8] │
└───────┴───────────────┘设置
支持以下设置:
| 设置 | 默认值 | 说明 |
|---|---|---|
max_broken_tables_ratio |
1 | 如果状态过旧的表占所有表的比例超过该值,则不自动恢复副本 |
max_replication_lag_to_enqueue |
50 | 如果副本的复制延迟超过该值,则在尝试执行查询时抛出异常。必须大于 0;0 会因 BAD_ARGUMENTS 被拒绝 (最小值为 1) |
wait_entry_commited_timeout_sec |
3600 | 如果超过超时时间但发起方主机尚未执行该查询,副本将尝试取消查询 |
collection_name |
在服务器配置中定义的集合名称,其中包含集群身份验证所需的全部信息 | |
check_consistency |
true | 检查本地元数据与 Keeper 中元数据的一致性;如果不一致,则执行副本恢复 |
max_retries_before_automatic_recovery |
10 | 在将副本标记为丢失并从快照恢复之前,执行队列条目的最大尝试次数 (0 表示无限) |
allow_skipping_old_temporary_tables_ddls_of_refreshable_materialized_views |
false | 如果启用,在 Replicated 数据库中处理 DDL 时,如有可能,会跳过为可刷新materialized view 的临时表创建和交换 DDL |
logs_to_keep |
1000 | Replicated 数据库在 ZooKeeper 中默认保留的日志数量。 |
default_replica_path |
/clickhouse/databases/{uuid} |
数据库在 ZooKeeper 中的路径。创建数据库时如果省略参数,则使用该值。 |
default_replica_shard_name |
{shard} |
副本在数据库中的分片名称。创建数据库时如果省略参数,则使用该值。 |
default_replica_name |
{replica} |
副本在数据库中的名称。创建数据库时如果省略参数,则使用该值。 |
internal_replication |
false | 使用此 Replicated 数据库的集群创建的分布式表,是将数据发送到某一个副本 (内部复制表示集群中的副本会自行完成复制) ,还是发送到所有副本 (无内部复制表示分布式表会将插入的数据发送到所有副本) |
默认值可以在配置文件中覆盖
<clickhouse>
<database_replicated>
<max_broken_tables_ratio>0.75</max_broken_tables_ratio>
<max_replication_lag_to_enqueue>100</max_replication_lag_to_enqueue>
<wait_entry_commited_timeout_sec>1800</wait_entry_commited_timeout_sec>
<collection_name>postgres1</collection_name>
<check_consistency>false</check_consistency>
<max_retries_before_automatic_recovery>5</max_retries_before_automatic_recovery>
<default_replica_path>/clickhouse/databases/{uuid}</default_replica_path>
<default_replica_shard_name>{shard}</default_replica_shard_name>
<default_replica_name>{replica}</default_replica_name>
<internal_replication>false</internal_replication>
</database_replicated>
</clickhouse>