优秀软件架构编程技巧

ClickHouse——数据副本

2023-02-26  本文已影响0人  小波同学

前言

ClickHouse集群的工作更多是针对逻辑层面,集群定义了多个节点的拓扑关系,这些节点在后续服务过程中很可能会协同工作,在执行层面的具体工作则交给了副本和分片来执行。

一、数据副本

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

官网地址如下:
https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replication/

如果在 MergeTree 表引擎前面加上 Replicated 前缀,就能够组合成一个新的引擎即 Replicated-mergeTree 复制表。


只有使用了 ReplicatedMergeTree 系列复制表引擎才能使用副本。ReplicatedMergeTree是 MergeTree 的派生表引擎,在 MergeTree 的基础上加入了分布式协同能力。


在 MergeTree 中,一个分区由开始创建到全部完成会经历两个存储区域

ReplicatedMergeTree 在上述基础上增加了 zookeeper 的部分,它会进一步在 zookeeper 内部创建一系列的监听节点,并以此实现多个实例之间的通信,在整个通信过程中 zookeeper 并不会设计数据的传输。

目前只有MergeTree系列里的表可以支持副本:

ClickHouse 的数据副本机制是表级别的,不是整个服务器级的,也就是说只针对表进行复制,一个数据库中可以同时包含复制表和非复制表。副本机制对于 select 查询是没有影响的,查询复制表和非复制表的速度是一样的。而写入数据时,ClickHouse 的集群没有主从之分,大家都是平等的。只不过配置了复制表后,Insert 以及 Alter 操作会同步到对应的副本机器中。对于复制表,每个 Insert 语句会往 Zookeeper 中写入十来条记录,相比非复制表,写 Zookeeper 会导致 Insert 语句的延迟时间略长。但是,在 ClickHouse 建议的每秒不超过一个 Insert 语句的执行频率下,这个延迟时间不会有太大的影响。

1.1 数据副本的特点

1.2 副本写入流程

在这其中,其实还有很多的实现细节。数据副本需要经过网络传输,所以副本中写入数据是有延迟的。默认情况下,ClickHouse 对于 Insert 语句,只会等待一个副本写入成功后就会返回。如果有多个副本的情况下,ClickHouse 是有可能丢失数据的。写入数据时,ClickHouse 只保证单个数据块的写入是原子的,而不能保证所有的数据写入是原子的。一个数据块的大小可以根据max_insert_block_size=1048576行进行分块。数据块写入时是会去重的,一个同样的 Insert 语句多次重复执行,数据库块只会执行一次。这是为了防止用户重复插入数据或者网络波动等原因造成的数据重发。这个去重机制只对应Replicated*MergeTree系列的表引擎,普通的MergeTree是不带这个去重功能的。

1.3 副本配置步骤

ClickHouse 的集群搭建依赖 Zookeeper。如果没有配置 Zookeeper 的话,依然可以创建复制表,但是这些复制表都将变成只读。另外,官方建议,不要在 ClickHouse 所在的服务器上运行 Zookeeper。因为 Zookeeper 对数据延迟非常敏感,而 ClickHouse 可能会占用所有可用的系统资源。

另外需要注意的是,当前版本要求 Zookeeper 版本最低为3.4.5。并且,官方对于 Zookeeper 的优化配置也提出了指导意见。具体如下:
https://clickhouse.com/docs/zh/operations/tips

服务器准备完成后,开始配置 ClickHouse。打开 ClickHouse 的配置文件/etc/clickhouse-server/config.xml,指定 Zookeeper 集群地址:

zookeeper 的配置方式

# 1. /etc/clickhouse-server/config.xml 修改如下配置

vim metrika.xml

<zookeeper>
  <node>
    <host>node2</host>
    <port>2181</port>
  </node>
</zookeeper>
-- 查询 zookeeper 中的数据,必须指定 path 条件
SELECT
    name,
    value,
    ctime
FROM system.zookeeper
WHERE path = '/'

┌─name───────┬─value─┬───────────────ctime─┐
│ zookeeper  │       │ 1970-01-01 08:00:00 │
│ clickhouse │       │ 2021-07-21 11:29:59 │
└────────────┴───────┴─────────────────────┘

副本的定义形式

CREATE TABLE [IF NOT EXISTS] [db_name.]table_name(
  name1 [type] [DEFAULT|MATERIALIZED|ALIAS expr],
  name2 [type] [DEFAULT|MATERIALIZED|ALIAS expr],
  ...
) ENGINE = ReplicatedMergeTree('zk_path','replica_name')
[PARTITION BY expr]
[ORDER BY expr]
[PRIMARY KEY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]

示例:

# 1 个分片,1 个副本
ReplicatedMergeTree('/clickhouse/tables/01/t_replicated','node3')
ReplicatedMergeTree('/clickhouse/tables/01/t_replicated','node4')

# 多 个分片,1 个副本
ReplicatedMergeTree('/clickhouse/tables/01/t_replicated','node3')
ReplicatedMergeTree('/clickhouse/tables/01/t_replicated','node4')

ReplicatedMergeTree('/clickhouse/tables/02/t_replicated','node3')
ReplicatedMergeTree('/clickhouse/tables/03/t_replicated','node4')

二、ReplicatedMergeTree 原理解析

2.1 数据结构

在 ReplicatedMergeTree 的核心逻辑大量使用量 zookeeper,以实现多个 ReplicatedMergeTree 副本实例之间的协同,包括副本选举、副本状态感知、操作分发日志、任务队列和 BlockID 去重判断等。

在执行 insert 、merge 和 mutation 操作的时候,都会涉及与 zookeeper 的通信。

在与 zookeeper通信时并不会涉及任何数据的传输,在查询数据的时候也不会访问 zookeeper,因此不必担心 zookeeper 承担太多压力。

2.1.1 zookeeper 内的节点结构

ReplicatedMergeTree 需要依靠 zookeeper 的事件监听机制实现各个副本之间的协同,当ReplicatedMergeTree 表创建过程中会以 zk_path 为根路径,在 zookeeper 中为这张表创建一组监听节点,按照作用不同,监听节点可以分为如下几类:

1、元数据

2、判断标识

3、日志操作

2.1.2 Entry日志对象的数据结构

在 zookeeper 中的两个重要的节点 log和 mutation 在clickhouse 中被抽象成了两个 Entry 对象。

LogEntry(封装 log 节点的信息)拥有以下核心属性

2.2 副本操作流程

2.2.1 insert 的执行流程

create table t_replicated(
  id UInt8,
  name String,
  date DateTime
)ENGINE=ReplicatedMergeTree('/clickhouse/tables/01/t_replicated','node3')
partition by toYYYYMM(date)
order by id

在创建过程中 ReplicatedMergeTree 会进行如下操作:

ls /clickhouse
get /clickhouse
ls /clickhouse/tables
get /clickhouse/tables
ls /clickhouse/tables/01
get /clickhouse/tables/01

get /clickhouse/tables/01/t_replicated
ls /clickhouse/tables/01/t_replicated


get /clickhouse/tables/01/t_replicated/metadata
ls /clickhouse/tables/01/t_replicated/metadata
get /clickhouse/tables/01/t_replicated/temp
ls /clickhouse/tables/01/t_replicated/temp
get /clickhouse/tables/01/t_replicated/mutations
ls /clickhouse/tables/01/t_replicated/mutations
get /clickhouse/tables/01/t_replicated/log
ls /clickhouse/tables/01/t_replicated/log
get /clickhouse/tables/01/t_replicated/leader_election
ls /clickhouse/tables/01/t_replicated/leader_election

ls /clickhouse/tables/01/t_replicated/leader_election/leader_election-0000000001
get /clickhouse/tables/01/t_replicated/leader_election/leader_election-0000000001



get /clickhouse/tables/01/t_replicated/columns
ls /clickhouse/tables/01/t_replicated/columns

get /clickhouse/tables/01/t_replicated/nonincrement_block_numbers
ls /clickhouse/tables/01/t_replicated/nonincrement_block_numbers

get /clickhouse/tables/01/t_replicated/replicas
ls /clickhouse/tables/01/t_replicated/replicas

get /clickhouse/tables/01/t_replicated/replicas/node3
ls /clickhouse/tables/01/t_replicated/replicas/node3

get /clickhouse/tables/01/t_replicated/replicas/node2/is_lost
ls /clickhouse/tables/01/t_replicated/replicas/node2/is_lost
get /clickhouse/tables/01/t_replicated/replicas/node2/is_active
ls /clickhouse/tables/01/t_replicated/replicas/node2/is_active
get /clickhouse/tables/01/t_replicated/replicas/node2/metadata
ls /clickhouse/tables/01/t_replicated/replicas/node2/metadata
get /clickhouse/tables/01/t_replicated/replicas/node2/mutation_pointer
ls /clickhouse/tables/01/t_replicated/replicas/node2/mutation_pointer
get /clickhouse/tables/01/t_replicated/replicas/node2/columns
ls /clickhouse/tables/01/t_replicated/replicas/node2/columns
get /clickhouse/tables/01/t_replicated/replicas/node2/max_processed_insert_time
ls /clickhouse/tables/01/t_replicated/replicas/node2/max_processed_insert_time
get /clickhouse/tables/01/t_replicated/replicas/node2/flags
ls /clickhouse/tables/01/t_replicated/replicas/node2/flags
get /clickhouse/tables/01/t_replicated/replicas/node2/log_pointer
ls /clickhouse/tables/01/t_replicated/replicas/node2/log_pointer
get /clickhouse/tables/01/t_replicated/replicas/node2/min_unprocessed_insert_time
ls /clickhouse/tables/01/t_replicated/replicas/node2/min_unprocessed_insert_time
get /clickhouse/tables/01/t_replicated/replicas/node2/host
ls /clickhouse/tables/01/t_replicated/replicas/node2/host
get /clickhouse/tables/01/t_replicated/replicas/node2/parts
ls /clickhouse/tables/01/t_replicated/replicas/node2/parts
get /clickhouse/tables/01/t_replicated/replicas/node2/queue
ls /clickhouse/tables/01/t_replicated/replicas/node2/queue
get /clickhouse/tables/01/t_replicated/replicas/node2/metadata_version
ls /clickhouse/tables/01/t_replicated/replicas/node2/metadata_version

get /clickhouse/tables/01/t_replicated/quorum
ls /clickhouse/tables/01/t_replicated/quorum
ls /clickhouse/tables/01/t_replicated/quorum/last_part
get /clickhouse/tables/01/t_replicated/quorum/last_part
ls /clickhouse/tables/01/t_replicated/quorum/parallel
get /clickhouse/tables/01/t_replicated/quorum/parallel
ls /clickhouse/tables/01/t_replicated/quorum/failed_parts
get /clickhouse/tables/01/t_replicated/quorum/failed_parts

ls /clickhouse/tables/01/t_replicated/block_numbers
get /clickhouse/tables/01/t_replicated/block_numbers
create table t_replicated(
  id UInt8,
  name String,
  date DateTime
)ENGINE=ReplicatedMergeTree('/clickhouse/tables/01/t_replicated','node2')
partition by toYYYYMM(date)
order by id;

在创建第二个副本实例的时候

insert into t_replicated values (1,'xiaoming','2021-07-21 18:01:49') 
# 向 /blicks 节点写入该数据分区的 block_id

db_merge.t_replicated (f43ce61c-6253-473c-a54d-4d9d6da57613) (Replicated OutputStream): Wrote block with ID '202107_8139788293933794752_9955392769311530712', 1 rows

# 向本地完成分区目录的写入

<Trace> db_merge.t_replicated (f43ce61c-6253-473c-a54d-4d9d6da57613): Renaming temporary part tmp_insert_202107_1_1_0 to 202107_0_0_0.
[zk: localhost:2181(CONNECTED) 2] ls /clickhouse/tables/01/t_replicated/blocks
[202107_8139788293933794752_9955392769311530712] # block_id 跟前面日志可以对应,该block_id 将作为后续去重操作的判断依据
[zk: localhost:2181(CONNECTED) 3] get /clickhouse/tables/01/t_replicated/blocks/202107_8139788293933794752_9955392769311530712
202107_0_0_0 # 保存的值为数据所在分区

如果设置了 insert_quorum 参数,且 insert_quorum>=2 则 node3会进一步监控已经写完操作的副本个数,只有写成功的副本数 大于等于 2 时,整个写操作才算成功。

[zk: localhost:2181(CONNECTED) 5] ls /clickhouse/tables/01/t_replicated/log
[log-0000000000]

[zk: localhost:2181(CONNECTED) 8] get /clickhouse/tables/01/t_replicated/log/log-0000000000
format version: 4
create_time: 2021-07-21 12:29:59
source replica: node3
block_id: 202107_8139788293933794752_9955392769311530712
get # 操作类型为 get
202107_0_0_0 # 分区
part_type: Compact
[zk: localhost:2181(CONNECTED) 25] get /clickhouse/tables/01/t_replicated/replicas/node2/log_pointer
1

在拉取了 logEntry 之后,并不会直接执行,而是将其转为任务对象放到队列,因为在复杂情况下,同一时间内可能收到多个 logEntry 使用队列形式消化任务是一种更为合理的方式。

2.2.2 merge 的执行流程

当 ReplicatedMergeTree 触发合并分区,即进入了这部分的流程。

无论 Merge 操作从哪个副本发起,其合并计划都会交给主副本来指定。整个流程从上至下按照时间顺序进行,大致分成 5 个步骤。

[zk: localhost:2181(CONNECTED) 33] ls /clickhouse/tables/01/t_replicated/log
[log-0000000001, log-0000000000]
[zk: localhost:2181(CONNECTED) 34] get /clickhouse/tables/01/t_replicated/log/log-0000000001
format version: 4
create_time: 2021-08-12 17:18:32
source replica: node2
block_id:
merge # 合并操作
202107_0_0_0 #合并的分区
into
202107_0_0_1 #合并后的分区
deduplicate: 0
part_type: Compact

2.2.3 mutation 的执行流程

当对 ReplicatedMergeTree 执行 alter delete 和 alter update 操作时,就会进行 mutation 的执行流程

与 merge 类似,无论从哪个节点发起 mutation 操作,都会由主副本进行相应。

alter table t_replicated delete where id = 1;

执行之后该副本会做两个操作,

[zk: localhost:2181(CONNECTED) 37] ls /clickhouse/tables/01/t_replicated/mutations
[0000000000]
[zk: localhost:2181(CONNECTED) 40] get /clickhouse/tables/01/t_replicated/mutations/0000000000
format version: 1
create time: 2021-07-21 17:56:27 # 创建时回见
source replica: node2
block numbers count: 1
202107    1
commands: DELETE WHERE id = 1 # 删除条件
alter version: -1

mutation 操作日志由 /clickhouse/tables/01/t_replicated/mutations 分发至各个副本

[zk: localhost:2181(CONNECTED) 42] ls /clickhouse/tables/01/t_replicated/log
[log-0000000003, log-0000000001, log-0000000002, log-0000000000]

[zk: localhost:2181(CONNECTED) 44] get /clickhouse/tables/01/t_replicated/log/log-0000000003
format version: 4
create_time: 2021-08-12 17:56:32
source replica: node2
block_id:
drop
202107_0_0_999999999_1

2.2.4 alter 执行流程

当 ReplicatedMergeTree 执行 alter 操作时进行元数据修改的时候,就会进入 alter 逻辑,例如增加,删除表字段等。

参考:
https://blog.csdn.net/qq_31557939/article/details/127019820

https://blog.csdn.net/weixin_44758876/article/details/123729984

https://www.cnblogs.com/wdh01/p/16872951.html

上一篇下一篇

猜你喜欢

热点阅读