利用复制表ReplicatedMergeTree实现ClickHouse高可用

  |   0 评论   |   6,675 浏览

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>

给分片配置多个副本

image.png

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

image.png

创建复制表

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;


读后有收获可以支付宝请作者喝咖啡