开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第27天,点击查看活动详情
文档参考:《ClickHouse原理解析与应用实践(数据库技术丛书)(朱凯)》
86.【数据库】ClickHouse从入门到放弃-副本与分片- 数据分片 - 掘金 (juejin.cn)
1.Distributed原理解析
Distributed表引擎是分布式表的代名词,它自身不存储任何数据,而是作为数据分片的透明代理,能够自动路由数据至集群中的各个节点,所以Distributed表引擎需要和其他数据表引擎一起协同工作,如图所示。
从实体表层面来看,一张分片表由两部分组成:
·本地表:通常以_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())
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分片。如图所示: