clickhouse副本同步与高可用功能验证,分布式表与集群配置,数据副本与复制表,ZooKeeper整合,创建复制表,副本同步机制,数据原子写入与去重,负载平衡策略,案例

1.分布式表与集群配置

  1. 分布式表基于Distributed引擎创建,在多个分片上运行分布式查询。
  2. 读取是自动并行化的,可使用远程服务器上的索引(如果有)。
  3. 数据在请求的本地服务器上尽可能地被部分处理。例如,对于GROUP BY查询,数据将在远程服务器 上聚合,聚合函数的中间状态将发送到请求服务器,然后数据将进一步聚合。

创建分布式表:

ENGINE = Distributed(cluster_name, db_name, table_name[, sharding_key[, policy_name]])

参数:

  • cluster_name:集群名称。
  • db_name:数据库名称,可使用常量表达式:currentDatabase()。
  • table_name: 各分片上的表名称。
  • sharding_key: (可选)分片的key,可设置为rand()。
  • policy_name: (可选)策略名称,用于存储异步发送的临时文件。

例如下面的/etc/metrika.xml的一部分内容:

<remote_servers>
    <logs>
	    <shard>
		    <weight>1</weight>
			<internal_replication>false</internal_replication>
			<replica>
			    <host>example01-01-1</host>
				<port>9000</port>
			</replica>
			<replica>
			    <host>example01-01-2</host>
				<port>9000</port>
			</replica>
		</shard>
		<shard>
		    <weight>2</weight>
			<internal_replication>false</internal_replication>
			<replica>
			    <host>example01-02-1</host>
				<port>9000</port>
			</replica>
			<replica>
			    <host>example01-02-2</host>
				<secure>1</secure>
				<port>9000</port>
			</replica>
		</shard>
	</logs>
</remote_servers>

这里定义了一个名为logs的集群名称,它有两个分片(shard)组成,每个分片包含两个副本(replica)。

分片是包含数据的不同服务器(要读取所有数据,必须访问所有分片)。

副本是存储复制数据的服务器(要读取所有数据,访问该分片上的任意一个副本上的数据即可)。

1.weight : 可选,写入数据时分片的权重,建议忽略该配置。
2.internal_repliacation : 可选,同一时刻是否只将数据写入其中一个副本。默认值:false(将数据写入所有副本),建议设置为true。写一个即可。避免重复写。
3.副本配置:配置每个Server的信息,必须参数:host和port,可选参数:user、password、secure和compression。
(1)、host : 远程服务器地址。支持IPv4和IPv6。也可指定域名,更改域名解析需 重启服务。
(2)、port : 消息传递的TCP端口。配置文件的tcp_port指定的端口,通常设置为 9000。
(3)、user : 用于连接到服务的用户名称。默认值:true。在users.xml文件中配置 了访问权限。
(4)、password:用于连接到远程服务的密码。默认值:空字符串。
(5)、secure : 使用ssl进行连接,通常还应该定义port=9440。
(6)、compression : 使用数据压缩。默认值:true。

2.数据副本与复制表

  • 只有MergeTree系列引擎支持数据副本,支持副本的引擎是在MergeTree引擎名称的前面加上前缀 Replicated。
  • 副本是表级别的而不是整个服务器级别的,因此服务器可以同时存储复制表和非复制表。
  • 副本不依赖于分片,每个分片都有自己独立的副本。

副本表如:

  • ReplicatedMergeTree
  • ReplicatedSummingMergeTree
  • ReplicatedReplacingMergeTree
  • ReplicatedAggregatingMergeTree
  • ReplicatedCollapsingMergeTree
  • ReplicatedVersionedCollapsingMergeTree
  • ReplicatedGraphiteMergeTree

3.ZooKeeper整合

ClickHouse使用Apache ZooKeeper来存储副本元信息, 在配置文件设置 zookeeper相关的参数。
ClickHouse在创建复制表的时候指定Zookeeper的目录,指定的目录会在建 表时自动创建。
如果ClickHouse的配置文件未配置ZooKeeper, 则无法创建复制表, 并且 任何存量的复制表都将是只读的。
对本地复制表的查询,不会使用ZooKeeper, 其查询速度和非复制表一样快。

本地复制表的数据插入,针对每个数据块(一个块最多有 max_insert_block_size = 1048576条记录),会通过几个事务将大约十个条目添加到Zookeeper。因此,与非复制表相比, 复制表的INSERT操作等 待时间稍长。

<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>

 

4.创建复制表

复制表的引擎要以Replicated为前缀,例如:ReplicatedMergeTree。

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);

引擎参数包含了变量,这些变量是在配置文件的”macros”部分配置的,例如:

<macros>
    <layer>05</layer>
    <shard>02</shard>
    <replica>clickhouse1</replica>
</macros>

Replicated*MergeTree引擎参数:
zoo_path : ZooKeeper中表的路径。
replica_name : ZooKeeper中的副本名称。

1.第一个参数ZooKeeper路径组成:
(1)、通用前缀:/clickhouse/tables/,建议复制表都使用类似这样的前缀。
(2)、分片标识符:{layer}-{shard},在本示例中,分片标识符有两部分组成,只要保证分片标识符能唯一标识一个分片即可。
(3)、ZooKeeper节点名称:table_name。节点名称最好与表名相同,节点名称在定义后不会更改,即使执行表的重命名操作。
2.第二个参数是副本名称,用于标识同一个分片的不同副本。副本名称只需要在每个shard中唯一即可。

上面的示例中,复制引擎的参数使用了变量替换。ClickHouse也支持使用显示的参数。在这种情况下,不能使用分布式的DDL查询(ON CLUSTER)。建议使用变量替换的方式传入参数,

降低出错概率。

在每个副本服务器上运行CREATE TABLE语句,如果该分片的表在其他节点已经创建且有数据,则该新副本自动同步其他副本的数据。

5.副本同步机制

  • 复制是多主异步的。
  • INSERT语句(以及ALTER)可在任意可用的服务器上执行。数据首先插入到本地的服务器 (即运行查询的服务器),然后数据被复制到其他服务器。
  • 由于复制是异步的,所以最近插入的数据出现在其他副本上会有一定的延迟。
  • 如果部分副本不可用,则在它们可用时写入数据。
  • 如果副本可用, 则等待的时间是通过网络传输压缩数据块所耗费的时间。
  • 默认情况下, INSERT操作只需等待一个副本写入成功后返回。如果仅将数据成功写入一个 副本,并且该副本的服务器不再存在, 则存储的数据将丢失。要启动来自多个副本的写入确 认机制,使用insert_quorum选项。

6.数据原子写入与去重

  • INSERT查询按照数据块插入数据,每个数据块最多max_insert_block_size(默认 max_insert_block_size = 1048576)条记录。换言之, 如果INSERT插入少于1048576条记 录,则插入操作是原子的。单个数据块的写入是原子的。
  • 数据块是去重的。 对于同一数据块的多次写入(相同大小的的数据块,包含相同的行以及相 同的顺序),该块仅写入一次。在出现网口故障等异常情况下, 客户端应用程序不知道数据 是否已将数据成功写入数据库,因此可以简单地重复执行INSERT查询。相同的数据发送到哪 个副本进行插入并不重要,INSERT是幂等的。数据去重可通过参数 insert_deduplicate控 制,默认为0(开启去重)。
  • 在复制过程中, 只有插入的源数据通过网络传输。进一步的数据转换(合并)会在所有副本 上以相同的方式进行处理。 这样可以最大限度减少网络带宽占用,这意味着当副本位于不同 的数据中心时,复制的效果也很好。
  • ClickHouse内部监控副本的数据同步,并能够在发生故障后恢复。故障转义是自动的(对于数据的微小差异)或半自动的(当数据的差异太大时,这可能表示配置错误)。
  • ClickHouse内部监控副本上的数据同步,并能够在发生故障后恢复。故障转移是自动的(对于数据的微小差异)或半自动的(当数据差异太大时,这可能表示配置错误)。

7.负载平衡策略

执行分布式查询时,首先计算分片的每个副本的错误数,然后将查询发送至最少错误的副本。如果没有错误或者错误数相同,则按如下的策略查询数据:
1.random(默认) : 将查询发送至任意一个副本。
2.nearest_hostname : 将查询发送至主机名最相似的副本。
3.in_order : 将查询按配置文件中的配置顺序发送至副本。
4.first_or_random : 选择第一个副本,如果第一个副本不可用,随机选择一个可用的副本。

在这里插入图片描述

设置策略的方式:

set load_balancing = 'first_or_random';

 

8.案例

1.在所有节点执行如下语句:

     创建本地复制表:

CREATE TABLE table_local on cluster mycluster
(
    EventDate DateTime,
    CounterID UInt32,
    UserID UInt32
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/table_local', '{replica}')
PARTITION BY toYYYYMM(EventDate)
ORDER BY (CounterID, EventDate, intHash32(UserID))
SAMPLE BY intHash32(UserID);

执行效果图:
在docker01-node节点执行的效果图如下:

 

在docker02-node上执行后的效果如下(提示已经存在了):

docker02-node :) CREATE TABLE table_local on cluster mycluster
:-] (
:-]     EventDate DateTime,
:-]     CounterID UInt32,
:-]     UserID UInt32
:-] ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/table_local', '{replica}')
:-] PARTITION BY toYYYYMM(EventDate)
:-] ORDER BY (CounterID, EventDate, intHash32(UserID))
:-] SAMPLE BY intHash32(UserID);

CREATE TABLE table_local ON CLUSTER mycluster
(
    `EventDate` DateTime,
    `CounterID` UInt32,
    `UserID` UInt32
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/table_local', '{replica}')
PARTITION BY toYYYYMM(EventDate)
ORDER BY (CounterID, EventDate, intHash32(UserID))
SAMPLE BY intHash32(UserID)

┌─host──────────┬─port─┬─status─┬─error─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬─num_hosts_remaining─┬─num_hosts_active─┐
│ 192.168.56.13 │ 9000 │     57 │ Code: 57, e.displayText() = DB::Exception: Table default.table_local already exists. (version 20.1.4.14 (official build)) │                   3 │                1 │
│ 192.168.56.11 │ 9000 │     57 │ Code: 57, e.displayText() = DB::Exception: Table default.table_local already exists. (version 20.1.4.14 (official build)) │                   2 │                1 │
│ 192.168.56.10 │ 9000 │     57 │ Code: 57, e.displayText() = DB::Exception: Table default.table_local already exists. (version 20.1.4.14 (official build)) │                   1 │                1 │
└───────────────┴──────┴────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴─────────────────────┴──────────────────┘
┌─host──────────┬─port─┬─status─┬─error─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬─num_hosts_remaining─┬─num_hosts_active─┐
│ 192.168.56.12 │ 9000 │     57 │ Code: 57, e.displayText() = DB::Exception: Table default.table_local already exists. (version 20.1.4.14 (official build)) │                   0 │                0 │
└───────────────┴──────┴────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴─────────────────────┴──────────────────┘
↓ Progress: 0.00 rows, 0.00 B (0.00 rows/s., 0.00 B/s.)                                                                                                                                0%Received exception from server (version 20.1.4):
Code: 57. DB::Exception: Received from 192.168.56.11:9000. DB::Exception: There was an error on [192.168.56.13:9000]: Code: 57, e.displayText() = DB::Exception: Table default.table_local already exists. (version 20.1.4.14 (official build)).

4 rows in set. Elapsed: 0.617 sec.

docker02-node :)

通过上面的案例可以知道,只要在一个节点上创建了副本表之后,在其它节点上也已经存在了。

创建分布式表(每个节点都要创建):

CREATE TABLE table_distributed as table_local ENGINE = Distributed(mycluster, default, table_local, rand());

2.验证副本的复制在clickhouse1上,对本地表操作。

在docker02-node上(docker02-node的分片副本节点)上,验证数据是否同步:

在docker03-node和docker04-node上执行(即shard2上)。发现查询不到结果,效果如下:

3.验证集群的功能

在任意节点查看分布式表的数据(都将出现下面的效果)。

在任意一个节点往分布式表里面插入5条数据:

insert into table_distributed values('2020-03-11 12:12:31', 21, 1);    docker04-node上执行
insert into table_distributed values('2020-03-12 12:12:32', 22, 2);    docker04-node上执行
insert into table_distributed values('2020-03-13 12:12:33', 23, 3);    docker03-node上执行
insert into table_distributed values('2020-03-14 12:12:34', 24, 4);    docker03-node上执行
insert into table_distributed values('2020-03-15 12:12:35', 25, 5);    docker02-node上执行

然后在任意一台机器上执行:

select * from table_distributed;

都可以看到:

docker02-node :) select * from table_distributed;

SELECT *
FROM table_distributed

┌───────────EventDate─┬─CounterID─┬─UserID─┐
│ 2020-03-11 12:12:33 │        22 │     37 │
└─────────────────────┴───────────┴────────┘
┌───────────EventDate─┬─CounterID─┬─UserID─┐
│ 2020-03-12 12:12:32 │        22 │      2 │
└─────────────────────┴───────────┴────────┘
┌───────────EventDate─┬─CounterID─┬─UserID─┐
│ 2020-03-11 12:12:31 │        21 │      1 │
└─────────────────────┴───────────┴────────┘
┌───────────EventDate─┬─CounterID─┬─UserID─┐
│ 2020-03-14 12:12:34 │        24 │      4 │
└─────────────────────┴───────────┴────────┘
┌───────────EventDate─┬─CounterID─┬─UserID─┐
│ 2020-03-13 12:12:33 │        23 │      3 │
└─────────────────────┴───────────┴────────┘
┌───────────EventDate─┬─CounterID─┬─UserID─┐
│ 2020-03-15 12:12:35 │        25 │      5 │
└─────────────────────┴───────────┴────────┘

6 rows in set. Elapsed: 0.007 sec.

docker02-node :)

然后,分别在两个分片的主机上查询本地表:
在docker01-node上(shard1)发现的效果是:

在docker03-node上(shard2)发现的效果是:

可以看到,使用分布式表插入数据,数据分散到不同分片(shard)的本地表。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值