ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS)。

ClickHouse 是 Yandex(俄罗斯最大的搜索引擎)开源的一个用于实时数据分析的基于列存储的数据库,其处理数据的速度比传统方法快 100-1000 倍。它的性能超过了目前市场上可比的面向列的DBMS,每台服务器每秒处理数亿至十亿多行和数十千兆字节的数据。

ClickHouse OLAP 适用场景有:1)读多于写;2)大宽表,读大量行但是少量列,结果集较小;3)数据批量写入,且数据不更新或少更新;4)无需事务,数据一致性要求低;5)灵活多变,不适合预先建模。

社区文档地址:

https://clickhouse.tech/docs/zh/

主要特点

1.列存储

行存储的好处是想查某个人所有的属性时,可以通过一次磁盘查找加顺序读取就可以。但是当想查所有人的年龄时,需要不停的查找,或者全表扫描才行,遍历的很多数据都是不需要的。

列存储的好处:

  • 对于列的聚合,计数,求和等统计操作要优于行式存储
  • 由于某一列的数据类型都是相同的,针对于数据存储更容易进行数据压缩,每一列选择更优的数据压缩算法,大大提高了数据的压缩比重
  • 由于数据压缩比更好,一方面节省了磁盘空间,另一方面对于cache也有了更大的发挥空间

2.DBMS的功能

  • 几乎覆盖了标准SQL的大部分语法,包括 DDL和 DML ,以及配套的各种函数

  • 用户管理及权限管理

  • 数据的备份与恢复

3.多样化引擎

clickhouse和mysql类似,把表级的存储引擎插件化,根据表的不同需求可以设定不同的存储引擎。目前包括合并树、日志、接口和其他四大类20多种引擎。

4.高吞吐写入能力

ClickHouse采用类LSM Tree的结构,数据写入后定期在后台Compaction。通过类LSM tree的结构,ClickHouse在数据导入时全部是顺序append写,写入后数据段不可更改,在后台compaction时也是多个段merge sort后顺序写回磁盘。顺序写的特性,充分利用了磁盘的吞吐能力,即便在HDD上也有着优异的写入性能。

官方公开benchmark测试显示能够达到50MB-200MB/s的写入吞吐能力,按照每行100Byte估算,大约相当于50W-200W条/s的写入速度。

5.数据分区与线程级并行

ClickHouse将数据划分为多个partition,每个partition再进一步划分为多个index granularity,然后通过多个CPU核心分别处理其中的一部分来实现并行数据处理。

在这种设计下,单条Query就能利用整机所有CPU。极致的并行处理能力,极大的降低了查询延时。

所以,clickhouse即使对于大量数据的查询也能够化整为零平行处理。但是有一个弊端就是对于单条查询使用多cpu,就不利于同时并发多条查询。所以对于高qps的查询业务,clickhouse并不是强项。

6.关联查询

clickhouse像很多OLAP数据库一样,单表查询速度优于关联查询,而且clickhouse的两者差距更为明显。

数据类型

  1. 整形:Int8,Int16,Int32,Int64,UInt8,UInt16,UInt32,UInt64
  2. 布尔型:就是Uint8,取值限制为 0 或 1
  3. 浮点型:Float32,Float64
  4. Decimal型:Decimal32(s),Decimal64(s),Decimal128(s)
  5. 字符串:String和FixedString
  6. 枚举:Enum8和Enum16;分别用’String'= Int8 和’String'= Int16形式表示
  7. 时间:Date,DateTime,DateTime64
  8. 数组:Array(T),T可以是任意类型,包含数组; 但不推荐使用多维数组

时序数据库

‌**ClickHouse可以用作时序数据库。**‌ ClickHouse是一个高性能的列式存储数据库,由Yandex开发,主要用于在线分析处理查询(OLAP),能够使用SQL查询实时生成分析数据报告。尽管ClickHouse最初并非专为时间序列数据设计,但其高性能和列式存储的特性使其在时间序列数据分析场景中表现出色‌。

ClickHouse在时间序列数据分析中具有以下优势:

  • 高性能‌:ClickHouse使用列式存储和基于事件的存储引擎,能够实现高性能的数据查询和分析。
  • 可扩展性‌:支持水平扩展,可以通过添加更多节点来扩展集群。
  • 实时性‌:支持实时数据处理和查询,实现低延迟的数据分析。
  • 丰富的函数和表达式‌:提供多种数据类型和函数,如聚合函数、分组函数和窗口函数,适用于复杂的数据操作和分析‌。

安装

docker安装:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
docker pull yandex/clickhouse-server
docker pull docker.m.daocloud.io/yandex/clickhouse-server
docker tag docker.m.daocloud.io/yandex/clickhouse-server 10.10.200.11:5000/yandex/clickhouse-server
docker rmi docker.m.daocloud.io/yandex/clickhouse-server
docker push 10.10.200.11:5000/yandex/clickhouse-server

# 直接跑起来
docker run -d --name sdx-clickh --ulimit nofile=262144:262144 yandex/clickhouse-server

# 如果想把数据盘挂载到本地目录
$ mkdir $HOME/some_clickhouse_database
$ docker run -d --name some-clickhouse-server --ulimit nofile=262144:262144 \
	-v $HOME/some_clickhouse_database:/var/lib/clickhouse \
	10.10.200.11:5000/yandex/clickhouse-server

你会看到docker启动了三个端口: 8123/tcp, 9000/tcp, 9009/tcp 。端口详解:

  • 8123 端口:这是 ClickHouse 的 HTTP 端口,用于提供基于 HTTP 的查询接口。通过该端口可以使用 HTTP 请求与 ClickHouse 服务器进行交互执行查询、获取查询结果等操作,比如:http://IP:8123/play
  • 9000 端口:这是 ClickHouse 的默认服务器端口,用于客户端与 ClickHouse 服务器进行通信。客户端应用程序可以通过该端口连接到 ClickHouse 服务器,并执行查询、插入和更新等数据库操作
  • 9004 端口:这是 ClickHouse 的分布式表引擎(Distributed Table Engine)使用的端口。当 ClickHouse 使用分布式表引擎进行数据分片和分布式查询时,节点之间会通过该端口进行通信
  • 9005 端口:这是 ClickHouse 的分布式表引擎(Distributed Table Engine)使用的备份(Replica)端口。当 ClickHouse 使用分布式表引擎进行数据备份和冗余存储时,节点之间会通过该端口进行数据同步和复制
  • 9009 端口:这是 ClickHouse 的远程服务器管理(Remote Server Management)端口。通过该端口,可以使用 ClickHouse 客户端工具(如 clickhouse-client)远程管理 ClickHouse 服务器,包括执行管理命令、配置修改等操作

用宿主机做端口映射的启动方式:

1
2
3
4
5
6
7
docker run -itd --name clickh-sdx --ulimit nofile=262144:262144 \
	-v /k8s-data/clickh-sdx/data:/var/lib/clickhouse \
	-v /k8s-data/clickh-sdx/log:/var/log/clickhouse-server \
	-p 8123:8123 -p 9000:9000 -p 9009:9009 10.10.200.11:5000/yandex/clickhouse-server
	
# 如果conf文件夹有预先准备的config.xml和users.xml等文件,可以考虑
	-v /k8s-data/clickh-sdx/conf:/etc/clickhouse-server \

测试

你可以进入docker容器中,查看具体的数据:

1
2
3
4
5
6
7
docker exec -it sdx-clickh bash 		# 控制台进入
root@db407a2f8707:> clickhouse-client 	# 客户端命令行
show databases
select 1

# 如果conf/users.xml设置了密码,需要输入密码才能登录
root@6c220a85c973:> clickhouse-client --password

常用SQL语句

一些常规操作,单机模式:

1
2
3
4
5
6
7
8
CREATE TABLE sdx_demo ( id UInt16, str String, create_date date ) \
	ENGINE = MergeTree(create_date, (id), 8192);

insert into sdx_demo(id,str,create_date) values (1, 'chende', now())
insert into sdx_demo(id,str,create_date) values (2, 'lmx', now())
insert into sdx_demo(id,str,create_date) values (3, 'wulei', now())

select * from sdx_demo

Go开发示例

ClickHouse Go Client

官方提供两种ClientSDK,一种更大众同时支持Go标准库方式连接模式和本地快速连接模式;另一种更轻量级只支持本地快速连接模式。

  • clickhouse-go - High level language client which supports either the Go standard database/sql interface or the native interface.
  • ch-go - Low level client. Native interface only.

Clickhouse-go SDK 两种连接模式对比

Native format Native protocol HTTP protocol Bulk write support Struct marshaling Compression Query Placeholders
NativeAPI
Stand API

示例代码如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package demo

import (
	"context"
	"database/sql"
	"fmt"
	"github.com/ClickHouse/clickhouse-go/v2"
	"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
	"time"
)

var serverAddr = []string{"YourIP:9000"}

// Stand Model
// +++++++++++++++++++++++++++++++++++++++++++++
func connStandSqlModel() *sql.DB {
	conn := clickhouse.OpenDB(&clickhouse.Options{
		Addr: serverAddr,
		Auth: clickhouse.Auth{
			Database: "default",
			Username: "default",
			Password: "",
		},
		//TLS: &tls.Config{
		//	InsecureSkipVerify: true,
		//},
		Settings: clickhouse.Settings{
			"max_execution_time": 60,
		},
		DialTimeout: time.Second * 30,
		Compression: &clickhouse.Compression{
			Method: clickhouse.CompressionLZ4,
		},
		//Debug:                true,
		BlockBufferSize:      10,
		MaxCompressionBuffer: 10240,
		ClientInfo: clickhouse.ClientInfo{
			Products: []struct {
				Name    string
				Version string
			}{{Name: "my-stand-model-demo", Version: "0.1"}},
		},
	})
	conn.SetMaxIdleConns(5)
	conn.SetMaxOpenConns(10)
	conn.SetConnMaxLifetime(time.Hour)
	return conn

	// or DSN URL
	// ++++++++++++++++++++++++++++++++++++++++++++
	//dsnUrl := "clickhouse://username:password@host1:9000,host2:9000/database?
	//dial_timeout=200ms&max_execution_time=60"
	//conn, err := sql.Open("clickhouse", dsnUrl)
	//if err != nil {
	//	panic(err)
	//}
	//return conn
}

func standModelDemo() {
	db := connStandSqlModel()
	defer db.Close()

	// 创建表格
	createTable := "CREATE TABLE test (id Int32, value String) ENGINE = Memory"
	if _, err := db.Exec(createTable); err != nil {
		panic(err)
	}

	// 插入数据
	insertData := "INSERT INTO test (id, value) VALUES(1, 'Hello'), (2, 'World')"
	if _, err := db.Exec(insertData); err != nil {
		panic(err)
	}

	// 查询数据
	rows, err := db.Query("SELECT * FROM test")
	if err != nil {
		panic(err)
	}
	defer rows.Close()

	var id int
	var value string
	for rows.Next() {
		if err = rows.Scan(&id, &value); err != nil {
			panic(err)
		}
		fmt.Printf("ID: %d, Value: %s\n", id, value)
	}

	// 清理表
	if _, err = db.Exec("DROP TABLE test"); err != nil {
		panic(err)
	}
}

// Native Model
// +++++++++++++++++++++++++++++++++++++++++++++
func connNativeModel() (driver.Conn, error) {
	var ctx = context.Background()
	var conn, err = clickhouse.Open(&clickhouse.Options{
		Addr: serverAddr,
		Auth: clickhouse.Auth{
			Database: "default",
			Username: "default",
			Password: "",
		},
		ClientInfo: clickhouse.ClientInfo{
			Products: []struct {
				Name    string
				Version string
			}{{Name: "my-native-model-demo", Version: "0.1"}},
		},
		Debugf: func(format string, v ...interface{}) {
			fmt.Printf(format, v)
		},
		//TLS: &tls.Config{
		//	InsecureSkipVerify: true,
		//},
	})
	if err != nil {
		return nil, err
	}
	if err = conn.Ping(ctx); err != nil {
		if exc, ok := err.(*clickhouse.Exception); ok {
			fmt.Printf("Exception [%d] %s \n%s\n", exc.Code, exc.Message, exc.StackTrace)
		}
		return nil, err
	}
	return conn, nil
}

func nativeModelDemo() {
	db, err := connNativeModel()
	if err != nil {
		panic(err)
	}
	defer db.Close()

	ctx := context.Background()

	// 创建表格
	createTable := "CREATE TABLE test (id Int32, value String) ENGINE = Memory"
	if err = db.Exec(ctx, createTable); err != nil {
		panic(err)
	}

	// 插入数据
	insertData := "INSERT INTO test (id, value) VALUES(1, 'Hello'), (2, 'World')"
	if err = db.Exec(ctx, insertData); err != nil {
		panic(err)
	}

	// 查询数据
	rows, err := db.Query(ctx, "SELECT * FROM test")
	if err != nil {
		panic(err)
	}
	defer rows.Close()

	var id int32 // must be int32
	var value string
	for rows.Next() {
		if err = rows.Scan(&id, &value); err != nil {
			panic(err)
		}
		fmt.Printf("ID: %d, Value: %s\n", id, value)
	}

	// 清理表
	if err = db.Exec(ctx, "DROP TABLE test"); err != nil {
		panic(err)
	}
}

// +++++++++++++++++++++++++++++++++++++++++++++
func RunExample() {
	//standModelDemo()
	nativeModelDemo()
}

参考阅读

https://www.jianshu.com/p/74eb7747210a

https://blog.csdn.net/askuld/article/details/139346641

https://cloud.tencent.com/developer/article/1799255

https://blog.csdn.net/seashouwang/article/details/138770274

https://www.5axxw.com/wiki/content/32lq5c

(完)