clickhouse数据复制表replication以及数据转换和恢复

一、概述

  分布式存储要保证高可用,就必须有数据冗余——即副本(replica)。ClickHouse依靠ReplicatedMergeTree引擎族与ZooKeeper实现了复制表机制,成为其高可用的基础。

      在实际操作中,为了最大化性能与稳定性,分片和副本几乎总是一同使用。

  仅 MergeTree 系列中的表支持复制,以下为正交图:

 

  复制工作在单个表的级别,而不是整个服务器。服务器可以同时存储复制表和非复制表。

  复制不依赖于分片。每个分片都有自己独立的复制。

  INSERT复制和查询的压缩数据ALTER

  CREATEDROPATTACH,DETACHRENAME查询在单个服务器上执行并且不会被复制:

  • CREATE TABLE查询在运行查询的服务器上创建一个新的可复制表。如果此表已存在于其他服务器上,则会添加一个新副本。
  • DROP TABLE查询将删除位于运行查询的服务器上的副本。
  • RENAME查询重命名副本之一上的表。换句话说,复制的表在不同的副本上可以有不同的名称。

  ClickHouse 使用Apache ZooKeeper存储副本元信息。使用 ZooKeeper 版本 3.4.5 或更高版本。

  要使用复制,请在zookeeper服务器配置部分设置参数。

  注意:不要忽视安全设置。ClickHouse 支持 ZooKeeper 安全子系统的digest ACL 方案。

  ZooKeeper集群地址设置示例:

复制代码
<zookeeper>
    <node>
        <host>example1</host>
        <port>2181</port>
    </node>
    <node>
        <host>example2</host>
        <port>2181</port>
    </node>
    <node>
        <host>example3</host>
        <port>2181</port>
    </node>
</zookeeper>
复制代码

  ClickHouse 还支持通过提供 ZooKeeper 集群名称和路径作为引擎参数将副本元信息存储在辅助 ZooKeeper 集群中。也就是说,它支持将不同表的元数据存储在不同的 ZooKeeper 集群中。

  设置辅助 ZooKeeper 集群地址的示例:

复制代码
<auxiliary_zookeepers>
    <zookeeper2>
        <node>
            <host>example_2_1</host>
            <port>2181</port>
        </node>
        <node>
            <host>example_2_2</host>
            <port>2181</port>
        </node>
        <node>
            <host>example_2_3</host>
            <port>2181</port>
        </node>
    </zookeeper2>
    <zookeeper3>
        <node>
            <host>example_3_1</host>
            <port>2181</port>
        </node>
    </zookeeper3>
</auxiliary_zookeepers>
复制代码

  要将表数据元存储在辅助 ZooKeeper 集群而不是默认 ZooKeeper 集群中,我们可以使用 SQL 使用 ReplicatedMergeTree 引擎创建表,如下所示:

CREATE TABLE table_name ( ... ) ENGINE = ReplicatedMergeTree('zookeeper_name_configured_in_auxiliary_zookeepers:path', 'replica_name') ...
可以指定任何现有的 ZooKeeper 集群,系统将使用其上的目录存储自己的数据(该目录在创建可复制表时指定)。

  如果配置文件中没有设置 ZooKeeper,则无法创建复制表,并且任何现有的复制表都将是只读的。

  ZooKeeper 不用于SELECT查询,因为复制不会影响性能,SELECT并且查询的运行速度与非复制表一样快。查询分布式复制表时,ClickHouse 行为由设置max_replica_delay_for_distributed_queriesfallback_to_stale_replicas_for_distributed_queries控制。

  对于每个INSERT查询,通过几个事务将大约十个条目添加到 ZooKeeper。(更准确地说,这是针对每个插入的数据块;INSERT 查询包含一个块或每行一个块。)与非复制表相比,max_insert_block_size = 1048576这会导致稍长的延迟。INSERT但是,如果按照建议以不超过INSERT每秒一个的批量插入数据,则不会产生任何问题。INSERTs用于协调一个 ZooKeeper 集群的整个 ClickHouse 集群每秒总共有几百个。数据插入的吞吐量(每秒的行数)与非复制数据的吞吐量一样高。

  对于非常大的集群,可以为不同的分片使用不同的 ZooKeeper 集群。但是,根据我们的经验,基于大约 300 台服务器的生产集群,这并没有被证明是必要的。

  复制是异步的和多主的。INSERT查询(以及ALTER)可以发送到任何可用的服务器。数据插入到运行查询的服务器上,然后复制到其他服务器。因为它是异步的,所以最近插入的数据会以一定的延迟出现在其他副本上。如果部分副本不可用,则在它们可用时写入数据。如果副本可用,则延迟是通过网络传输压缩数据块所需的时间。为复制表执行后台任务的线程数可以通过background_schedule_pool_size设置来设置。

  ReplicatedMergeTree引擎使用单独的线程池进行复制提取。池的大小受background_fetches_pool_size设置的限制,可以通过服务器重新启动进行调整。

  默认情况下,INSERT 查询等待确认仅从一个副本写入数据。如果数据仅成功写入一个副本,并且具有该副本的服务器不复存在,则存储的数据将丢失。要启用对来自多个副本的数据写入的确认,请使用该insert_quorum选项。

  每个数据块都是原子写入的。INSERT 查询被分成最多max_insert_block_size = 1048576行的块。换句话说,如果INSERT查询的行数少于 1048576,则自动生成。

  数据块被重复数据删除。对于同一个数据块的多次写入(相同大小的数据块包含相同顺序的相同行),该块只被写入一次。这样做的原因是当客户端应用程序不知道数据是否已写入数据库时​​发生网络故障,因此INSERT可以简单地重复查询。将相同数据发送到哪个副本 INSERT 并不重要。INSERTs是幂等的。重复数据删除参数由merge_tree服务器设置控制。

  在复制期间,只有要插入的源数据通过网络传输。进一步的数据转换(合并)以相同的方式在所有副本上进行协调和执行。这最大限度地减少了网络使用,这意味着当副本驻留在不同的数据中心时,复制工作得很好。(请注意,在不同的数据中心复制数据是复制的主要目标。)

  可以拥有相同数据的任意数量的副本。根据经验,一个相对可靠和方便的解决方案可以在生产中使用双重复制,每台服务器使用 RAID-5 或 RAID-6(在某些情况下使用 RAID-10)。

  系统监控副本上的数据同步性,并能够在发生故障后恢复。故障转移是自动的(对于数据的微小差异)或半自动的(当数据差异太大时,这可能表示配置错误)。

  数据插入流程图如下:

二、创建复制表

  Replicated前缀被添加到表引擎名称中例如:ReplicatedMergeTree

  Replicated*MergeTree 参数

  • zoo_path— ZooKeeper 中表的路径。
  • replica_name— ZooKeeper 中的副本名称。
  • other_parameters— 用于创建复制版本的引擎的参数,例如ReplacingMergeTree.

  例子:

复制代码
CREATE TABLE table_name
(
    EventDate DateTime,
    CounterID UInt32,
    UserID UInt32,
    ver UInt16
) ENGINE = ReplicatedReplacingMergeTree('/clickhouse/tables/{layer}-{shard}/table_name', '{replica}', ver)
PARTITION BY toYYYYMM(EventDate)
ORDER BY (CounterID, EventDate, intHash32(UserID))
SAMPLE BY intHash32(UserID);
复制代码

  不推荐使用的语法示例

CREATE TABLE table_name
(
    EventDate DateTime,
    CounterID UInt32,
    UserID UInt32
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/table_name', '{replica}', EventDate, intHash32(UserID), (CounterID, EventDate, intHash32(UserID), EventTime), 8192);

  如示例所示,这些参数可以包含大括号中的替换。替换值取自配置文件的部分。

  例子:

<macros>
    <layer>05</layer>
    <shard>02</shard>
    <replica>example05-02-1</replica>
</macros>

  ZooKeeper 中表的路径对于每个复制的表应该是唯一的。不同分片上的表应该有不同的路径。在这种情况下,路径由以下部分组成:

  /clickhouse/tables/是通用前缀。我们建议完全使用这个。

  {layer}-{shard}是分片标识符。在此示例中,它由两部分组成,因为示例集群使用双层分片。对于大多数任务,可以只保留 {shard} 替换,它将扩展为分片标识符。

  table_name是 ZooKeeper 中表的节点名称。使其与表名相同是个好主意。它是显式定义的,因为与表名相比,它在 RENAME 查询后不会更改。 提示:也可以在前面添加数据库名称table_name例如db_name.table_name

  这两个内置替换{database}{table}可以使用,它们分别扩展为表名和数据库名(除非这些宏在macros节中定义)。所以zookeeper路径可以指定为'/clickhouse/tables/{layer}-{shard}/{database}/{table}'使用这些内置替换时要小心表重命名。Zookeeper 中的路径无法更改,重命名表时,宏会扩展为不同的路径,表将引用 Zookeeper 中不存在的路径,并进入只读模式。

  副本名称标识同一张表的不同副本。可以为此使用服务器名称,如示例中所示。该名称只需要在每个分片中是唯一的。

  可以显式定义参数,而不是使用替换。这对于测试和配置小型集群可能很方便。ON CLUSTER但是,在这种情况下,不能使用分布式 DDL 查询 ( )。

  在处理大型集群时,我们建议使用替换,因为它们可以降低出错的可能性。

  Replicated可以在服务器配置文件中为表引擎指定默认参数。例如:

<default_replica_path>/clickhouse/tables/{shard}/{database}/{table}</default_replica_path>
<default_replica_name>{replica}</default_replica_name>

  在这种情况下,可以在创建表时省略参数:

CREATE TABLE table_name (
    x UInt32
) ENGINE = ReplicatedMergeTree
ORDER BY x;

  它相当于:

CREATE TABLE table_name (
    x UInt32
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/{database}/table_name', '{replica}')
ORDER BY x;

  CREATE TABLE在每个副本上运行查询。此查询创建一个新的复制表,或向现有表添加一个新副本。

  如果在表已经包含其他副本上的一些数据之后添加新副本,则在运行查询后数据将从其他副本复制到新副本。换句话说,新副本将自己与其他副本同步。

  要删除副本,请运行DROP TABLE但是,只有一个副本被删除 - 驻留在运行查询的服务器上的副本。

复制代码
# 复制表的创建,其中创建的几个重要监控线程
StorageReplicatedMergeTree::StorageReplicatedMergeTree
    queue // 表的队列,保存了需要处理的操作
    restarting_thread // 重连zk的线程,保证能够一直连接zk ReplicatedMergeTreeRestartingThread::run
    queue_updating_task // 队列更新任务,监控zk上的log,将它们加载到queue中
    mutations_updating_task // mutation更新任务,监控zk上的mutations节点
    merge_selecting_task // 选择part进行merge或者mutate的任务
    background_executor // 后台处理队列的线程
    
# 数据的写入
InterpreterFactory::get
InterpreterInsertQuery::execute
    StorageReplicatedMergeTree::write
        ReplicatedMergeTreeBlockOutputStream
    ReplicatedMergeTreeBlockOutputStream::write
        writeTempPart // 数据写入本地磁盘
        commitPart // 向zk提交插入的part信息
            renameTempPartAndAdd // 临时目录变为正式目录
            makeCreateRequest // 提交log_entry,zk上会创建log-xxx
            mergeSelectingTask // 主动触发merge任务

# 队列更新线程 queueUpdatingTask,也就是log entry的监控线程
ReplicatedMergeTreeQueue::pullLogsToQueue // 监控zk上是否有新的log产生
    makeCreateRequest // 将log的内容放到zk上的/queue节点下
    insertUnlocked // 在queue中新增当前entry
    
# 队列任务处理进程 scheduling_task
StorageReplicatedMergeTree::getDataProcessingJob
    selectQueueEntry
        queue.selectEntryToProcess // 从queue中选择可以执行的log entry
    processQueueEntry // 处理队列中的任务
        processEntry
            executeLogEntry // 如果当前entry指定的part在本节点上,则return,否则从副本fetch
                executeFetch // 找到一个有该part的副本就可以
                    fetchPart
                        fetcher.fetchPart // 通过http获取具体数据
            removeProcessedEntry // 删除zk上的队列
复制代码

 

三、故障后恢复

  如果服务器启动时 ZooKeeper 不可用,复制的表将切换到只读模式。系统会定期尝试连接到 ZooKeeper。

  如果 ZooKeeper 在 期间不可用INSERT,或者与 ZooKeeper 交互时发生错误,则会引发异常。

  连接到 ZooKeeper 后,系统会检查本地文件系统中的数据集是否与预期的数据集匹配(ZooKeeper 存储此信息)。如果存在轻微的不一致,系统会通过将数据与副本同步来解决它们。

  如果系统检测到损坏的数据部分(文件大小错误)或无法识别的部分(写入文件系统但未记录在 ZooKeeper 中的部分),则会将它们移动到detached子目录(它们不会被删除)。任何缺失的部分都会从副本中复制。

  请注意,ClickHouse 不会执行任何破坏性操作,例如自动删除大量数据。

  当服务器启动(或与 ZooKeeper 建立新会话)时,它只检查所有文件的数量和大小。如果文件大小匹配但字节在中间某处发生了更改,则不会立即检测到,但仅在尝试读取SELECT查询数据时才检测到。该查询引发关于不匹配校验和或压缩块大小的异常。在这种情况下,数据部分被添加到验证队列中,并在必要时从副本中复制。

  如果本地数据集与预期的数据差异太大,则会触发安全机制。服务器在日志中输入此内容并拒绝启动。这样做的原因是这种情况可能表明配置错误,例如,如果一个分片上的副本被意外配置为不同分片上的副本。但是,此机制的阈值设置得相当低,并且在正常故障恢复期间可能会发生这种情况。在这种情况下,通过“按下按钮”半自动恢复数据。

  要开始恢复,请在 ZooKeeper 中使用任何内容创建节点/path_to_table/replica_name/flags/force_restore_data,或者运行命令来恢复所有复制的表:

sudo -u clickhouse touch /var/lib/clickhouse/flags/force_restore_data

  然后重新启动服务器。启动时,服务器会删除这些标志并开始恢复。

四、数据完全丢失后恢复

  如果其中一台服务器上的所有数据和元数据都消失了,请按照以下步骤进行恢复:

  1. 在服务器上安装 ClickHouse。如果使用它们,请在包含分片标识符和副本的配置文件中正确定义替换。
  2. 如果有必须在服务器上手动复制的未复制表,请从副本(在目录中/var/lib/clickhouse/data/db_name/table_name/)复制它们的数据。
  3. 从副本复制位于的表定义/var/lib/clickhouse/metadata/如果在表定义中明确定义了分片或副本标识符,请更正它以使其对应于该副本。(或者,启动服务器并进行所有ATTACH TABLE应该在 .sql 文件中的查询/var/lib/clickhouse/metadata/。)
  4. 要开始恢复,请创建包含任何内容的 ZooKeeper 节点/path_to_table/replica_name/flags/force_restore_data,或运行命令以恢复所有复制的表:sudo -u clickhouse touch /var/lib/clickhouse/flags/force_restore_data

  然后启动服务器(重新启动,如果它已经在运行)。数据将从副本下载。

  另一种恢复选项是从 ZooKeeper ( ) 中删除有关丢失副本的信息,然后按照“创建复制表/path_to_table/replica_name”中所述再次创建副本

  恢复期间对网络带宽没有限制。如果要同时恢复多个副本,请记住这一点。

五、从 MergeTree 转换为 ReplicatedMergeTree 

  我们使用该术语MergeTree来指代 中的所有表引擎MergeTree family,与 for 相同ReplicatedMergeTree

  如果有一个MergeTree手动复制的表,则可以将其转换为复制表。如果已经在表中收集了大量数据MergeTree并且现在想要启用复制,则可能需要执行此操作。

  如果各个副本上的数据不同,请先同步它,或者删除除一个之外的所有副本上的此数据。

  重命名现有的 MergeTree 表,然后ReplicatedMergeTree使用旧名称创建一个表。将旧表中的数据移动到detached包含新表数据的目录内的子目录中 ( /var/lib/clickhouse/data/db_name/table_name/)。然后ALTER TABLE ATTACH PARTITION在其中一个副本上运行以将这些数据部分添加到工作集中。

六、从 ReplicatedMergeTree 转换为 MergeTree 

  创建一个具有不同名称的 MergeTree 表。将包含ReplicatedMergeTree表数据的目录中的所有数据移动到新表的数据目录中。然后删除ReplicatedMergeTree表并重新启动服务器。

  如果想在ReplicatedMergeTree不启动服务器的情况下删除表:

  • .sql删除元数据目录 ( /var/lib/clickhouse/metadata/)中的相应文件。
  • 删除 ZooKeeper ( /path_to_table/replica_name) 中的对应路径。

  之后,可以启动服务器,创建MergeTree表,将数据移动到其目录,然后重新启动服务器。

七、Zookeeper 集群中元数据丢失或损坏时的恢复 

  如果 ZooKeeper 中的数据丢失或损坏,可以通过将数据移动到如上所述的未复制表来保存数据。

八、ReplicatedMergeTree表删除更新数据等操作

1)drop 表相关操作

  在我们删除本地表和分布式表后,立即重建是没有问题的。唯一有问题的就是复制表,因为复制表需要在zookeeper上建立一个路径,存放相关数据。clickhouse默认的库引擎是原子数据库引擎,删除Atomic数据库中的表后,它不会立即删除,而是会在480秒后删除。由下面这个参数控制:

config.xml

<database_atomic_delay_before_drop_table_sec>480</database_atomic_delay_before_drop_table_sec>

  ①使用普通数据库而不是原子数据库。 create database … Engine=Ordinary。
  ②使用uniq ZK路径。{uuid}/clickhouse/tables/{layer}-{shard}-{uuid}/。
  ③减少database_atomic_delay_before_drop_table_sec = 0 & drop table … sync

2)alter update  delete 等操作

  对于非副本表,所有ALTER查询都是同步执行的。对于副本表,查询只是向ZooKeeper添加适当操作的指令,操作本身会尽快执行,可以通过replication_alter_partitions_sync设置控制执行等待,如果为0表示不等待,如果为1表示只等待自己执行(默认,即一个),如果为2表示需要等待所有节点完成。另外还可以通过replication_wait_for_inactive_replica_timeout参数设置等待时间,0表示不等待,负整数表示无限制等待,正整数表示等待秒数。如果 replication_alter_partitions_sync = 2,某些副本ALTER操作超过 replication_wait_for_inactive_replica_timeout 时间,则会抛出 UNFINISHED 异常。

3)truncate table操作

  会等待所有的副本处理结束,即使某些副本出现故障了,也还会继续等待,没有设置超时时间。truncate 操作没有回滚机制,在它分别遍历分区和副本时,如果存在多个分区和多个副本,且某个副本存在故障时,那么遍历到故障副本时,流程会一直卡住(无法满足stop_waiting条件,该副本的log一直无法处理)。但是,此时,之前遍历过的副本已经处理完了truncate操作。这就导致两点不一致:

  ①副本间的数据已经不一致了。
  ②已经遍历过的副本只删除了部分数据,一般是只有第一个分区的数据被删除了

整体流程如下:

复制代码
StorageReplicatedMergeTree::truncate // 入口
    // 如下遍历所有分区
    dropAllPartsInPartition // 停止merge并在zk上创建删除分区的log, 类型为LogEntry::DROP_RANGE
    waitForAllReplicasToProcessLogEntry // 等待所有副本执行完删除分区操作
        // 如下遍历所有副本,包括自己
        waitForTableReplicaToProcessLogEntry
            // 根据log_pointer和log编号判断是否已经处理完当前log-entry,没有处理完或者没有满足stop_waiting的条件,则一直等待
            // 等待queue节点下文件消失

// 将log节点下的内容拉取到queue节点下,并将entry放到变量queue中
ReplicatedMergeTreeQueue::pullLogsToQueue
    ...
    
// 处理队列的后台线程
StorageReplicatedMergeTree::getDataProcessingJob
    selectQueueEntry
    processQueueEntry // 处理队列中的任务
        processEntry
            executeLogEntry
                executeDropRange // 真正删除part
            removeProcessedEntry /
复制代码

 

如执行中未立即得到结果,建议等待,如果等待太久,只能进行节点重启,之后再检查数据是否删除成功。

 

posted @   渐逝的星光  阅读(6277)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示