拉取数据_快到飞起的分析数据库ClickHouse笔记高可用

本文详细介绍了ClickHouse的副本与分片机制,包括ReplicatedMergeTree引擎、副本定义、数据结构及协同流程,以及分片的定义、配置、原理和查询流程。ClickHouse通过副本保证数据可用性和通过分片实现水平扩展,提供了高可用的解决方案。
摘要由CSDN通过智能技术生成

592040d00c7a663ce5da1548d616dc9f.png

ClickHouse通过副本保证数据的可用,通过分片来实现数据水平扩展和性能提升。高可用HA(High Availability)是设计分布式系统架构时必须考虑的因素之一 。一般指通过设计减少系统不能提供服务的时间,假设系统一直能够提供服务,我们说系统的可用性是100%。如果系统每运行100个时间单位,会有1个时间单位无法提供服务,我们说系统的可用性是99%。很多公司的高可用目标是4个9,即99.99%,换算成时间为系统一年的停机时间为8.76个小时。

1. ClickHouse副本

1.1 定义

        既可以将所有节点组成一个单一集群,也可以按照具体业务的诉求,把节点拆分为多个小的集群。在每个小的集群区域之间,它们的节点、分区和副本数量可以各不相同。

        同一张表中,数据不同的节点为分片,数据相同为副本。

  • 分片:数据的水平扩展。

  • 副本:数据的存储冗余,防止丢失。

1.2 ReplicatedMergeTree引擎

        ClickHouse中只有 MergeTree 系列里的表可支持副本。数据副本必须使用ReplicatedMergeTree复制表系列引擎,其才能应用副本的能力。

        在MergeTree引擎中,一个数据分区由开始创建到全部完成,会历经两类存储区域。一是内存,数据首先会被写入内存缓冲区;二是本地磁盘,数据接着会被写入tmp临时目录分区,待全部完成后再将临时目录重命名为正式分区。

        ReplicatedMergeTree在上述的基础之上增加了ZooKeeper的部分。其特点为:

(1)依赖ZooKeeper实现同步:执行INSERT和ALTER查询需要zk。但是select查询时并不需要zk。

(2)表级别的副本:在表中可配置副本的数量,以及副本在集群内的分布位置。

(3)多主架构(Multi Master):在任一副本上执行INSERT和ALTER查询,将会分发至每个副本。

(4)Block数据块:insert依据max_insert_block_size的大小(默认1048576行)将数据切分成若干个Block数据块。因此Block数据块是数据写入的基本单元,并且具有写入的原子性和唯一性。

  • 原子性:Block块内的数据要么全部写入成功,要么全部失败。

  • 唯一性:按当前Block数据块的数据顺序、数据行和数据大小等指标,计算Hash信息摘要并记录在案。防止重复写入。

        ZooKeeper的配置方式主要是在配置文件中做相应的修改:

(1)创建文件metrika.xml,设置zookeeper-servers标签。

(2)修改config.xml文件,增加标签,包含zk配置。被引用zk的zookeeper-servers标签。

(3)ck系统表提供了zk的代理表,使用sql即可查询zk信息,但必须指定path条件:

select * from system.zookeeper where path='/';

1.3 副本的定义方式

        定义副本方式:

engine=ReplicatedMergeTree('zk_path','replica_name')

        zk_path用于指定在ZooKeeper中创建的数据表的路径,路径名称是自定义。一般约定俗成为:/clickhouse/tables/{shard}/table_name

        /clickhouse/tables/是约定俗成的路径固定前缀,表示存放数据表的根路径。

        {shard}表示分片编号,通常用数值替代,例如01、02、03。一张数据表可以有多个分片,而每个分片都拥有自己的副本。

        table_name表示数据表的名称,为了方便维护,通常与物理表的名字相同(虽然ClickHouse并不强制要求路径中的表名称和物理表名相同)。

        replica_name的作用是定义在ZooKeeper中创建的副本名称,该名称是区分不同副本实例的唯一标识。其中一种约定成俗的命名方式是使用所在服务器的域名称。

*    参数使用

        对于zk_path而言,同一张数据表的同一个分片的不同副本,应该定义相同的路径;

        对于replica_name而言,同一张数据表的同一个分片的不同副本,应该定义不同的名称。

1.4 ReplicatedMergeTree数据结构

        启用ReplicatedMergeTree分布式表引擎后,将会在ZooKeeper内的生成相应的节点数据,其结构为:

  • 元数据:包含/metadata保存元数据、/columns保存列字段信息、/replicas副本名称

  • 判断标识:包含/leader_election主副本的选举工作、/blocks记录Block数据块的Hash信息摘要、/block_numbers、 /quorum记录quorum的数量,至少有quorum数量的副本写入成功后,整个写操作才算成功。

  • 操作日志:包含/log常规操作日志节点、/mutations MUTATION操作日志节点、/replicas/{replica_name}/*每个副本各自的节点下的一组监听节点

*    重要的节点:

  • /queue:任务队列节点,用于执行具体的操作任务

  • /log_pointer:log日志指针节点,记录了最后一次执行的log日志下标信息

  • /mutation_pointer:mutations日志指针节点,记录了最后一次执行的mutations日志名称

*    Entry日志对象的数据结构:

  • LogEntry:封装/log的子节点信息

  • MutationEntry:封装/mutations的子节点信息

1.5 副本协同流程

        副本协同的核心操作主要有INSERT、MERGE、MUTATION和ALTER四种,其对应数据写入、分区合并、数据修改和元数据修改。以下分别介绍其核心流程:

        INSERT的核心执行流程:写入数据的节点负责发送Log日志,通知其他副本下载写入的数据。

(1)创建第一个副本实例:

  • 根据zk_path初始化所有的ZooKeeper节点,

  • 在/replicas/节点下注册自己的副本实例, 

  • 启动监听任务,监听/log日志节点,

  • 参与副本选举,选举出主副本,选举的方式是向/leader_election/插入子节点,第一个插入成功的副本就是主副本。

(2)创建第二个副本实例:

  • 执行流程同上

(3)向第一个副本实例写入数据:

  • 首先会在本地完成分区目录的写入,

  • 接着向/blocks节点写入该数据分区的block_id,

  • 根据block_id去重,重复数据将会被忽略。

  • 如果设置了insert_quorum参数(默认为0),并且insert_quorum>=2,则节点会进一步监控已完成写入操作的副本个数

(4)由第一个副本实例推送Log日志

  • 由执行了INSERT的副本向/log节点推送操作日志,其余所有副本都会基于Log日志以相同的顺序执行命令。

(5)第二个副本实例拉取Log日志

  • 插入副本推送日志后,其余副本会一直监听/log节点变化,会触发日志的拉取任务并更新log_pointer,将其指向最新日志下标。

  • 拉取了LogEntry,放入任务队列。(可能会同时收到多个LogEntry,因此采用队列方式处理)

(6)第二个副本实例向其他副本发起下载请求

  • 基于/queue队列开始执行任务。当看到type类型为get的时候,即意味着其他副本成功写入了数据,需要本地同步数据。

  • 选择一个远端的其他副本作为数据的下载来源,

  • 选择算法:从/replicas节点拿到所有的副本节点,遍历这些副本,选取其中最大的log_pointer下标,并且/queue子节点数量最少。(数据更完整和负载更小)

  • 下载请求默认5次,max_fetch_partition_retries_count参数控制

(7)第一个副本实例响应数据下载

(8)第二个副本实例下载数据并完成本地写入。整个写入流程结束。

        MERGE的核心执行流程:无论在哪被触发,都会首先被转交至主副本,再由主副本负责合并计划的制定、消息日志的推送以及对日志接收情况的监控

  • 副本执行OPTIMIZE后,会创建远程连接,尝试与主副本通信

  • 主副本接收通信。

  • 由主副本制定MERGE计划并推送Log日志,并判断哪些分区需要被合并。

  • 各个副本分别拉取Log日志。

        MUTATION的核心执行流程:无论MUTATION操作从哪个副本发起,首先都会由主副本进行响应

  • 对ReplicatedMergeTree执行ALTER DELETE或者ALTER UPDATE操作

  • 推送MUTATION日志。

  • 所有副本实例各自监听MUTATION日志。

  • 由主副本实例响应MUTATION日志并推送Log日志。

  • 各个副本实例分别拉取Log日志。

  • 各个副本实例分别在本地执行MUTATION。

        ALTER的核心执行流程:谁执行谁负责的原则

  • 执行副本修改zk中共享元数据。

  • 其余副本监听zk共享元数据变更并各自执行本地修改。

  • 执行副本确认所有副本完成修改。

2. ClickHouse分片

2.1 定义

        ClickHouse中的每个服务节点都可称为一个shard(分片)。

Distributed表引擎自身不存储任何数据,它能够作为分布式表的一层透明代理,在集群内部自动开展数据的写入、分发、查询、路由等工作。

2.2 集群的配置方式

        集群配置用shard代表分片、用replica代表副本。

        其语义为:

        (1)1分片,0副本

        (2)1分片,1副本

1.不包含副本的分片设置

        直接使用定义分片节点。

        选填参数:

  • weight分片权重默认为1

  • user为ClickHouse用户,默认为default

  • password为ClickHouse的用户密码,默认为空字符串

  • secure为SSL连接的端口,默认为9440

  • compression表示是否开启数据压缩功能,默认为true。

2.自定义分片与副本设置

        下定义N个,则表示一个分片,n-1个副本。集群中replica数量的上限是由ClickHouse节点的数量决定的。

2.3 分布式DDL

        集群可以实现分布式DDL,分布式DDL语句在执行的过程中也需要借助ZooKeeper的协同能力,以实现日志分发。

        通过集群配置实现分布式ddl语句执行:

create/drop/rename/alter table on cluster cluster_name

create table new_table_local on cluster funnel(

id UInt64

)ENGINE=ReplicatedMergeTree('/clickhouse/tables/{shard}/new_table_1','{replica}')

order by id

        其中用{shard}和{replica}两个动态宏变量代替硬编码方式。

        查看节点的宏变量:

select * from system.macros

        config.xml配置中预先定义了分区01的宏变量:

01

funnel.com

2.4 分布式DDL流程

        分布式DDL的核心执行流程主要为3步。

  • 推送DDL日志,执行语句的副本创建DDLLogEntry日志并将日志推送到zk

  • 节点监听zk并拉取日志并执行

  • 确认执行进度(客户端会阻塞等待180秒,以期望所有host执行完毕。如果等待时间大于180秒,则会转入后台线程继续等待)

2.5 Distributed原理

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

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

        分布式表与本地表一致性检查:读时检查的机制,不一致将会在查询时报错。

1.定义形式

        分布式表定义为:

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

        其中的sharding_key意为分片键,选填参数。在数据写入的过程中,分布式表会依据分片键的规则,将数据分布到各个host节点的本地表。比如round()。

        建表时指定集群名,即使用ON CLUSTER分布式DDL,在集群的每个分片节点上,都会建一张Distributed表,可以从任意一端发起对所有分片的读、写请求。

        再使用ON CLUSTER分布式DDL建立对应的本地表即可在集群中各节点都创建。

2.查询的分类

        分布式查询分为两类:

        一类会作用于本地表的查询:对于INSERT和SELECT查询,Distributed将会以分布式的方式作用于local本地表。

        另一类只会影响Distributed自身,不会作用于本地表的查询:

Distributed支持部分元数据操作,包括CREATE、DROP、RENAME和ALTER,其中ALTER并不包括分区的操作(ATTACH PARTITION、REPLACE PARTITION等)。这些查询都只会修改Distributed表自身,并不会修改local本地表。

所以要彻底删除一张分布式表,则需要分别删除分布式表和本地表。

*    不支持的查询:

        Distributed表不支持任何MUTATION类型的操作,包括ALTERDELETE和ALTER UPDATE。

2.6 分片规则

        不声明分片键,分布式表只能包含一个分片,即只能映射一张本地表。分片键要求返回一个整型类型的取值,包括Int系列和UInt系列。

ENGINE=Distributed(cluster,database,table,userId)

        或返回整型的表达式:

round()、intHash64(userId)

        还有一些影响分片的参数:

  • 分片权重(weight)

    集群配置中设置,默认为1,尽量较小值,权重越大写入数据越多。

  • slot(槽)

    slot的数量等于所有分片的权重之和,slot按照权重元素的取值区间,与对应的分片形成映射关系。

  • 选择函数

        判断一行数据写入哪一个分片。

        因此,整个分片定位的流程为:

  • 找出slot的取值,公式:slot=shard_value % sum_weight(shard_value分片键取值,sum_weight权重和)

  • 基于slot值找到对应的数据分片

2.7 分布式写入流程

        分布式写入的核心流程主要有两种思路。一种思路是外部计算好数据写入到那些分片,并行写入。另一种思路为通过Distributed表引擎代理写入分片数据。

1.写入分片核心流程

  • 执行写入的节点计算数据所属分片,本地分片写入;(异步写直接返回,同步写等待其他节点写入完成,insert_distributed_sync参数默认false异步)

  • 其他节点数据写入临时目录,建立远端连接发送数据;

  • 向其他节点发送数据,节点接收并写入;

  • 执行节点确认写入完成。

2.副本复制数据的核心流程(两种方式)

        方式一:通过Distributed复制数据

        本地表不使用ReplicatedMergeTree表引擎,也能实现数据副本的功能。Distributed会同时负责分片和副本的数据写入工作,而副本数据的写入流程与分片逻辑相同。

        缺点:执行节点需要同时负责分片和副本的写入,可能造成写入的单点瓶颈。

        方式二:通过ReplicatedMergeTree复制数据

        集群的shard配置中增加internal_replication参数并将其设置为true。Distributed表在该shard中只会选择一个合适的replica并对其写入数据。如果使用ReplicatedMergeTree作为本地表的引擎,则在该shard内,多个replica副本之间的数据复制会交由ReplicatedMergeTree自己处理。

        shard中选择replica的算法:节点维护一个全局计数器errors_count,服务异常计数加1,拥有多个replica时,选择errors_count错误最少的那个。

2.8 分布式查询流程

        分布式查询只能通过Distributed表引擎。

1.多副本的路由规则

        如果一个shard拥有多个副本replica。Distributed表多副本查询时会依据负载均衡算法,由load_balancing参数控制,算法有:

  • random

    默认,选择errors_count错误数量最少的replica;相同则随机选一个。

  • nearest_hostname

    选择errors_count错误数量最少的replica;相同则选择集群配置中host名称与当前host最相似的一个。

  • in_order

    选择errors_count错误数量最少的replica;相同则选择集群配置中replica的定义顺序逐个选择。

  • first_or_random

    选择errors_count错误数量最少的replica;相同则选择集群配置中replica的定义顺序逐个选择,如果replica不可用则随机一个。

2.多分片查询的核心流程

        即前文所述的谁执行谁负责。执行节点按照分片数量将查询拆分成若干个针对本地表的子查询,然后向各个分片发起查询,最后再汇总各个分片的返回结果。

        相当于多个分片的UNION联合查询。先并行的查询各个分片数据,最后再合并返回结果。

        本地表查询和分布式查询各有优劣。单独使用本地表可能会导致局部数据不全的问题,而使用分布式表则可能会产生查询被放大的问题,即没有目标数据存放的节点也会被发起查询请求。

3.使用Global优化分布式子查询

        在IN查询子句使用分布式表的时候,查询请求会被放大N的平方倍,N等于分片节点的数量。

        可以使用GLOBAL IN或JOIN进行优化:

select uniq(id) from new_table_all where repo=100 and id global in (select id from new_table_all where repo=100);

        使用GLOBAL修饰符之后,用内存表临时保存了IN子句查询到的数据,并将其发送到远端分片节点(满足最外层的select分布式查询),到达数据共享的目的。

        缺点:内部网络的数据分发要求临时表不能太大,如果存在重复数据可以使用distinct去重。

*    系列完结。

参考资料:

[1] Yandex.clickhouse官方文档[EB/OL]:https://clickhouse.tech/docs/en/

[2] 朱凯.ClickHouse原理解析与应用实践[M]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值