数据副本
只有 MergeTree 系列里的表可支持副本:
- ReplicatedMergeTree
- ReplicatedSummingMergeTree
- ReplicatedReplacingMergeTree
- ReplicatedAggregatingMergeTree
- ReplicatedCollapsingMergeTree
- ReplicatedVersionedCollapsingMergeTree
- ReplicatedGraphiteMergeTree
副本是表级别的,不是整个服务器级的。所以,服务器里可以同时有复制表和非复制表。
副本不依赖分片。每个分片有它自己的独立副本。
对于 INSERT
和 ALTER
语句操作数据的会在压缩的情况下被复制(更多信息,看 ALTER )。
而 CREATE
,DROP
,ATTACH
,DETACH
和 RENAME
语句只会在单个服务器上执行,不会被复制。
CREATE TABLE
在运行此语句的服务器上创建一个新的可复制表。如果此表已存在其他服务器上,则给该表添加新副本。DROP TABLE
删除运行此查询的服务器上的副本。RENAME
重命名一个副本。换句话说,可复制表不同的副本可以有不同的名称。
ClickHouse 使用 Apache ZooKeeper 存储副本的元信息。请使用 ZooKeeper 3.4.5 或更高版本。
要使用副本,需在 Zookeeper 服务器的配置部分设置相应参数。
不要忽视安全设置。 ClickHouse 支持 ZooKeeper 安全子系统中的 digest
ACL 方案 。
Zookeeper 集群地址的设置样例如下:
<zookeeper>
<node index="1">
<host>example1</host>
<port>2181</port>
</node>
<node index="2">
<host>example2</host>
<port>2181</port>
</node>
<node index="3">
<host>example3</host>
<port>2181</port>
</node>
</zookeeper>
通过以引擎参数的形式提供 ZooKeeper 集群的名称和路径,ClickHouse 同样支持将副本的元信息存储在备用 ZooKeeper 集群上。也就是说,支持将不同数据表的元数据存储在不同的 ZooKeeper 集群上。
设置备用 ZooKeeper 集群地址的样例如下:
<auxiliary_zookeepers>
<zookeeper2>
<node>
<host>example_2_1</host>
<port>2181</port>
</node>
<node>
<host>example_2_2</host>
<port>2181</port>
</node>
<node>
<host>example_2_3</host>
<port>2181</port>
</node>
</zookeeper2>
<zookeeper3>
<node>
<host>example_3_1</host>
<port>2181</port>
</node>
</zookeeper3>
</auxiliary_zookeepers>
为了将数据表的元数据存储到备用 ZooKeeper 集群而非默认 ZooKeeper 集群,我们可以通过如下 SQL 的方式创建使用 ReplicatedMergeTree 引擎的数据表:
CREATE TABLE table_name ( ... ) ENGINE = ReplicatedMergeTree('zookeeper_name_configured_in_auxiliary_zookeepers:path', 'replica_name') ...
你可以配置任何现有的 ZooKeeper 集群,系统会使用里面的目录来存取元数据(该目录在创建可复制表时指定)。
如果配置文件中没有设置 ZooKeeper ,则无法创建复制表,并且任何现有的复制表都将变为只读。
SELECT
查询并不需要借助 ZooKeeper ,副本并不影响 SELECT
的性能,查询复制表与非复制表速度是一样的。查询分布式表时,ClickHouse的处理方式可通过设置 max_replica_delay_for_distributed_queries 和 fallback_to_stale_replicas_for_distributed_queries 修改。
对于每个 INSERT
语句,会通过几个事务将十来个记录添加到 ZooKeeper。(确切地说,这是针对每个插入的数据块; 每个 INSERT 语句的每 max_insert_block_size = 1048576
行和最后剩余的都各算作一个块。)相比非复制表,写 zk 会导致 INSERT
的延迟略长一些。但只要你按照建议每秒不超过一个 INSERT
地批量插入数据,不会有任何问题。一个 ZooKeeper 集群能给整个 ClickHouse 集群支撑协调每秒 几百个 INSERT
。数据插入的吞吐量(每秒的行数)可以跟不用复制的数据一样高。
对于非常大的集群,你可以把不同的 ZooKeeper 集群用于不同的分片。然而,即使 Yandex.Metrica 集群(大约300台服务器)也证明还不需要这么做。
复制是多主异步。 INSERT
语句(以及 ALTER
)可以发给任意可用的服务器。数据会先插入到执行该语句的服务器上,然后被复制到其他服务器。由于它是异步的,在其他副本上最近插入的数据会有一些延迟。如果部分副本不可用,则数据在其可用时再写入。副本可用的情况下,则延迟时长是通过网络传输压缩数据块所需的时间。为复制表执行后台任务的线程数量,可以通过 background_schedule_pool_size 进行设置。
ReplicatedMergeTree
引擎采用一个独立的线程池进行复制拉取。线程池的大小通过 background_fetches_pool_size 进行限定,它可以在重启服务器时进行调整。
默认情况下,INSERT 语句仅等待一个副本写入成功后返回。如果数据只成功写入一个副本后该副本所在的服务器不再存在,则存储的数据会丢失。要启用数据写入多个副本才确认返回,使用 insert_quorum
选项。
单个数据块写入是原子的。 INSERT 的数据按每块最多 max_insert_block_size = 1048576
行进行分块,换句话说,如果 INSERT
插入的行少于 1048576,则该 INSERT 是原子的。
数据块会去重。对于被多次写的相同数据块(大小相同且具有相同顺序的相同行的数据块),该块仅会写入一次。这样设计的原因是万一在网络故障时客户端应用程序不知道数据是否成功写入DB,此时可以简单地重复 INSERT
。把相同的数据发送给多个副本 INSERT 并不会有问题。因为这些 INSERT
是完全相同的(会被去重)。去重参数参看服务器设置 merge_tree 。(注意:Replicated*MergeTree 才会去重,不需要 zookeeper 的不带 MergeTree 不会去重)
在复制期间,只有要插入的源数据通过网络传输。进一步的数据转换(合并)会在所有副本上以相同的方式进行处理执行。这样可以最大限度地减少网络使用,这意味着即使副本在不同的数据中心,数据同步也能工作良好。(能在不同数据中心中的同步数据是副本机制的主要目标。)
你可以给数据做任意多的副本。Yandex.Metrica 在生产中使用双副本。某一些情况下,给每台服务器都使用 RAID-5 或 RAID-6 和 RAID-10。是一种相对可靠和方便的解决方案。
系统会监视副本数据同步情况,并能在发生故障后恢复。故障转移是自动的(对于小的数据差异)或半自动的(当数据差异很大时,这可能意味是有配置错误)。
创建复制表
在表引擎名称上加上 Replicated
前缀。例如:ReplicatedMergeTree
。
Replicated*MergeTree 参数
zoo_path
— ZooKeeper 中该表的路径。replica_name
— ZooKeeper 中的该表的副本名称。other_parameters
— 关于引擎的一系列参数,这个引擎即是用来创建复制的引擎,例如,ReplacingMergeTree
。
示例:
CREATE TABLE table_name
(
EventDate DateTime,
CounterID UInt32,
UserID UInt32
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/table_name', '{replica}')
PARTITION BY toYYYYMM(EventDate)
ORDER BY (CounterID, EventDate, intHash32(UserID))
SAMPLE BY intHash32(UserID)
已弃用的建表语法示例:
CREATE TABLE table_name
(
EventDate DateTime,
CounterID UInt32,
UserID UInt32
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/table_name', '{replica}', EventDate, intHash32(UserID), (CounterID, EventDate, intHash32(UserID), EventTime), 8192)
如上例所示,这些参数可以包含宏替换的占位符,即大括号的部分。它们会被替换为配置文件里 ‘macros’ 那部分配置的值。示例:
<macros>
<layer>05</layer>
<shard>02</shard>
<replica>example05-02-1</replica>
</macros>
ZooKeeper 中该表的路径对每个可复制表都要是唯一的。不同分片上的表要有不同的路径。 这种情况下,路径包含下面这些部分:
/clickhouse/tables/
是公共前缀,我们推荐使用这个。
{layer}-{shard}
是分片标识部分。在此示例中,由于 Yandex.Metrica 集群使用了两级分片,所以它是由两部分组成的。但对于大多数情况来说,你只需保留 {shard}
占位符即可,它会替换展开为分片标识。
table_name
是该表在 ZooKeeper 中的名称。使其与 ClickHouse 中的表名相同比较好。 这里它被明确定义,跟 ClickHouse 表名不一样,它并不会被 RENAME 语句修改。
HINT:你也可以在 table_name
前面添加一个数据库名称。例如: db_name.table_name
。
两个内置的占位符 {database}
和 {table}
也可使用,它们可以展开成数据表名称和数据库名称(只有当这些宏指令在 macros
部分已经定义时才可以)。因此 ZooKeeper 路径可以指定为 '/clickhouse/tables/{layer}-{shard}/{database}/{table}'
。
当使用这些内置占位符时,请当心数据表重命名。 ZooKeeper 中的路径无法变更,而当数据表被重命名时,宏命令将展开为另一个路径,数据表将指向一个 ZooKeeper 上并不存在的路径,并因此转变为只读模式。
副本名称用于标识同一个表分片的不同副本。你可以使 用服务器名称,如上例所示。同个分片中不同副本的副本名称要唯一。
你也可以显式指定这些参数,而不是使用宏替换。对于测试和配置小型集群这可能会很方便。但是,这种情况下,则不能使用分布式 DDL 语句(ON CLUSTER
)。
使用大型集群时,我们建议使用宏替换,因为它可以降低出错的可能性。
你可以在服务器的配置文件中指定 Replicated
数据表引擎的默认参数。例如:
<default_replica_path>/clickhouse/tables/{shard}/{database}/{table}</default_replica_path>
<default_replica_name>{replica}</default_replica_name>
这样,你可以在建表时省略参数:
CREATE TABLE table_name (
x UInt32
) ENGINE = ReplicatedMergeTree
ORDER BY x;
它等价于:
CREATE TABLE table_name (
x UInt32
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/{database}/table_name', '{replica}')
ORDER BY x;
在每个副本服务器上运行 CREATE TABLE
查询。将创建新的复制表,或给现有表添加新副本。
如果其他副本上已包含了某些数据,在表上添加新副本,则在运行语句后,数据会从其他副本复制到新副本。换句话说,新副本会与其他副本同步。
要删除副本,使用 DROP TABLE
。但它只删除那个 – 位于运行该语句的服务器上的副本。