# 6. ClickHouse 副本集群

副本的目的主要是保障数据的高可用性,即使一台 ClickHouse 节点宕机,那么也可以从其他服务器获得相同的数据。ClickHouse 的副本集群需要用到其中的一种表引擎 ReplicatedMergeTree

副本写入流程如下图所示:

image-20220411215623889

副本虽然能够提高数据的可用性,降低丢失风险,但是每台服务器实际上必须容纳全量 数据,对数据的横向扩容没有解决。

要解决数据水平切分的问题,需要引入分片的概念。通过分片把一份完整的数据进行切分,不同的分片分布到不同的节点上,再通过 Distributed 表引擎把数据拼接起来一同使用。 Distributed 表引擎本身不存储数据,有点类似于 MyCat 之于 MySql,成为一种中间件,通过分布式逻辑表来写入、分发、路由来操作多台节点不同分片的分布式数据。

分片集群写入流程(3 分片 2 副本共 6 个节点):

image-20220412125636630

分片集群读取流程(3 分片 2 副本共 6 个节点):

image-20220412125704499

# 6.1 集群规划

  • 3 节点
    • 2 分片
      • 分片1 有 2 个副本
      • 分片 2 只有 1 个副本
机器1 机器2 机器3
os centos 7 centos 7 centos 7
ip 172.16.208.160 172.16.208.161 172.16.208.162
zookeeper 3.7.0 3.7.0 3.7.0
ClickHouse 21.7.3.14-2 21.7.3.14-2 21.7.3.14-2
作用 分片1_副本1 分片1_副本2 分片2_副本1
image-20220412134341979

# 6.2 部署 zookeeper 集群

# 6.3 修改 config.xml 配置文件

分别修改三台机器上 /etc/clickhouse-server/ 目录下的 config.xml 配置文件,修改 <remote_servers><zookeeper-services><macros> 标签的内容,在差不多 700 行的位置:

  • 172.16.208.160

    <!-- 分片集群信息 -->
    <remote_servers>
      	<!--此处省略 clickhouse 自带的测试代码 -->
        <go2ch_cluster> <!-- 集群名称--> 
            <shard> <!--集群的第一个分片-->
                <internal_replication>true</internal_replication>
                <replica><!--该分片的第一个副本-->
                    <host>172.16.208.160</host>
                    <port>9000</port> 
                </replica>  
                <replica><!--该分片的第二个副本-->
                     <host>172.16.208.161</host>
                    <port>9000</port>
                </replica>
            </shard>
            <shard> <!--集群的第二个分片--> 
                <internal_replication>true</internal_replication> 
                <replica> <!--该分片的第一个副本-->
                    <host>172.16.208.162</host>
                    <port>9000</port>
                </replica>
            </shard>
        </go2ch_cluster>
    </remote_servers>
    
    <!-- zk 集群信息 -->
    <zookeeper>
      <node>
        <host>172.16.208.160</host>
        <port>2181</port>
      </node>
      <node>
        <host>172.16.208.161</host>
        <port>2181</port>
      </node>
      <node>
        <host>172.16.208.162</host>
        <port>2181</port>
      </node>
    </zookeeper>
    
    <macros>
        <shard>01</shard> <!--不同机器放的分片数不一样--> 
        <replica>rep_go2ch_1_1</replica> <!--不同机器放的副本数不一样-->
    </macros>
    
  • 172.16.208.161

    <!-- 分片集群信息 -->
    <remote_servers>
      	<!--此处省略 clickhouse 自带的测试代码 -->
        <go2ch_cluster> <!-- 集群名称--> 
            <shard> <!--集群的第一个分片-->
                <internal_replication>true</internal_replication>
                <replica><!--该分片的第一个副本-->
                    <host>172.16.208.160</host>
                    <port>9000</port> 
                </replica>  
                <replica><!--该分片的第二个副本-->
                     <host>172.16.208.161</host>
                    <port>9000</port>
                </replica>
            </shard>
            <shard> <!--集群的第二个分片--> 
                <internal_replication>true</internal_replication> 
                <replica> <!--该分片的第一个副本-->
                    <host>172.16.208.162</host>
                    <port>9000</port>
                </replica>
            </shard>
        </go2ch_cluster>
    </remote_servers>
    
    <!-- zk 集群信息 -->
    <zookeeper>
      <node>
        <host>172.16.208.160</host>
        <port>2181</port>
      </node>
      <node>
        <host>172.16.208.161</host>
        <port>2181</port>
      </node>
      <node>
        <host>172.16.208.162</host>
        <port>2181</port>
      </node>
    </zookeeper>
    
    <macros>
        <shard>01</shard> <!--不同机器放的分片数不一样--> 
        <replica>rep_go2ch_1_2</replica> <!--不同机器放的副本数不一样-->
    </macros>
    
  • 172.16.208.162

    <!-- 分片集群信息 -->
    <remote_servers>
      	<!--此处省略 clickhouse 自带的测试代码 -->
        <go2ch_cluster> <!-- 集群名称--> 
            <shard> <!--集群的第一个分片-->
                <internal_replication>true</internal_replication>
                <replica><!--该分片的第一个副本-->
                    <host>172.16.208.160</host>
                    <port>9000</port> 
                </replica>  
                <replica><!--该分片的第二个副本-->
                     <host>172.16.208.161</host>
                    <port>9000</port>
                </replica>
            </shard>
            <shard> <!--集群的第二个分片--> 
                <internal_replication>true</internal_replication> 
                <replica> <!--该分片的第一个副本-->
                    <host>172.16.208.162</host>
                    <port>9000</port>
                </replica>
            </shard>
        </go2ch_cluster>
    </remote_servers>
    
    <!-- zk 集群信息 -->
    <zookeeper>
      <node>
        <host>172.16.208.160</host>
        <port>2181</port>
      </node>
      <node>
        <host>172.16.208.161</host>
        <port>2181</port>
      </node>
      <node>
        <host>172.16.208.162</host>
        <port>2181</port>
      </node>
    </zookeeper>
    
    <macros>
        <shard>02</shard> <!--不同机器放的分片数不一样--> 
        <replica>rep_go2ch_2_1</replica> <!--不同机器放的副本数不一样-->
    </macros>
    

# 6.4 测试

# 6.4.1 重启 ClickHouse

因为修改了配置文件,所以三台机器都需要重启 ClickHouse:

sudo clickhouse restart

# 6.4.2 查询集群状态

SELECT * FROM system.clusters;

可以看到我们刚刚创建的分片副本集群:

image-20220412134601411

# 6.4.3 建数据表

172.16.208.160 上执行建表语句:

  • 会自动同步到 172.16.208.161172.16.208.162
  • 集群名字要和配置文件中的一致
  • 分片和副本名称从配置文件的宏定义中获取

本示例的具体的建表 SQL 语句如下:

CREATE TABLE shard_example ON CLUSTER go2ch_cluster(
	id UInt32,
  sku_id String,
  total_amount Decimal(16,2),
  create_time Datetime
)
ENGINE=ReplicatedMergeTree('/clickhouse/tables/{shard}/shard_example', '{replica}')
PARTITION BY toYYYYMMDD(create_time)
PRIMARY KEY (id)
ORDER BY (id, sku_id);

ReplicatedMergeTree('zookeeper_name_configured_in_auxiliary_zookeepers:path', 'replica_name') 参数解释:

  • 第一个参数是分片信息在 zookeeper 的存储路径,一般按照:/clickhouse/tables/{shard}/{table_name} 的格式,如果只有一个分片就写 01 即可。
  • 第二个参数是副本名称,相同的分片副本名称不能相同

当前是在搭建分片副本集群,因为我们前面修改了 config.xml 配置,这里 ClickHouse 会自动去配置文件中读取 {shard}{replica} 的值,然后自动赋值到各个分片及副本上,为其同步建表。

执行结果如下:

CREATE TABLE shard_example ON CLUSTER go2ch_cluster
(
    `id` UInt32,
    `sku_id` String,
    `total_amount` Decimal(16, 2),
    `create_time` Datetime
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/shard_example', '{replica}')
PARTITION BY toYYYYMMDD(create_time)
PRIMARY KEY id
ORDER BY (id, sku_id)

Query id: 12b99e46-fc2b-4023-bdb9-7f13d3e93bde

┌─host───────────┬─port─┬─status─┬─error─┬─num_hosts_remaining─┬─num_hosts_active─┐
│ 172.16.208.161 │ 90000 │       │                   20 │
│ 172.16.208.162 │ 90000 │       │                   10 │
│ 172.16.208.160 │ 90000 │       │                   00 │
└────────────────┴──────┴────────┴───────┴─────────────────────┴──────────────────┘

3 rows in set. Elapsed: 0.570 sec. 

这个时候我们在 172.16.208.161172.16.208.162 上都可以看到 shard_example 表的:

localhost :) show tables;

SHOW TABLES

Query id: 5070ea08-479a-4f08-8376-8a2d610bcde6

┌─name───────────┐
│ shard_example  │
└────────────────┘

10 rows in set. Elapsed: 0.012 sec. 

# 6.4.4 创建分布式表

172.16.208.160 上创建分布式表:

CREATE TABLE shard_example_all ON CLUSTER go2ch_cluster(
  	id UInt32,
    sku_id String,
    total_amount Decimal(16,2),
    create_time Datetime
)
ENGINE=Distributed(go2ch_cluster, default, shard_example, hiveHash(sku_id));

ENGINE=Distributed(...) 参数含义:

  • 集群名称
  • 数据库名
  • 本地表名
  • 分片建:分片键必须是整型数字,所以用 hiveHash() 函数转换,也可以 rand()

执行结果:

CREATE TABLE shard_example_all ON CLUSTER go2ch_cluster
(
    `id` UInt32,
    `sku_id` String,
    `total_amount` Decimal(16, 2),
    `create_time` Datetime
)
ENGINE = Distributed(go2ch_cluster, default, shard_example, hiveHash(sku_id))

Query id: f1e9afaa-f24e-4f07-93c7-51e983588154

┌─host───────────┬─port─┬─status─┬─error─┬─num_hosts_remaining─┬─num_hosts_active─┐
│ 172.16.208.161 │ 90000 │       │                   20 │
│ 172.16.208.162 │ 90000 │       │                   10 │
│ 172.16.208.160 │ 90000 │       │                   00 │
└────────────────┴──────┴────────┴───────┴─────────────────────┴──────────────────┘

3 rows in set. Elapsed: 0.456 sec. 

# 6.4.5 插入数据

172.16.208.160 上执行 insert 语句,注意 insert 的 表要写分布式表的名称:

INSERT INTO shard_example_all VALUES
(101,'sku_001',1000.00,'2020-06-01 12:00:00'),
(102,'sku_002',2000.00,'2020-06-01 12:00:00'),
(103,'sku_004',2500.00,'2020-06-01 12:00:00'),
(104,'sku_002',2000.00,'2020-06-01 12:00:00'),
(105,'sku_003',600.00,'2020-06-02 12:00:00');

# 6.4.6 查询数据

  • SELECT * FROM shard_example_all;

    ┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
    │ 102 │ sku_002 │      2000.002020-06-01 12:00:00 │
    │ 103 │ sku_004 │      2500.002020-06-01 12:00:00 │
    │ 104 │ sku_002 │      2000.002020-06-01 12:00:00 │
    └─────┴─────────┴──────────────┴─────────────────────┘
    ┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
    │ 101 │ sku_001 │      1000.002020-06-01 12:00:00 │
    └─────┴─────────┴──────────────┴─────────────────────┘
    ┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
    │ 105 │ sku_003 │       600.002020-06-02 12:00:00 │
    └─────┴─────────┴──────────────┴─────────────────────┘
    
  • 172.16.208.160 上 SELECT * FROM shard_example;

    ┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
    │ 102 │ sku_002 │      2000.002020-06-01 12:00:00 │
    │ 103 │ sku_004 │      2500.002020-06-01 12:00:00 │
    │ 104 │ sku_002 │      2000.002020-06-01 12:00:00 │
    └─────┴─────────┴──────────────┴─────────────────────┘
    
  • 172.16.208.161 上 SELECT * FROM shard_example;

    ┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
    │ 102 │ sku_002 │      2000.002020-06-01 12:00:00 │
    │ 103 │ sku_004 │      2500.002020-06-01 12:00:00 │
    │ 104 │ sku_002 │      2000.002020-06-01 12:00:00 │
    └─────┴─────────┴──────────────┴─────────────────────┘
    
  • 172.16.208.162 上 SELECT * FROM shard_example;

    ┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
    │ 101 │ sku_001 │      1000.002020-06-01 12:00:00 │
    └─────┴─────────┴──────────────┴─────────────────────┘
    ┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
    │ 105 │ sku_003 │       600.002020-06-02 12:00:00 │
    └─────┴─────────┴──────────────┴─────────────────────┘
    

可以看到 160 和 161 同属同一分片,互为副本,162 单属另外一个分片。

# 6.5 常见问题

# 6.5.1 副本不生效

需要在 config.xml 配置文件中打开 <interserver_http_port><interserver_http_host> 标签,这里是用来副本间传数据的地址和端口,端口默认配好了是 9009,防火墙需要放开这个端口,地址需要配置下指定当前主机具体的地址,不能是127.0.0.1 ,localhost 之类的,不然别的主机读不到这台机。

<interserver_http_port>9009</interserver_http_port>
<interserver_http_host>192.168.x.x</interserver_http_host>
上次更新: 8/22/2022, 10:48:17 PM