一、Replicated*MergeTree复制表原理

复制表通过zookeeper实现,其实就是通过zookeeper进行统一命名服务,并不依赖config.xml的remote_servers配置

不过虽然不依赖,但我们配置的时候尽可能还是要把复制表配置的分片副本信息与config.xml的remote_servers里的分片副本信息一致。因为使用Distributed分布式表时,是不会使用zookeeper的信息,而是从config.xml的remote_servers获取分片以及副本信息(搞不懂为什么不和复制表一样,蛋疼)

 

1、复制表引擎

Replicated*MergeTree('shard_name','replicate_name')

如上,复制表要指定两个参数。

第一个参数:当前本地复制表实例所属的分片服务名称。

分片服务名是zookeeper上的目录名称,如果你知道zookeeper实现的统一命名服务,那就好理解了,类似dubbo的服务提供者在zookeeper注册的服务名。在dubbo中同一个服务名有多个实例提供相同的服务。服务调用者只需要通过服务名就能够获取到该服务的所有实例的信息。。

复制表这和dubbo差不多。。分片服务名就是在zookeeper上注册的服务提供者名称。多个复制表的该名称如果一样,那么这些复制表都属于同一个分片服务,只不过表示不同副本而已。同一个分片服务的不同副本之间数据会相互同步,保持一致,具体原理请看第2小节。

 

第二个参数:当前这张表所属的副本名称,一般用replica1、replica2表示。。如果第一个参数相同,当前第二个参数需要不同。。用以区分当前副本与其他副本。。

 

使用:

创建本地复制表模板如下:

CREATE TABLE test2.t1_local (`EventTime` Date, `id` UInt32) 
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/table_name', '{replica}')
PARTITION BY EventTime ORDER BY id

如果使用大括号表示会从配置文件中获取宏定义的变量。。通过<macros/>标签设置。。这自行百度,很简单。

我没使用大括号获取宏定义变量,而是写死,其实是一样的。后期改成宏变量好维护。如下:

CREATE TABLE test2.t1_local (`EventTime` Date, `id` UInt32) 
ENGINE = ReplicatedMergeTree('/snowball/tables/t1_local/shard_2', 'replica_2') 
PARTITION BY EventTime ORDER BY id

根据两个参数的结合,使用zookeeper的做统一命名服务。用于表示当前本地复制表是哪个分片服务的哪个副本。

当你create创建复制表时,就会把当前复制表的元数据信息注册到zookeeper上。执行上述create table的sql后,在zookeeper上可以找到相关数据:

然后每个shard下存储对应副本的元数据信息(这个分片的所有副本分别在哪个机器上之类的信息)。如下:

2、复制表同步原理

ClickHouse会把属于同一个分片的所有副本数据进行同步。

这就是根据创建复制表时注册到zookeeper上的信息实现的。

我们知道,其实一个副本就在一个ClickHouse实例上就是一张本地的表,只不过这张表引擎是复制表而已。

比如shard_1分片下有replicat1和replicat1两个副本。就是在两个在不同ClickHouse实例上的本地表,表引擎都是ReplicatedMergeTree。

但这两个复制表的shard分片是一样的(第一个参数是一样的),而第二个参数(副本名)不一样,所以这两个表互为副本。

当你往replicat1执行insert语句插入数据时,ReplicatedMergeTree复制表引擎就会启动同步机制。。我个人猜测同步机制原理是:

(1)当你一个本地复制表执行insert语句插入数据时

(2)该本地复制表引擎会通过对应的分片服务名在zookeeper查询到该分片的其他副本的元数据

(3)把数据insert到本地复制表之后,再把数据发送到该分片的其他副本上也进行insert。直到都insert成功为止。

(4)并且在任一副本的复制表上insert,都会通过上述步骤把数据同步到其他副本。

 

 

3、对复制表的使用理解

复制表机制就仅仅只是提供一种副本机制。属于同一个分片服务的不同复制表之间会相互同步数据。

但在查询某个副本时,这个副本宕机了还无法把这个查询自动切换到其他副本查询。需要重新去另外一个未宕机的副本实例上查询那个副本对应的本地复制表。虽然不同副本的数据是一样的,但对用户来说,某个副本宕机了还需要手动切换查询的副本实例。

如果要做到在某个副本宕机时自动切换到其他可用副本,那么就需要结合Distributed分布式表进行使用了。

 

二、Distributed分布式表

    分布式表其实是一种视图,

    分布式引擎,本身不存储数据,但可以在多个服务器上进行分布式查询。读是自动并行的。读取时,远程服务器表的索引(如果存在)会被使用。

1、首先要了解config.xml的remote_servers配置集群信息

因为分布式表是根据上面的配置信息获取到自定义的集群名、分片、副本等信息的。这些信息不会存到zookeeper。

分布式表根据上面配置的信息才能知道查找和插入分布式表时,实际插入的数据是往哪些分片、副本插入。。已经副本宕机时自动切换到新的可用副本。

我的测试环境配置如下

<remote_servers>
      <cluster_all>
            <shard>
                <internal_replication>true</internal_replication>
                <replica>
                    <default_database>test1</default_database>
                    <host>node1</host>
                    <port>9000</port>
                </replica>
                <replica>
                    <default_database>test2</default_database>
                    <host>node2</host>
                    <port>9000</port>
                </replica>
            </shard>
            <shard>
                <internal_replication>true</internal_replication>
                <replica>
                   <default_database>test1</default_database>
                    <host>node2</host>
                    <port>9000</port>
                </replica>
                <replica>
                    <default_database>test2</default_database>
                    <host>node3</host>
                    <port>9000</port>
                </replica>
            </shard>
            <shard>
                <internal_replication>true</internal_replication>
                <replica>
                    <default_database>test1</default_database>
                    <host>node3</host>
                    <port>9000</port>
                </replica>
                <replica>
                    <default_database>test2</default_database>
                    <host>node1</host>
                    <port>9000</port>
                </replica>
            </shard>
        </cluster_all>
</remote_server>

 

      Distributed(cluster_name, database, table, [sharding_key])

      参数解析:

      (1)cluster_name:集群名。

      (2)database:数据库名。可以为空,但为空时,查找某个分片的某个副本对应本地表时会使用上面配置中的default_databases的数据库名。

        为什么这样呢?因为机器不够。我这里是三个分片,每个分片两个副本。总共六个副本,那么就要创建六个本地表。但我只有三台机器。这就意味着每个机器上都得存在两个副本,要创建两个本地表,然后Distributed第(3)个参数指定的本地表名也只能指定一个名字,意味着创建的两个本地表名必须相同。。

在同一个ClickHouse实例的同一个数据库上,肯定无法做到create table两个同名的本地表。。所以在机器有限的情况下,有两种方式解决:

<1>一台机器一个ClickHouse实例,把这两个本地表创建在同一个ClickHouse实例的两个不同数据库上。

<2>一台机器两个ClickHouse实例,把这两个本地表创建在不同ClickHouse实例的同一个数据库中。

因为同一机器上搭建多个ClickHouse实例太复杂,所以选择第一种方式。

这又会出现另一个问题,Distributed引擎的第(2)个参数没办法传递多个数据库名称,这样分布式表就无法确定副本对应的本地表在哪个数据库。这个解决方式如下:

在配置文件中配置每个副本的default_databases,创建分布式表时Distributed引擎的第(2)个参数传入空串,这样当Distributed引擎查找副本时(查看下面ps),发现传入的数据库名称是空串空串,就会根据配置文件的副本的default_databases找到相应的数据库。

ps:Distributed引擎查找副本使用default_databases值的几种情况:

<1>正常查询副本时

<2>副本宕机,查找并切换其他副本

)    

 

如果机器足够,就没必要这样了。。。比如上面六个副本,那么用六台机器,每台机器一个ClickHouse实例,每个ClickHouse实例的同一个数据库中创建一个本地表,这样就行了。

      (3)table:本地表的表名。。去理解本地表和分布式表的关系

      (4)sharding_key:数据分片键。
 

 

2、internal_replication的配置

可以参考:http://cxy7.com/articles/2019/06/07/1559910377679.html

<1>如果底层是非复制表,那么这个值设为false(默认)。表示insert分布式表时,会在分片的所有副本都写入一份。

<2>如果底层是复制表,那么这个值配置为true。表示分布式表不会往所有副本都写入。。仅写入到一个副本。

 

internal_replication这个参数是控制写入数据到分布式表时,分布式表会控制这个写入是否的写入到所有副本中。。与复制表的同步是不一样的。为什么<2>中要设置为true,这就是为了避免和复制表的同步复制机制出现冲突,导致数据重复或者不一致。。

因为如果既是复制表、internal_replication又为false,那么写入到分布式表时会写入到同一分片的所有副本,而此时复制表的机制也会把不同副本之间的数据进行同步。。而且分布式表写入到所有副本并不是原子性的,也就是说,写入到所有副本时,写入某个副本失败了,那这个副本就写入失败了,不会矫正。。复制表的同步是会保证同步的。

 

 

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐