1. 基本概念

OLAP(Online Analytical Processing:联机分析处理,是数据仓库的核心部分,支持复杂的分析操作,侧重决策支持,并且提供直观易懂的查询结果。例如动态报表系统。

特点:

  1. 对数据实时性要求不是很高
  2. 数据量大,因为 OLAP支持的是动态查询,所以用户也需要通过将很多数据的统计后才能得到想要知道的信息,例如时间序列分析等,所以处理的数据量很大
  3. 重点在于支持决策分析,所以查询一般是动态的,即允许用户随时提出查询的要求

  1. 行式数据库和列式数据库的区别与优缺点

特性

行式数据库

列式数据库

数据存储方式

按行存储数据(每行是一个数据单元)

按列存储数据(每列是一个数据单元)

查询性能

适合小范围,频繁的读写操作

适合大规模数据分析、查询特定列的数据

事物处理

高效支持事物,适合频繁的增删改查

不适合频繁更新或插入数据

数据压缩

较差

较好(相同数据类型的数据压缩效果更佳)

适用场景

OLTP(联机事物处理)

OLAP(联机分析处理)

IO性能

在全表查询时可能较差

对列查询优化,减少I/O操作

常见数据库

Mysql、Pg、Sql Server

Apache Hbase、CK

总结:列式数据库适用于OLAP场景,数据分析领域,不适合频繁的数据读写。

  1. Clickhouse 基础

Clickhouse 服务启动之后一般有如下端口比较常用:

8123

http default port,web查询和数据操作,适用于API调用

8443

http ssl/tls default port

9000

本地协议端口,由ck应用程序使用,例如 clickhouse-client、clickhouse-server,用于分布式查询的服务器间通信

9004

Mysql emulation port

9005

Pg emulation port

9009

用于低级数据访问的服务器间通信端口,用于数据交换、复制和服务器间通信

    1. 使用docker搭建clickhouse基础环境

// 拉取镜像

docker pull bitnami/clickhouse:23.3.2

// 启动 clickhosue 测试环境

docker run -itd --name ck-test -p 127.0.0.1:18123:8123 -e "CLICKHOUSE_ADMIN_PASSWORD=alben" bitnami/clickhouse:23.3.2

// 进入到clickhouse 容器

docker exec -it ck-test /bin/bash

// 使用 clickhouse-client 连接 clickhosue server

./clickhouse-client --port 9000 --password alben

    1. 常用命令

OPTIMIZE table {TABLE_NAME} final

用于触发表的手动合并操作,final 表示强制进行表的最终合并,即无论是否符合合并条件,都进行合并,常用于清理碎片或者整理表的状态

    1. 分区、分片、副本
  1. 分区:将数据表根据某个列的值划分为多个区域,每个分区可以独立存储和管理,分区通常根据时间和某些业务字段来进行划分。
    1. 查询时只扫描相关分区,可以方便的对过期数据进行删除或者存档操作

CREATE TABLE my_table

(

    event_date Date,

    user_id Int32,

    event_type String

) ENGINE = MergeTree()

PARTITION BY toYYYYMM(event_date)  -- 按月份分区

ORDER BY (event_date, user_id);

  1. 分片:将数据分布到不同的节点(服务器)上,每个节点只能存储数据的一部分,分片通常用于分布式集群中,通过将数据拆分到多个节点上,实现水平扩展。
    1. 允许将数据分布到多个服务器上,避免单个节点的存储瓶颈
    2. 通过将查询负载分配到多个节点上,分片可以减少单个节点的压力,提高查询吞吐量
    3. 扩展性:分片是实现水平扩展的关键,随着数据量的增加,可以通过添加更多的节点来增加处理能力

// my_table_local 是在每个节点上存储数据的本地表

// my_table_distributed 是分布式表,它将数据查询请求转发到集群中的不同节点

// my_cluster 集群名称、default 数据库名称、my_table_local 是在每个节点上的本地表名,rand() 是分片的选择方法

// 分布式表本身并不存储数据,而是将查询请求转发到对应的节点上

CREATE TABLE my_table_local

(

    event_date Date,

    user_id Int32,

    event_type String

) ENGINE = MergeTree()

PARTITION BY toYYYYMM(event_date)

ORDER BY (event_date, user_id);

CREATE TABLE my_table_distributed

(

    event_date Date,

    user_id Int32,

    event_type String

) ENGINE = Distributed('my_cluster', 'default', 'my_table_local', rand());

    1. 使用docker搭建clickhouse集群
  1. Clickhosue 集群有两种,旧版本的依赖于 zookeeper,最新版本可以使用 clickhosue keeper
      1. 以 zookeeper 搭建ck集群
        1. 搭建 zookeeper 集群

// 拉取镜像

docker pull bitnami/zookeeper3.7.1

// 启动容器并将 zookeeper 默认配置拷贝出来

// 在 zookeeper 的数据存储目录下创建 myid 文件,并分别向里面写入 1,2,3

// zookeeper 配置文件内容如下所示:

# The number of milliseconds of each tick

tickTime=2000

# The number of ticks that the initial 

# synchronization phase can take

initLimit=10

# The number of ticks that can pass between 

# sending a request and getting an acknowledgement

syncLimit=5

# the directory where the snapshot is stored.

# do not use /tmp for storage, /tmp here is just 

# example sakes.

dataDir=/bitnami/zookeeper/data

# the port at which the clients will connect

clientPort=32181

clientPortAddress=127.0.0.1

# the maximum number of client connections.

# increase this if you need to handle more clients

maxClientCnxns=60

#

# Be sure to read the maintenance section of the 

# administrator guide before turning on autopurge.

#

# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance

#

# The number of snapshots to retain in dataDir

autopurge.snapRetainCount=3

# Purge task interval in hours

# Set to "0" to disable auto purge feature

autopurge.purgeInterval=0

## Metrics Providers

#

# https://prometheus.io Metrics Exporter

#metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider

#metricsProvider.httpPort=7000

#metricsProvider.exportJvmInfo=true

preAllocSize=65536

snapCount=100000

maxCnxns=0

reconfigEnabled=false

quorumListenOnAllIPs=false

4lw.commands.whitelist=srvr, mntr

maxSessionTimeout=40000

admin.serverPort=8080

admin.enableServer=true

server.1=127.0.0.1:12881:13881

server.2=127.0.0.1:22881:23881

server.3=127.0.0.1:32881:33881

// 依次启动 zookeeper 的三个节点

docker run -itd -e "ALLOW_ANONYMOUS_LOGIN=yes" --name zk_03 --network=host    -v  /Users/hongguangyu/Desktop/clickhouse/node03/zookeeper/data:/bitnami/zookeeper/data    -v /Users/hongguangyu/Desktop/clickhouse/node03/zookeeper/conf:/opt/bitnami/zookeeper/conf  bitnami/zookeeper:3.7.1

// 启动完成之后,查看zookeeper 各个节点的状态如下所示

        1. 搭建 clickhouse 集群

搭建2分片2副本的集群,总共具有4个ck节点。

  1. 拉取clickhouse镜像

docker pull bitnami/clickhouse:23.3.2

  1. 将clickhouse 配置文件备份出来并移动到外面

修改 config.xml 文件,将末尾的以下配置删掉,因为目前clickhouse镜像服务启动都是使用主机模式,需要手动配置 tcp_port、http_port、interserver_http_port,如果未删除掉,启动的时候会有端口异常报错。

  1. 在 conf.d 文件夹下新建 metrics.xml 文件,并写入以下内容,需要注意,tcp_port、http_port、interserver_http_port、macros需要根据情况进行配置。

<clickhouse>

<listen_host>127.0.0.1</listen_host>

<tcp_port>19000</tcp_port>

<http_port>18123</http_port>

<interserver_http_port>19009</interserver_http_port>

    <remote_servers>

        <ck_cluster>

           <!-- 数据分片1  -->

            <shard>

                <internal_replication>true</internal_replication>

                <replica>

                    <user>default</user>

                    <password>alben</password>

                    <host>127.0.0.1</host>

                    <port>9000</port>

                </replica>

                <replica>

                    <user>default</user>

                    <password>alben</password>

                    <host>127.0.0.1</host>

                    <port>19000</port>

                </replica>

            </shard>

            <shard>

                <internal_replication>true</internal_replication>

                <replica>

                    <user>default</user>

                    <password>alben</password>

                    <host>127.0.0.1</host>

                    <port>29000</port>

                </replica>

                <replica>

                    <user>default</user>

                    <password>alben</password>

                    <host>127.0.0.1</host>

                    <port>39000</port>

                </replica>

    </shard>

        </ck_cluster>

    </remote_servers>

<!-- zookeeper 集群信息 -->

    <zookeeper>

        <node index="1">

            <host>127.0.0.1</host>

            <port>12181</port>

        </node>

        <node index="2">

            <host>127.0.0.1</host>

            <port>22181</port>

        </node>

        <node index="3">

            <host>127.0.0.1</host>

            <port>32181</port>

        </node>

    </zookeeper>

    

    <networks>

        <ip>::</ip>

    </networks>

    <clickhouse_compression>

        <case>

            <min_part_size>10000000000</min_part_size>

            <min_part_size_ratio>0.01</min_part_size_ratio>

            <method>lz4</method>

        </case>

    </clickhouse_compression>

<macros>

    <shard>1</shard>

    <replica>2</replica>

</macros> 

</clickhouse>

  1. 使用以下命令行分别启动4个clickhouse节点

docker run -itd --name ck_01 -e "CLICKHOUSE_ADMIN_PASSWORD=alben" --network=host -v /Users/hongguangyu/Desktop/clickhouse/node01/clickhouse/conf:/opt/bitnami/clickhouse/etc/ bitnami/clickhouse:23.3.2

  1. 分别进入4个clickhouse 节点并执行以下命令

docker exec -it ck_01 /bin/bash

cd /opt/bitnami/clickhouse/bin/

./clickhouse-client --password alben --port 39000

CREATE TABLE default.test_local (id UInt32, name String) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/test_local', '{replica}') ORDER BY id;

CREATE TABLE default.test_distributed AS default.test_local ENGINE = Distributed('my_cluster', default, test_local, rand());

  1. 分别在1、3 节点执行以下表插入命令,可以看到当查询 test_distributed 表时,可以查询到全量的数据,当查询 test_local 时,只能查询到本地的数据,且根据 clickhouse 的remote_servers 配置,2 节点 test_local 的数据和 1 节点test_local 的数据一致,3 节点 test_local 的数据和 4 节点 test_local 的数据一致

INSERT INTO default.test_local VALUES (5, 'Alice'), (6, 'Bob');

    1. 使用 clickhouse keeper 搭建clickhouse集群

相关配置以及搭建教程参考如下官方文档:

https://clickhouse.com/docs/en/architecture/replication#clickhouse-keeper-01-configuration

  1. 需要在2.4 操作的基础之上,准备 clickhouse keeper 的配置,其中标红的配置,在3台clickhouse节点上有区别,其他的配置3台节点上保持一致

<clickhouse>

    <logger>

        <level>trace</level>

        <log>/opt/log/clickhouse-keeper/clickhouse-keeper.log</log>

        <errorlog>/opt/log/clickhouse-keeper/clickhouse-keeper.err.log</errorlog>

        <size>1000M</size>

        <count>3</count>

    </logger>

    <listen_host>127.0.0.1</listen_host>

    <keeper_server>

        <tcp_port>9181</tcp_port>

        <server_id>1</server_id>

        <log_storage_path>/opt/lib/clickhouse/coordination/log</log_storage_path>

        <snapshot_storage_path>/opt/lib/clickhouse/coordination/snapshots</snapshot_storage_path>

        <coordination_settings>

            <operation_timeout_ms>10000</operation_timeout_ms>

            <session_timeout_ms>30000</session_timeout_ms>

            <raft_logs_level>trace</raft_logs_level>

        </coordination_settings>

        <raft_configuration>

            <server>

                <id>1</id>

                <hostname>127.0.0.1</hostname>

                <port>9234</port>

            </server>

            <server>

                <id>2</id>

                <hostname>127.0.0.1</hostname>

                <port>19234</port>

            </server>

            <server>

                <id>3</id>

                <hostname>127.0.0.1</hostname>

                <port>29234</port>

            </server>

        </raft_configuration>

    </keeper_server>

</clickhouse>

  1. Clickhouse Server 配置中的 zk 配置也需要更改

<zookeeper>

        <!-- where are the ZK nodes -->

        <node>

            <host>127.0.0.1</host>

            <port>9181</port>

        </node>

        <node>

            <host>127.0.0.1</host>

            <port>19181</port>

        </node>

        <node>

            <host>127.0.0.1</host>

            <port>29181</port>

        </node>

    </zookeeper>

  1. 分别启动以下三台ck容器

docker run -itd --name ck_01 -e "CLICKHOUSE_ADMIN_PASSWORD=alben" --network=host -v /Users/hongguangyu/Desktop/clickhouse/keepernode01/clickhouse/conf:/opt/bitnami/clickhouse/etc/ bitnami/clickhouse:23.3.2

  1. 在容器中,执行以下命令启动 clickhouse-keeper 

cd /opt/bitnami/clickhouse/bin/

./clickhouse-keeper -C /opt/bitnami/clickhouse/etc/keeper/keeper.xml --daemon

  1. CK 集群验证,在ck_01 节点上执行以下命令,可以发现ck_02 节点上也创建了 db1 数据库,table1表,并也在表中插入了以下数据:

CREATE DATABASE db2 ON CLUSTER ck_cluster

CREATE TABLE db2.table1 ON CLUSTER ck_cluster

(

    `id` UInt64,

    `column1` String

)

ENGINE = ReplicatedMergeTree

ORDER BY id

INSERT INTO db2.table1 (id, column1) VALUES (1, 'abc');

  1. 当停止多于两台节点的 clickhouse-keeper 后,再执行insert 命令报错如下:

  1. select * from system.clusters,可以发现集群状态如下所示:

  1. 常见的表引擎
    1. MergeTree 引擎

MergeTree 引擎是 Clickhouse 最常用的表引擎,特别适用于大规模的数据存储和分析。可以通过以下方式使用,是MergeTree家族的基础表引擎:

CREATE TABLE my_table

(

    id UInt64,

    timestamp DateTime,

    value Float64

)

ENGINE = MergeTree()

PARTITION BY toYYYYMM(timestamp)

ORDER BY (id, timestamp)

PRIMARY KEY id

TTL timestamp + INTERVAL 1 YEAR DELETE;

以上语句执行完毕之后,可以在 /bitnami/clickhouse/data/metadata/ 目录下发现一个test文件夹,内容如下:

数据存储

  1. 列式存储:数据按列存储,每一列单独存储在一个文件中,有利于压缩和查询性能
  2. 分区:数据可以根据指定的列(如日期)进行分区,每个分区可以独立管理和查询
  3. 排序:数据在写入时按指定的排序键进行排序,这有助于提高性能,特别是范围查询和聚合

数据写入

  1. 批量写入:数据通常以批量的方式写入,每个批次的数据会生成一个新的数据块
  2. 合并:数据块会定期进行合并,以减少数据文件的数量,提高查询性能,合并过程中会删除重复的数据,进行数据压缩

数据读取

  1. 索引:clickhouse 使用稀疏索引来加速查询,稀疏索引记录了每个数据块的最小值和最大值,查询时可以跳过不相关的数据块
  2. 预读:clickhouse 会预读数据块,以提高读取性能

数据保留

  1. TTL:可以设置数据保留策略,数据超过指定的时间后会被自动删除

创建 MergeTree 时有如下参数: 

ENGINE

引擎名称和参数

ORDER BY

排序键

PARTITION BY

分区键,大多数情况下,不需要分区键,如果确实需要分区,通常不需要比按月更细粒度的分区,按月分区,使用 toYYYYMM(data_column)

PRIMARY KEY

主键

TTL

数据过期时间

    1. ReplacingMergeTree

MergeTree虽然有主键,但是不能对相同主键的数据进行去重。Clickhouse提供了ReplacingMergeTree 引擎用于去重,能够在合并分区时删除重复的数据,删除时保留最新的数据。

测试方法:

// 创建 MergeTree 引擎的表

CREATE TABLE my_table

(

    `id` UInt64,

    `timestamp` DateTime,

    `value` Float64

)

ENGINE = MergeTree

PARTITION BY toYYYYMM(timestamp)

PRIMARY KEY id

ORDER BY (id, timestamp)

TTL timestamp + toIntervalYear(1

// 插入两条主键一致的数据

insert into my_table(id, timestamp, value) values(1, '2024-10-10 11:11:11', 1.2);

insert into my_table(id, timestamp, value) values(1, '2024-10-10 11:11:11', 1.2);

// 手动触发数据合并操作

OPTIMIZE table my_table final;

// 创建 ReplacingMergeTree 引擎的表

CREATE TABLE my_table1

(

    `id` UInt64,

    `timestamp` DateTime,

    `value` Float64

)

ENGINE = ReplacingMergeTree

PARTITION BY toYYYYMM(timestamp)

PRIMARY KEY id

ORDER BY (id, timestamp)

TTL timestamp + toIntervalYear(1)

// 插入两条主键一致的数据

insert into my_table1(id, timestamp, value) values(1, '2024-10-10 11:11:11', 1.2);

insert into my_table1(id, timestamp, value) values(1, '2024-10-10 11:11:11', 1.2);

// 手动触发数据合并操作

OPTIMIZE table my_table1 final;

    1. ReplicatedMergeTree

主要用于支持数据的高可用性和容错性,特别是在集群环境中。允许表在多个节点之间进行数据的复制,这样即使一个节点发生故障,其他节点上仍然有数据的副本,从而保证系统的高可用性。

  1. 每个 ReplicatedMergeTree 表都有一个或多个副本,副本会自动同步数据,当一个副本不可用时,ck 会从其他副本读取数据,保证查询不会中断。
    1. ReplicatedMergeTree 引擎本身并不直接通过表的定义来设置副本的数量,副本的数量是在集群部署时通过配置和集群架构来管理的

CREATE TABLE my_table

(

    event_date Date,

    user_id Int32,

    event_type String

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

PARTITION BY toYYYYMM(event_date)

ORDER BY (event_date, user_id);

  1. 在创建 ReplicatedMergeTree 时有两种方式:
    1. 第一种:在任意一个副本节点上执行以下命令,会在所有的副本上创建table1

CREATE TABLE db2.table1 ON CLUSTER ck_cluster

(

    `id` UInt64,

    `column1` String

)

ENGINE = ReplicatedMergeTree

ORDER BY id

    1. 第二种:在所有的副本节点上都执行以下命令

CREATE TABLE default.test_local (id UInt32, name String) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/test_local', '{replica}') ORDER BY id;

需要注意,以上两种方式创建的复制表有区别,第一种貌似并没有和insert_quorum配置强关联(3个节点的情况下,最少需要两个节点正常才能写入数据,2个节点的情况下,必须两个节点都正常才能写入数据,即使insert_quorum设置为1),第二个需要满足成功写入副本的数量大于 insert_quorum配置,即设置insert_quorum 为1时,3副本集群,一个副本正常也能正常写入。

    1. Distrubuted 

ReplicatedMergeTree 和 Distributed 表引擎相互结合可以组成集群级别的高可用性。

CREATE TABLE my_table_local

(

    event_date Date,

    user_id Int32,

    event_type String

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

PARTITION BY toYYYYMM(event_date)

ORDER BY (event_date, user_id);

CREATE TABLE my_table_distributed

(

    event_date Date,

    user_id Int32,

    event_type String

) ENGINE = Distributed('my_cluster', 'default', 'my_table_local', rand());

  1. 深入理解篇
    1. 副本数据复制原理

Clickhouse 中只有使用了 ReplicatedMergeTree 复制表系列引擎,才能应用副本的能力。

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

  1. 元数据:
    1. /metadata:保存元数据信息,包括主键、分区键、采样表达式
    2. /columns:保存列字段信息,包括列名称和数据类型
    3. /replicas:保存副本名称,对应设置参数中的 replica_name
  2. 判断标识:
    1. /leader_election:用于主副本的选举工作,主副本会主导 MERGE 和 MUTATION 操作(Alter Delete、Alter Update)。这些任务在主副本完成之后再借助zookeeper将消息事件分发至其他副本
    2. /blocks:记录 blocks 数据块的hash信息摘要,以及对应的partition_id。通过hash 摘要能够判断blocks数据块是否重复
    3. /quorum:记录 quorum 的数量,当至少有 quorum 数量的副本写入成功后,整个写操作才算成功
  3. 操作日志:
    1. /log:常规操作日志(insert、merge、drop partition)它是整个工作机制中最为重要的一环,保存了副本需要执行的任务指令,log 使用了 zk 的持久顺序节点,每条指令的名称以 log- 为前缀递增,每一个副本实例都会监听 /log 节点,当有新的指令加入时,它们会把指令加入副本各自的任务队列,并执行任务
    2. /replicas/{replica_name}:每个副本各自节点下的一组监听节点,用于指导副本在本地执行具体的任务指令:
  1. /queue:任务队列节点,用于执行具体的操作任务,当副本从/log 或者 /mutations 节点监听到操作指令时,会讲执行任务添加到该节点下,并基于队列执行
  2. /log_pointer:log 日志指针节点,记录了最后一次执行的log日志下标信息
  3. /mutation_pointer:mutation 日志指针节点,记录了最后一次执行的mutations日志名称,例如mutation_pointer

如果配置了 insert_quorum 参数,并且 insert_quorum >= 2 ,则 linux121 会进一步监控已完成写入操作的副本个数,只有当写入副本个数大于或等于 insert_quorum 时,整个写入操作才算成功。否则会报错:

需要注意 insert_quorum 配置在 users.xml 里面:

    1. 分片数据实现原理

参考文档:

  1. https://clickhouse.com/docs
  2. Clickhouse的数据副本协同原理详解(借助ZK实现)_clickhouse 副本多久同步数据-CSDN博客
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐