一文入门 | 性能凶悍的开源分析数据库ClickHouse

2022-06-06 10:29:03 浏览数 (1)

作者:王三岁

灵雀云后端工程师

clickhouse简介

ClickHouse是一个开源的,面向列的MPP架构数据分析数据库(大规模并行处理),由俄罗斯Yandex为OLAP和大数据用例创建。

ClickHouse全称是Click Stream,Data Warehouse,简称ClickHouse就是基于页面的点击事件流,面向数据仓库进行OLAP分析。

ClickHouse对实时查询处理的支持使其适用于需要亚秒级分析结果的应用程序。

ClickHouse的查询语言是SQL的一种方言,它支持强大的声明性查询功能,同时为最终用户提供熟悉度和较小的学习曲线。

ck的优势和不足

优势:

  1. 查询速度非常快,每台服务器每秒处理上亿或上百亿行的数据。
  2. 可以充分利用硬件多线程实现单个查询的分支处理性能超过每秒2tb
  3. 数据存储经过压缩,能够减少io
  4. 存储引擎强大
  5. 性能好: 一亿性能对比:

十亿性能对比

  1. 容错与高可靠: 支持分布式集群 支持多主机异步复制,可跨多数据中心部署 所有节点都相等避免出现单点故障 单个节点或整个数据中心停机时间不影响系统读写可用性 包括许多企业健全功能和针对认为错误的故障安全机制

不足:

  1. 不支持事务(这其实也是大部分OLAP数据库的缺点)
  2. 不擅长根据主键按行粒度查询(但是支持这种操作),它是按列存储,按列查询,故并不很适合按行查询的场景。
  3. 不擅长按行删除数据(但是支持这种操作),按行删除数据性能较低

ClickHouse的特性

  • 定位是分析性数据库,即OLAP,不是严格的关系型 OLAP(关系型的联机分析处理'基于大批量数据聚合统计分析,侧重于查询',和它一起比较的还有OLTP联机事务处理'基于事务的数据处理分析,侧重于事务',我们常见的ERP,CRM系统就属于OLTP)
  • 完整的DBMS(关系数据库) 具有database、table、row、DDL、DML、用户、权限控制、数据备份、数据恢复、分布式管理的功能、支持大规模并行运算、每个节点存储对应分区数据
  • 列式存储数据库 相同的列的数据存在一起,查询的时候只查需要的列,其他的列不关注,不做io,避免全表扫描,有更好的压缩比。
  • 在线实时查询
  • 不需要任何数据预处理
  • 支持批量更新
  • 拥有完善的SQl支持和函数
  • 支持高可用
  • 不依赖Hadoop复杂生态(像ES一样,开箱即用)

ClickHouse应用场景

ck比较适用于广告流量,web流量,app浏览,金融,电子商务,信息安全,电信网络游戏和物联网等领域

非常适合大数据分析的场景,可以用于电信数据的存储和统计,用户行为的数据记录和分析,信息安全日志分析,商业智能与广告网络价值的数据挖掘,以及网络游戏和物联网的数据分析和处理。

ClickHouse 作为一款高性能OLAP数据库,但并不适合事务性场景

clickhouse 的数据访问流程

  • server ck服务器实现了多个不同的接口: 1.用与外部客户端的http接口 2.用于数据传输拷贝的接口 3.用于本机的ClickHouse客户端接口,也作为在分布式查询执行中跨服务器通信的TCP接口
  • Parser分析器 负责创建AST对象(抽象语法树) 将一条SQL解析成AST语法树的形式,不同的SQL有不同的Parser分析器来解析
  • Intercepter解释器 负责解释AST对象,创建查询的执行通道
  • IStorage 存储接口 负责根据AST语句的要求返回指定列的原始数据 定义了DDL、read、write方法,负责数据定义查询和写入
  • Block ClickHouse内部的数据操作均是通过操作Block对象完成的。 Block对象包含了数据对象(Column)、数据类型(DataType)、列名组成的三元组,Block对象进一步抽象和封装了该三元组,使得通过Block对象可以完成一系列的数据操作
  • Column和Field Column提供了数据的读取能力 Column和Field是ck最基础的映射单元 ck按列存储数据,每列数据由一个Column对象表示,Field表示Column的一个单值 ( 也就是单列中的一行数据 )
  • DataType 数据类型由DataType负责,提供了序列化和反序列化,从Column和Field获取数据
  • Function ck提供两类函数(普通函数和聚合函数) 普通函数由IFunction接口定义 聚合函数有状态,由IAggregateFunction定义,以COUNT聚合函数为例,其AggregateFunctionCount的状态使用整型UInt64记录。状态支持序列化和反序列化,在分布式节点间可以进行传输,实现增量计算。

安装单点clickhouse

支持的平台:x86_64 、AArch64、Power9

代码语言:javascript复制
#官方预构建的二进制文件通常为 x86_64 编译并利用 SSE 4.2 指令集,需要进行检测
grep -q sse4_2 /proc/cpuinfo && echo "SSE 4.2 supported" || echo "SSE 4.2 not supported"
#来自 DEB 包的安装方式:
sudo apt-get install -y apt-transport-https ca-certificates dirmngr
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 8919F6BD2B48D754

echo "deb https://packages.clickhouse.com/deb stable main" | sudo tee 
    /etc/apt/sources.list.d/clickhouse.list
sudo apt-get update

sudo apt-get install -y clickhouse-server clickhouse-client
#启动 clickhouse-server 默认端口9000
sudo service clickhouse-server start
#启动 clickhouse-client链接server
clickhouse-client # or "clickhouse-client --password" if you've set up a password.


#可以用以下方式安装对应版本
sudo apt-get install clickhouse-server=21.8.5.7 clickhouse-client=21.8.5.7 clickhouse-common-static=21.8.5.7


# arm 系统需要使用wget获取对应版本包
 wget --progress=bar:force:noscroll "https://builds.dev.altinity.cloud/apt-repo/pool/main/clickhouse-client_21.8.5.7.dev_all.deb" -P /tmp/clickhouse_debs     
 wget --progress=bar:force:noscroll "https://builds.dev.altinity.cloud/apt-repo/pool/main/clickhouse-common-static_21.8.5.7.dev_arm64.deb" -P /tmp/clickhouse_debs     
 wget --progress=bar:force:noscroll "https://builds.dev.altinity.cloud/apt-repo/pool/main/clickhouse-server_21.8.5.7.dev_all.deb" -P /tmp/clickhouse_debs 

部署中可能用到的一些额外配置

  • 文件句柄数量配置:/etc/security/limits.d/clickhouse.conf 在linux下每一个tcp连接都要占一个文件描述符,如果达到上限,就会出现错误:“Socket/File:Can’t open so many files”。 如果配置太小可能会影响性能。
  • 定时任务配置:/etc/corn.d/clickhouse-server

与MySQL类似,启动clickhouse程序会启动一个服务进程,默认端口9000,一台主机上可启动多实例,需要使用不同端口。

服务启动后会默认创建system与default两个数据库。

ck的常见命令

代码语言:javascript复制
#基本和mysql差不多
SHOW databases;
SHOW tables;
USE [database];
DESC  [table];
SELECT * from [database].[table];
SHOW CREATE insert_select_testtable;
CREATE TABLE insert_select_testtable
(
    `a` Int8,
    `b` String,
    `c` Int8
)
ENGINE = MergeTree()
ORDER BY a ;
INSERT INTO insert_select_testtable (*) VALUES (1, 'a', 1) ;

ck的核心目录

  • 服务端配置文件目录: /etc/clickhouse-server
  • 数据目录(默认目录): /var/lib/clickhouse
    • ck的表数据存于/var/lib/clickhouse/data/[database]/[table]目录下
  • 表的元数据目录:/var/lib/clickhouse-server/metadata
  • 日志目录:/var/log/clickhouse-server
    • clickhouse-server.err.logclickhouse-server.log文件
  • 可执行文件目录:/user/bin
    • clickhouse 主程序的可执行文件
    • clickhouse-client 客户端可执行文件
    • clickhouse-server 服务端可执行文件
    • clickhouse-compressor 内置的解压缩工具

CK的索引

默认提供两种索引,稀疏索引和跳数索引,

根据索引所覆盖的行数产生索引标记来记录数据的区间信息

稀疏索引

按主键或者排序键进行排序后保存,默认粒度8192行。稀疏索引只需要使用少量的索引标记就可以记录大量数据的区间位置信息,这样索引文件足够小,常驻内存取用更快

跳数索引

由数据的聚合产生(Max/min等)数据经过间隔力度构建索引后,跳数索引会根据索引粒度对数据进行汇总(跳跃)。

跳数索引的粒度其实相当于对已构建的索引的合并。

ck的数据类型

不同版本的clickhouse 的数据类型有所区别,具体可以使用以下命令查询:

代码语言:javascript复制
select * from system.data_type_families;
# 数据类型的官方名称 是否区分大小写 别名 别名和官名用哪个都行
┌─name────────────────────────────┬─case_insensitive─┬─alias_to────┐
│ BLOB                            │                1 │ String      │
└─────────────────────────────────┴──────────────────┴─────────────┘

比较基础的数据类型

这里列举了一些和其他的数据库没有太大区别的数据类型。

UInt8, UInt16, UInt32, UInt64, UInt128, UInt256, Int8, Int16, Int32, Int64, Int128, Int256

uuid

Datetime64,Date32

Float32, Float64

Decimal(P, S), Decimal32(S), Decimal64(S), Decimal128(S), Decimal256(S)

string

注意 ck并没有提供bool数据类型,可以使用uint8取值限制为0 或者1

FixedString(N)

存储固定长度(按字节数计)字符串

  • 如果字符串包含的字节数少于字符串,则用空字节补充字符串N
  • Too large value for FixedString(N)如果字符串包含多于N字节,则引发异常。
  • 查询数据时,ClickHouse 不会删除字符串末尾的空字节。如果使用WHERE子句,则需要手动添加空字节以匹配该FixedString值 比如:当FixedString(2)的数据k值为a,使用where k = 'a'无法查到该叙述,需要使用where k ='a'
  • FixedString(N)的长度是个常量。仅由空字符组成的字符串,函数length返回值为N,而函数empty的返回值为1,即有长度的空字符串

LowCardinality(data_type)

把其它数据类型转变为字典编码类型。

它适合长度和定义域都可变,但总体基数不是特别大的列

低基数是一种修饰类型,即用法为LowCardinality(type)。其中type表示的原始类型可以是String、FixedString、Date、DateTime,以及除了Decimal之外的所有数值类型。一般来说 string比较常用。

它使用字典位置进行过滤、分组、加速某些函数(例如length()).

使用 LowCarditality 数据类型的效率依赖于数据的多样性。如果一个字典包含少于10000个不同的值,那么ClickHouse可以进行更高效的数据存储和处理。反之如果字典多于10000,效率会表现的更差。

当使用字符类型的时候,可以考虑使用 LowCardinality 代替enum。LowCardinality 通常更加灵活和高效。

代码语言:javascript复制
CREATE TABLE lc_t
(
    `id` UInt16,
    `strings` LowCardinality(String)
)
ENGINE = MergeTree()
ORDER BY id

array(T)

在同一数组内可以包含多种数据类型,但是数据类型必须要兼容;定义表的时候数组需要明确指定元素类型.

数组的最大大小限制为一百万个元素。

如果 ClickHouse 无法确定数据类型,则会生成异常。当尝试同时创建包含字符串和数字的数组时会发生这种情况。

举例:

代码语言:javascript复制
CREATE TABLE t_arr (`arr` Array(Array(Array(UInt32)))) ENGINE = MergeTree ORDER BY tuple();

INSERT INTO t_arr VALUES ([[[12, 13, 0, 1],[12]]]);

SELECT arr.size0, arr.size1, arr.size2 FROM t_arr;
#结果:
┌─arr.size0─┬─arr.size1─┬─arr.size2─┐
│         1 │ [2]       │ [[4,1]]   │
└───────────┴───────────┴───────────┘

Enum

由命名值组成的枚举类型。

命名值必须声明为'string' = integer对。ClickHouse 仅存储数字,但支持通过名称对值进行操作。

ClickHouse 自动选择Enum插入数据的类型。支持使用Enum8Enum16类型来确定存储的大小。

代码语言:javascript复制
#创建一个带有一个枚举 Enum8('hello' = 1, 'world' = 2) 类型的列:
CREATE TABLE t_enum
(
  x Enum8('hello' = 1, 'world' = 2)
)
ENGINE = TinyLog
#这个 `x` 列只能存储类型定义中列出的值:`'hello'`或`'world'`。如果尝试保存任何其他值,ClickHouse 抛出异常。

Tuple

组类型由1~n个元素组成,每个元素之间允许设置不同的数据类型,且彼此之间不要求兼容。元组支持类型推断,其推断依据以最小存储代价为原则。

代码语言:javascript复制
SELECT tuple(1,'a') AS x, toTypeName(x)
┌─x───────┬─toTypeName(tuple(1, 'a'))─┐
│ (1,'a') │ Tuple(UInt8, String)      │
└─────────┴───────────────────────────┘

SELECT tuple(1, NULL) AS x, toTypeName(x)
┌─x────────┬─toTypeName(tuple(1, NULL))──────┐
│ (1,NULL) │ Tuple(UInt8, Nullable(Nothing)) │
└──────────┴─────────────────────────────────┘

CREATE TABLE named_tuples (`a` Tuple(s String, i Int64)) ENGINE = Memory;
INSERT INTO named_tuples VALUES (('y', 10)), (('x',-10));
#使用名称查找元素
SELECT a.s FROM named_tuples;
┌─a.s─┐
│ y   │
│ x   │
└─────┘
#使用索引查找元素
SELECT a.2 FROM named_tuples;
┌─tupleElement(a, 2)─┐
│                 10 │
│                -10 │
└────────────────────┘

Nested(name1 Type1, Name2 Type2, …)

嵌套数据结构类似于嵌套表。一张数据表,可以定义任意多个嵌套类型字段,但每个字段的嵌套层级只支持一级,即嵌套表内不能继续使用嵌套类型。

代码语言:javascript复制
CREATE TABLE dept (
    name String,
    people Nested(
        id UInt8,
        name String
    )
) ENGINE = Memory;
INSERT INTO dept VALUES ('研发部',[001,002,003],['小李','小张','小刘']);
INSERT INTO dept VALUES ('测试部',[001,002],['小李','小张']);

SELECT name, dept.id, dept.name FROM nested_test;
┌─name──┬──people.id────┬─people.name──────────┐
│ 研发部 │ [001,002,003] │ ['小李','小张','小刘'] │
└───────┴───────────────┴──────────────────────┘
┌─name──┬─people.id─┬─people.name────┐
│ 测试部 │ [001,002] │ ['小李','小张'] │
└───────┴───────────┴────────────────┘

INSERT INTO dept VALUES ('研发部',003,'小刘');
# 报错:DB::Exception: Type mismatch in IN or VALUES section. Expected: Array(UInt8). Got: UInt64
INSERT INTO dept VALUES ('研发部',[001,002],['小李','小张','小刘']);
# DB::Exception: Elements 'people.id' and 'people.name' of Nested data structure 'people' (Array columns) have different array sizes..

AggregateFunction

AggregateFunction是clickhouse提供的一种特殊的数据类型,它能够以二进制的形式存储中间状态结果。

其使用方法也十分特殊,对于AggregateFunction类型的列字段,数据的写入和查询都与寻常不同。在写入数据时,需要调用State函数。而在查询数据时,则需要调用相应的Merge函数。

代码语言:javascript复制
-- 建表语句 这里uniq 和sum是指定的聚合函数,而uniqState、uniqMerge 是函数对应的State和Merge函数
CREATE TABLE t
(
  id String,
 code AggregateFunction(uniq,String), 
 value AggregateFunction(sum,UInt32), 
) ENGINE = ...
-- 写入测试数据;
INSERT INTO TABLE t SELECT 'A000', uniqState('code1'), sumState(toUInt32(100));
INSERT INTO TABLE t SELECT 'A000', uniqState('code1'), sumState(toUInt32(100));
INSERT INTO TABLE t SELECT 'A001', uniqState('code1'), sumState(toUInt32(100));
INSERT INTO TABLE t SELECT 'A001', uniqState('code2'), sumState(toUInt32(50));
-- 查询数据
SELECT id,uniqMerge(code),sumMerge(value) FROM t GROUP BY id;
┌─id────┬─uniqMerge(code)─┬─sumMerge(value)─┐
│ A001  │               2 │             150 │
│ A000  │               1 │             200 │
└───────┴─────────────────┴─────────────────┘

Nullable(typename)

允许将表示“缺失值”的特殊标记(NULL)与允许的正常值一起存储TypeName。例如,Nullable(Int8)类型列可以存储Int8类型值,而没有值的行将存储NULL.

对于 a TypeName,您不能使用复合数据类型[Array]和[Tuple]。复合数据类型可以包含Nullable类型值,例如Array(Nullable(Int8)).

注意:为了存储空值会额外占用存储空间,会对性能有影响。

代码语言:javascript复制

CREATE TABLE t_null(x Int8, y Nullable(Int8)) ENGINE TinyLog
INSERT INTO t_null VALUES (1, NULL), (2, 3)
SELECT x   y FROM t_null
┌─plus(x, y)─┐
│       ᴺᵁᴸᴸ │
│          5 │
└────────────┘
# 如果对应的是null返回1 否则返回0
SELECT y.null FROM t_null;
┌─n.null─┐
│      1 │
│      0 │
└────────┘

Domain

域名类型分为IPv4和IPv6两类,本质上它们是对整型和字符串的进一步封装

IPv4使用UInt32存储,相比String更加紧凑,占用的空间更小,查询性能更快

代码语言:javascript复制
CREATE TABLE IP4_TEST (
    url String,
    ip IPv4
) ENGINE = Memory;
INSERT INTO IP4_TEST VALUES ('www.baidu.com','192.0.0.0');
SELECT url,ip,toTypeName(ip) FROM IP4_TEST;
#错误的ip无法写入
INSERT INTO IP4_TEST VALUES ('www.baidu.com','192.0.0');
# DB::Exception: Invalid IPv4 value.

IPv6类型是基于FixedString(16)封装的,它的使用方法与IPv4一样。

数据库引擎

延时引擎 Lazy

最近一次访问间隔expiration_time_in_seconds`秒,将数据存到内存里,只能用于*Log表。

它是为存储许多小的*Log表而优化的,对于这些表,两次访问之间的时间间隔很长。

代码语言:javascript复制
CREATE DATABASE testlazy ENGINE = Lazy(expiration_time_in_seconds);

Atomic

默认引擎

它支持非阻塞的[DROP TABLE]和[RENAME TABLE]查询和原子的[EXCHANGE TABLES t1 AND t2]查询。

数据库Atomic中的所有表都有唯一的UUID,并将数据存储在目录/clickhouse_path/store/xxx/xxxyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy/,其中xxxyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy是该表的UUID。

RENAME TABLES是在不更改UUID和移动表数据的情况下执行的。这些查询不会等待使用表的查询完成,而是会立即执行。

DROP TABLE时,不删除任何数据,数据库Atomic只是通过将元数据移动到/clickhouse_path/metadata_dropped/将表标记为已删除,并通知后台线程。最终表数据删除前的延迟由[database_atomic_delay_before_drop_table_sec]设置指定。

可以使用SYNC修饰符指定同步模式。使用[database_atomic_wait_for_drop_and_detach_synchronously]设置执行此操作,DROP等待运行 SELECT, INSERT和其他使用表完成的查询。表在不使用时将被实际删除。

MySQL

MySQL引擎用于将远程的MySQL服务器中的表映射到ClickHouse中,并允许对表进行INSERTSELECT查询,以方便在ClickHouse与MySQL之间进行数据交换

MySQL数据库引擎会将对其的查询转换为MySQL语法并发送到MySQL服务器中,因此可以执行诸如SHOW TABLESSHOW CREATE TABLE之类的操作。

但是不能使用RENAMECREATE TABLEALTER

代码语言:javascript复制
CREATE DATABASE [IF NOT EXISTS] db_name [ON CLUSTER cluster]
ENGINE = MySQL('host:port', ['database' | database], 'user', 'password')

表引擎

表引擎有以下作用

1.数据如何存储,放在哪儿

2.支持哪些查询,如何支持

3.对并发访问的支持,能否多线程请求方式执行语句

4.所支持的查询种类

5.是否支持索引

6.主备复制的支持

CK提供了近30种表引擎,分为四类:log、MergeTree、Special、Integration

Log(日志)

用于做小表的数据分析(100万行左右)。

TinyLog、StripeLog、Log

共同特点
  • 数据存储在磁盘上。
  • 写入时将数据追加在文件末尾。
  • 不支持突变操作。
  • 不支持索引(这意味着 SELECT 在范围查询时效率不高。)
  • 非原子地写入数据(如果某些事情破坏了写操作,例如服务器的异常关闭,表内数据不完整)
差异:

LogStripeLog 引擎都支持:

  • 并发访问数据的锁。(INSERT 请求执行过程中表会被锁定,并且其他的读写数据的请求都会等待直到锁定被解除。如果没有写数据的请求,任意数量的读请求都可以并发执行。)
  • 并行读取数据。(在读取数据时,ClickHouse 使用多线程。每个线程处理不同的数据块。)

TinyLog:

对并发访问没有限制(没有锁)

如果同时从表中读取并在不同的查询中写入,则读取操作将抛出异常 如果同时写入多个查询中的表,则数据将被破坏。

Log:

Log «标记» 的小文件与列文件存在一起。

这些标记写在每个数据块上,并且包含偏移量,这些偏移量指示从哪里开始读取文件以便跳过指定的行数。这使得可以在多个线程中读取表数据。

对于并发数据访问,可以同时执行读取操作,而写入操作则阻塞读取和其它写入。

Log引擎适用于临时数据,write-once 表以及测试或演示目的。

MergeTree(合并树)

该类型引擎用于做大数据量分析,是ClickHouse官方主要推荐的引擎类型,支持几乎所有的ClickHouse核心功能。

常用的存储引擎:

MergeTree:

该引擎中数据按列存储,每个列数据以二进制形式单独压缩,性能最好

ReplacingMergeTree:

基于MergeTree复制引擎,在分布式高可用汇总结合zk做副本复制

Distributed:

分布式引擎,此类表不存储数据,相当于视图功能,写入数据到分布式表中,会把请求分不到集群的各个分片中;在查询的时候做聚合查询再返回

Special

为特定的场景定制,例如:内存、缓存和文件

Integration

用于对其他外部数据库表的集成

比如将外部数据导入或者直接读取其他的数据源

ClickHouse集群

ClickHouse集群解决的是高可用与负载均衡的问题,一个集群可以用多个节点组成,当某集群节点出现故障后不影响整个集群的正常使用

什么是分区

在clickhouse 中对于一张表做分区,则是对数据的纵向切分,数据以目录的形式存在,在写入时创建,相同分区的数据最终合并到同一个分区目录,不同分区的数据不会被合并到一起。

分区不能解决各个节点的数据分布问题,只可以降低数据文件的搜索范围。

CK分布式集群

该分布式集群有两个分布式节点,每个分布式节点上有一个本地表,都有一个分布式表(Distributed),读取数据的时候分布式表会在两个本地表(也可以说是分片)中都读取数据汇总后返回给用户,收到的读写任务时,分布式表将任务分发到各个本地表。

原理如下图:

本地表

存放用户实际的数据,一个本地表等同于一份数据分片

分布式表

是一个逻辑概念,不存储任何数据,是本地表的访问代理,类似于MySQL中的视图,作用类似于分库中间件

多个节点上有多个本地表,集群中有一个分布式表,数据存入的时候 会由分布式表将数据随机分散到多个分片中,这时如果有一个节点坏掉,集群中数据会丢失一部分(即坏掉的分片的数据没了)

使用on cluster语句在集群的某台机器上执行以下代码,即可在每台机器上创建本地表和分布式表,其中⼀张本地表对应着⼀个数据分⽚,分布式表通常以本地表加“_all”命名。它与本地表形成⼀对多的映射关系,之后可以通过分布式表代理操作多张本地表。

这里有个要注意的点,就是分布式表的表结构尽量和本地表的结构一致。如果不一致,在建表时不会报错,但在查询或者插入时可能会抛出异常。在集群中使用,我们要加上on cluster <cluster_name>的ddl,这样我们的建表语句在某一台clickhouse实例上执行一次即可分发到集群中所有实例上执行。

先在每一个分片上创建本地表:

代码语言:javascript复制
--使用ReplicatedMergeTree引擎创建本地表test_log
create table test_log on cluster ck_cluster
(
    totalDate Date,
    unikey    String
)
    engine = ReplicatedMergeTree('/clickhouse/test/tables/{shard}/test_log', '{replica}')
        PARTITION BY totalDate
        ORDER BY unikey
        SETTINGS index_granularity = 8192;

分布式表引擎的创建模板:

代码语言:javascript复制
ENGINE = Distributed(cluster, database, table, [sharding_key])

参数描述:

cluster:集群名称,在对分布式表执⾏读写的过程中,它会使⽤集群的配置信息来找到相应的host节点。database,table:数据库和本地表名称,用于将分布式表映射到本地表上。sharding_key: 分⽚键,分布式表会按照这个规则,将数据分发到各个本地表中。

代码语言:javascript复制
--创建分布式表test_log_all,数据在读写时会根据rand()随机函数的取值,决定数据写⼊哪个分⽚,也可以用hash取值。
create table test_log_all on cluster ck_cluster
(
    totalDate Date,
    unikey    String
)
    engine = Distributed('ck_cluster', 'test', 'test_log', rand());

CK 的副本复制

对每个分片复制了一份副本,放在其他的节点上,即做了数据备份,当某节点坏掉,会从其他节点的副本读取数据。

ClickHouse只允许一个实例持有一个分片,所以在生产环境中,一般采用两个甚至多个对等的集群互相复制和热备(依靠ReplicatedMergeTree引擎族实现复制表),当某集群上的某节点挂掉后,可以由其他集群上持有对应分片的节点顶上,实现高可用。

CK的副本复制不可避免降低了性能,假设说一个2核的机器,本来创建一个ck实例,这个ck实例可以用2核的cpu计算能力,但是当增加副本时,每个副本只能使用1个cpu的计算能力了。

但是其实对于大多数使用ClickHouse的场景来说,ck都是被用于数据分析使用,在这样的场景下数据都是从其他的生产库中周期抽取,进行大数据分析的,所以数据其实是被允许丢失的。

但是如果数据丢失后,需要重新将所有的数据同步过来,可能会付出较大的代价,所以一般会创建一个副本来使用,保证集群的可用性。

分布式集群的读写规则

数据写入:

如果直接写入的是A1/B1/C1,那么对应的复本表会写同样的数据

如果写入的是分布式表,则根据规则随机平分写入或者单独写入到某个分片中,副本进行复制

数据读取:

会从A/B/C同时读取数据,A中的数据随机从A1或者A2读取,读取到所有数据后合并到一起返回结果,如果A1挂了会从A2 读,不影响集群读取

常见的CK分布式方案

方案1:纯分片

该方案在不同节点上创建分片,使用Distributed MergeTree结构。

优点:

架构简单

并行查询分布式表,查询速度非常快

缺点:

如果某个分片节点损坏,会丢失数据且无法恢复,查询会报错,整个集群会瘫掉

适用场景:

不经常做分析,但是有数据分析需求,集群不需要持续运行提供服务,但是数据量较大(数据少可以直接用单点),采用该方式提高查询分析的效率,满足需要。

方案2 :分片 副本复制

该方案在不同节点上创建分片,并对每个分片做副本复制,使用Distributed MergeTree结构,副本复制由Distributed控制

优点:

数据安全有保障,不存在某一数据节点故障后无法查询,集群瘫掉的问题

并行查询分布式表,速度快

缺点:

某个节点存储损坏后,节点再次上线或者用其他的新节点上线,都会被视为空节点,损坏前该损坏节点所存储的数据无法恢复,只能保证新数据的,当另外一个分片坏掉时(低概率事件),依然会面临集群数据丢失集群瘫痪的问题

复制和分片数据分发均为Distributed组件控制,Distributed出现问题,集群就会出问题。

适用场景:

这种架构适用于有新数据周期性写入,只对新数据做分析不对老数据分析的场景(老数据会舍弃)

方案3:分片 副本复制 高可用

该方案在不同节点上创建分片,并对每个分片做副本复制,使用ReplicatedMergeTree Distributed Zookeeper结构。

本地表使用ReplicatedMergeTree方式创建

Zookeeper是一个分布式服务框架,主要用于解决分布式应用中遇到的一些数据管理问题,例:统一命名服务,状态同步服务、集群管理,分布式应用配置项的管理。

优点:

共享同一个Zookeeper路径的表,相互同步数据,数据安全有保障,不存在节点故障,新节点上线Zookeeper会把损坏前的数据同步。

并行查询分布式表,速度快

缺点:

如果一次几百万写入,容易导致Zookeeper集群压力过大出现异常(可以采用写入本地表,读取分布式表的方案,可保证每秒几百个insert操作)

相比非复制表,insert的延迟略大,需要等待Zookeeper集群驱动复制。

成本较高

适用场景:

适合数据量大,安全性要求高的生产环境,是最优的分布式高可用集群方案

生产环境方案总结

使用方案3

  • 副本数量至少为两个,即两份数据
  • 通过外围负载均衡分发方式 写表写本地表,读表读分布式表,表管理统一入口
  • 默认并发100左右
  • 批量写入不建议小批量,小批量写入会造成过多的数据合并,性能会下降
  • 建议不要在一台主机内使用多实例,ck使用时很容易跑满cpu
  • 建议zk 5节点,不和ck在同一主机
  • 节点配置:CPU48核、内存128G192G、磁盘SSD RAID5/10、数据量1TB/40亿左右

0 人点赞