91.【数据库】ClickHouse从入门到放弃-副本与分片- Distributed原理及分片规则

447 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第27天,点击查看活动详情 

文档参考:《ClickHouse原理解析与应用实践(数据库技术丛书)(朱凯)》

86.【数据库】ClickHouse从入门到放弃-副本与分片- 数据分片 - 掘金 (juejin.cn)

1.Distributed原理解析

Distributed表引擎是分布式表的代名词,它自身不存储任何数据,而是作为数据分片的透明代理,能够自动路由数据至集群中的各个节点,所以Distributed表引擎需要和其他数据表引擎一起协同工作,如图所示。

image.png

从实体表层面来看,一张分片表由两部分组成:

·本地表:通常以_local为后缀进行命名。本地表是承接数据的载体,可以使用非Distributed的任意表引擎,一张本地表对应了一个数据分片。

·分布式表:通常以_all为后缀进行命名。分布式表只能使用Distributed表引擎,它与本地表形成一对多的映射关系,日后将通过分布式表代理操作多张本地表。

对于分布式表与本地表之间表结构的一致性检查,Distributed表引擎采用了读时检查的机制,这意味着如果它们的表结构不兼容,只有在查询时才会抛出错误,而在创建表时并不会进行检查。不同ClickHouse节点上的本地表之间,使用不同的表引擎也是可行的,但是通常不建议这么做,保持它们的结构一致,有利于后期的维护并避免造成不可预计的错误。

1.1 定义形式

Distributed表引擎的定义形式如下所示:

ENGINE = Distributed(cluster, database, table [,sharding_key])

其中,各个参数的含义分别如下:

·cluster:集群名称,与集群配置中的自定义名称相对应。在对分布式表执行写入和查询的过程中,它会使用集群的配置信息来找到相应的host节点。

·database和table:分别对应数据库和表的名称,分布式表使用这组配置映射到本地表。

·sharding_key:分片键,选填参数。在数据写入的过程中,分布式表会依据分片键的规则,将数据分布到各个host节点的本地表。

现在用示例说明Distributed表的声明方式,建表语句如下所示:

CREATE TABLE test_shard_2_all ON CLUSTER sharding_simple (
    id UInt64
)ENGINE = Distributed(sharding_simple, default, test_shard_2_local,rand())

image.png

CREATE TABLE test_shard_2_local ON CLUSTER sharding_simple (
    id UInt64
)ENGINE = MergeTree()
ORDER BY id
PARTITION BY id

至此,拥有两个数据分片的分布式表test_shard_2就建好了。

1.2 查询的分类

Distributed表的查询操作可以分为如下几类:

·会作用于本地表的查询:对于INSERT和SELECT查询,Distributed将会以分布式的方式作用于local本地表。而对于这些查询的具体执行逻辑,将会在后续小节介绍。

·只会影响Distributed自身,不会作用于本地表的查询:Distributed支持部分元数据操作,包括CREATE、DROP、RENAME和ALTER,其中ALTER并不包括分区的操作(ATTACH PARTITION、REPLACE PARTITION等)。这些查询只会修改Distributed表自身,并不会修改local本地表。例如要彻底删除一张分布式表,则需要分别删除分布式表和本地表,示例如下。

--删除分布式表
DROP TABLE test_shard_2_all ON CLUSTER sharding_simple
--删除本地表
DROP TABLE test_shard_2_local ON CLUSTER sharding_simple

·不支持的查询:Distributed表不支持任何MUTATION类型的操作,包括ALTER DELETE和ALTER UPDATE。

1.3 分片规则

关于分片的规则这里将做进一步的展开说明。分片键要求返回一个整型类型的取值,包括Int系列和UInt系列。例如分片键可以是一个具体的整型列字段:

按照用户id的余数划分
Distributed(cluster, database, table ,userid)

也可以是一个返回整型的表达式:

--按照随机数划分
Distributed(cluster, database, table ,rand())
--按照用户id的散列值划分
Distributed(cluster, database, table , intHash64(userid))

如果不声明分片键,那么分布式表只能包含一个分片,这意味着只能映射一张本地表,否则,在写入数据时将会得到如下异常:

Method write is not supported by storage Distributed with more than one shard and no sharding key provided

如果一张分布式表只包含一个分片,那就意味着其失去了使用的意义了。所以虽然分片键是选填参数,但是通常都会按照业务规则进行设置。

那么数据具体是如何被划分的呢?想要讲清楚这部分逻辑,首先需要明确几个概念。

1.分片权重(weight)

在集群的配置中,有一项weight(分片权重)的设置:

<sharding_simple><!-- 自定义集群名称 -->
    <shard><!-- 分片 -->
        <weight>10</weight><!-- 分片权重 -->
            ……
    </shard>
    <shard>
        <weight>20</weight>
            ……
    </shard>

weight默认为1,虽然可以将它设置成任意整数,但官方建议应该尽可能设置成较小的值。分片权重会影响数据在分片中的倾斜程度,一个分片权重值越大,那么它被写入的数据就会越多。

2.slot(槽)

slot可以理解成许多小的水槽,如果把数据比作是水的话,那么数据之水会顺着这些水槽流进每个数据分片。slot的数量等于所有分片的权重之和,假设集群sharding_simple有两个Shard分片,第一个分片的weight为10,第二个分片的weight为20,那么slot的数量则等于30。slot按照权重元素的取值区间,与对应的分片形成映射关系。在这个示例中,如果slot值落在[0,10)区间,则对应第一个分片;如果slot值落在[10,20]区间,则对应第二个分片。

3.选择函数

选择函数用于判断一行待写入的数据应该被写入哪个分片,整个判断过程大致分成两个步骤:

(1)它会找出slot的取值,其计算公式如下:

slot = shard_value % sum_weight

其中,shard_value是分片键的取值;sum_weight是所有分片的权重之和;slot等于shard_value和sum_weight的余数。假设某一行数据的shard_value是10,sum_weight是30(两个分片,第一个分片权重为10,第二个分片权重为20),那么slot值等于10(10%30=10)。

(2)基于slot值找到对应的数据分片。当slot值等于10的时候,它属于[10,20)区间,所以这行数据会对应到第二个Shard分片。如图所示:

image.png