利用复制表ReplicatedMergeTree实现ClickHouse高可用
- ClickHouse高可用的方案
- 一、子表使用MergeTree引擎,Insert写Distributed表
- 二、子表使用ReplicatedMergeTree引擎,Insert写子表
- 启用复制表
- 修改config.xml配置
- 配置ZooKeeper集群的地址
- 给分片配置多个副本
- 为每个分片配置macros
- 创建复制表
- 添加一点数据
- 查看一个副本服务器的本地表
- 登录副本2机器,查询
- 分布式DDL
ClickHouse高可用的方案
ClickHouse中为了实现分布式查询,一般使用Distributed表,其子表可以使用各种引擎,但最常用也是功能最强大的引擎就是*MergeTree系列了。在实际使用中通常有以下两种高可用的方案。
一、子表使用MergeTree引擎,Insert写Distributed表
在这种情况下,分布式表会跨服务器分发插入数据。 为了写入分布式表,必须要配置分片键(最后一个参数)。当然,如果只有一个分片,则写操作在没有分片键的情况下也能工作,因为这种情况下分片键没有意义,所有数据都将发送到一个分片。
通常将internal_replication参数设置为false,这样写操作会将数据写入所有副本以实现高可用。实质上,这意味着要分布式表本身来复制数据。这种方式不如使用复制表的好,因为不会检查副本的一致性,并且随着时间的推移,副本数据可能会有些不一样。
在这种方案的实际使用中,笔者多次遇到DistrDirMonitor导致的故障
阻塞
kernel: INFO: task DistrDirMonitor:56052 blocked for more than 120 seconds. kernel: "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
OomKill
kernel: Out of memory: Kill process 56074 (DistrDirMonitor) score 894 or sacrifice child kernel: Killed process 56074 (DistrDirMonitor) total-vm:450312764kB, anon-rss:174896660kB, file-rss:0kB, shmem-rss:0kB
二、子表使用ReplicatedMergeTree引擎,Insert写子表
可以将internal_replication参数设置为true,因为可以由ClickHouse来保证数据副本的一致性。你可以自已指定要将哪些数据写入哪些服务器,并直接在每个分片上执行写入,并且你可以使用任何分片方案。对于复杂业务特性的需求,这可能是非常重要的。官方推荐这种方案。
使用复制表并不影响效率
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
。数据插入的吞吐量(每秒的行数)可以跟不用复制的数据一样高。
启用复制表
修改config.xml配置
配置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>
给分片配置多个副本
<remote_servers> <cxy7_cluster> <shard> <!-- Optional. Shard weight when writing data. Default: 1. --> <weight>1</weight> <!-- Optional. Whether to write data to just one of the replicas. Default: false (write data to all replicas). --> <internal_replication>true</internal_replication> <replica> <host>192.168.4.2</host> <port>9000</port> </replica> <replica> <host>192.168.4.4</host> <port>9000</port> </replica> </shard> <shard> <weight>2</weight> <internal_replication>true</internal_replication> <replica> <host>192.168.4.3</host> <port>9000</port> </replica> <replica> <host>192.168.4.5</host> <port>9000</port> </replica> </shard> </cxy7_cluster> </remote_servers>
注意internal_replication应配置为true
为每个分片配置macros
<macros> <shard>2</shard> <replica>192.168.4.3</replica> </macros>
创建复制表
cxy7.com :) CREATE TABLE log_test ON CLUSTER cxy7_cluster :-] ( :-] ts DateTime, :-] uid String, :-] biz String :-] ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/log_test', :-] '{replica}') PARTITION BY toYYYYMMDD(ts) ORDER BY (ts) SETTINGS index_granularity = 8192; CREATE TABLE log_test ON CLUSTER cxy7_cluster ( `ts` DateTime, `uid` String, `biz` String ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/log_test', '{replica}') PARTITION BY toYYYYMMDD(ts) ORDER BY ts SETTINGS index_granularity = 8192 ┌─host────────┬─port─┬─status─┬─error─┬─num_hosts_remaining─┬─num_hosts_active─┐ │ 192.168.4.1 │ 9123 │ 0 │ │ 3 │ 2 │ │ 192.168.4.2 │ 9123 │ 0 │ │ 2 │ 2 │ └─────────────┴──────┴────────┴───────┴─────────────────────┴──────────────────┘ ┌─host────────┬─port─┬─status─┬─error─┬─num_hosts_remaining─┬─num_hosts_active─┐ │ 192.168.4.3 │ 9123 │ 0 │ │ 1 │ 1 │ └─────────────┴──────┴────────┴───────┴─────────────────────┴──────────────────┘ ┌─host────────┬─port─┬─status─┬─error─┬─num_hosts_remaining─┬─num_hosts_active─┐ │ 192.168.4.4 │ 9123 │ 0 │ │ 0 │ 0 │ └─────────────┴──────┴────────┴───────┴─────────────────────┴──────────────────┘ 4 rows in set. Elapsed: 0.205 sec.
添加一点数据
INSERT INTO log_test VALUES ('2019-06-07 20:01:01', 'a', 'show'); INSERT INTO log_test VALUES ('2019-06-07 20:01:02', 'b', 'show'); INSERT INTO log_test VALUES ('2019-06-07 20:01:03', 'a', 'click'); INSERT INTO log_test VALUES ('2019-06-08 20:01:04', 'c', 'show'); INSERT INTO log_test VALUES ('2019-06-08 20:01:05', 'c', 'click');
查看一个副本服务器的本地表
192.168.4.5 :) SELECT hostName(),* FROM log_test; SELECT hostName(), * FROM log_test ┌─hostName()──────────────────────┬──────────────────ts─┬─uid─┬─biz──┐ │ 192.168.4.5 │ 2019-06-08 20:01:04 │ c │ show │ └─────────────────────────────────┴─────────────────────┴─────┴──────┘ ┌─hostName()──────────────────────┬──────────────────ts─┬─uid─┬─biz───┐ │ 192.168.4.5 │ 2019-06-07 20:01:03 │ a │ click │ └─────────────────────────────────┴─────────────────────┴─────┴───────┘ ┌─hostName()──────────────────────┬──────────────────ts─┬─uid─┬─biz───┐ │ 192.168.4.5 │ 2019-06-08 20:01:05 │ c │ click │ └─────────────────────────────────┴─────────────────────┴─────┴───────┘ ┌─hostName()──────────────────────┬──────────────────ts─┬─uid─┬─biz──┐ │ 192.168.4.5 │ 2019-06-07 20:01:01 │ a │ show │ └─────────────────────────────────┴─────────────────────┴─────┴──────┘ ┌─hostName()──────────────────────┬──────────────────ts─┬─uid─┬─biz──┐ │ 192.168.4.5 │ 2019-06-07 20:01:02 │ b │ show │ └─────────────────────────────────┴─────────────────────┴─────┴──────┘ 5 rows in set. Elapsed: 0.002 sec.
登录副本2机器,查询
192.168.4.3 :) SELECT hostName(),* FROM log_test; SELECT hostName(), * FROM log_test ┌─hostName()──────────────────────┬──────────────────ts─┬─uid─┬─biz──┐ │ 192.168.4.3 │ 2019-06-08 20:01:04 │ c │ show │ └─────────────────────────────────┴─────────────────────┴─────┴──────┘ ┌─hostName()──────────────────────┬──────────────────ts─┬─uid─┬─biz───┐ │ 192.168.4.3 │ 2019-06-08 20:01:05 │ c │ click │ └─────────────────────────────────┴─────────────────────┴─────┴───────┘ ┌─hostName()──────────────────────┬──────────────────ts─┬─uid─┬─biz──┐ │ 192.168.4.3 │ 2019-06-07 20:01:01 │ a │ show │ └─────────────────────────────────┴─────────────────────┴─────┴──────┘ ┌─hostName()──────────────────────┬──────────────────ts─┬─uid─┬─biz──┐ │ 192.168.4.3 │ 2019-06-07 20:01:02 │ b │ show │ └─────────────────────────────────┴─────────────────────┴─────┴──────┘ ┌─hostName()──────────────────────┬──────────────────ts─┬─uid─┬─biz───┐ │ 192.168.4.3 │ 2019-06-07 20:01:03 │ a │ click │ └─────────────────────────────────┴─────────────────────┴─────┴───────┘ 5 rows in set. Elapsed: 0.003 sec.
两边一致!
分布式DDL
配置了Zookeeper之后,就可以执行分布式的DDL了,而不需要逐台登录服务器执行本地表的操作,简便且不容易出错
只需要在DDL的语句后面加上一个ON CLUSTER子句
例
修改字段类型
ALTER TABLE log_test ON CLUSTER cxy7_cluster MODIFY COLUMN biz LowCardinality(String) ;
下线分区
ALTER TABLE log_test ON CLUSTER cxy7_cluster DETACH PARTITION 20190608;
删除表
DROP TABLE log_test ON CLUSTER cxy7_cluster;
