简约的clickhouse操作库 —— CKgroup | 七日打卡
从实习开始接触 clickhouse
,算是打开之前对数据库的认知。“原来数据库也有这么不一样的地方”,具体不一样在哪,可以先看看 clickhouse官网 的介绍。简单来说说 clickhouse
优秀设计的地方。
clickhouse
列式存储
行式存储的特点是将一条数据记录集中存在一起,这种方式更加贴近于关系模型。写入的效率较高,在读取时也可以快速获得一个完整数据记录,呈现数据的局部性。
但是行式存储对于 OLAP 分析查询并不友好:
- 在查询的时候会将整体全部的字段查询出来,而返回给用户的时候一般是某几个字段,这样其实大量的
IO
操作是无效的。 - 读取到内存是成块结构。而根据计算机存储介质,其实会有很多无效的数据被填充到 CPU缓存行 中。
clickhouse
在存储上会把每个字段以一个文件存储,并搭配上 稀疏索引 ,在查询宽表上可以极大加速查询。
表引擎
丰富的表引擎,给开发者更多的选择和适应各种场景。列举一些我平时应用较多的引擎:
Mysql
:直接映射mysql table
,可以让你使用clickhouse
丰富的函数去操作mysql
数据。MergeTree
:可以说这是ck
最大分支的表引擎系列,Replicated+
支持数据副本,Replacing+
支持删除重复数据【Merge duplicated data】。(后续可以单开文章细讲)Kafka
:可以直接作为Kafka
的消费端,同时搭配Materialized Views
可以做到数据持久化。
More
Batch insert
,推荐批量写入,这个也很符合数据库这些软件设计初衷。减少IO
,提升写入性能。- 丰富的函数搭配丰富的数据类型,真正纯
SQL
开发。
上面说了 ck
很多优秀的特性,那如何更好地运用这些特性在开发中操作 ck
?
go
暴露了顶层的接口 database/sql
,同时也已经有类似 clickhouse-go
这种 driver,可以像操作原生API那样操作 ck
。但是还不够:
- 如何运用
batch insert
提升写入性能? - 分布式表和本地表,应该选择那个写入?
- 返回数据量过大,如何借助
go
优秀的设计返回数据?
这就是我们设计 ckgroup
的初衷:提供一个更适合 clickhouse
,同时更人性化的库。
ckgroup
ckgroup
是基于 clickhouse-go 的封装 ,提供更加友好和方便的 API 供开发者使用。
特性
- 纯
Golang
开发 - 插入时自动做了集群
hash
分片,避免clickhouse
内分片导致的插入性能损耗和数据分布不均 - 插入失败重试机制
- 查询结果自动转为
struct
安装
Go 1.13
及以上,支持Go的3个最新版本clichouse (19.16+)
$ go get -u github.com/tal-tech/cds/tools/ckgroup
快速体验
确保已安装 docker
, docker-compose
。在下载 ckgroup
后,可以直接运行 ./demo.sh
。
如上图所示,demo.sh
完成了以下这些:
- 创建以
ReplicatedMergeTree
引擎的表。数据会以副本形式存储在clickhouse
中。 ExecAuto()
插入时会预先把每一个shard
的数据分割好,这个可以在打印的每一个节点数据量见得。QueryRow()
查询结果
使用
ckgroup
是对 clickhouse-go
的封装。在使用上开发者只需要导入 ckgroup
,不需要导入其他的 driver,就可以操作 clickhouse
。
在本例中,我们准备了两条语句:
- 一条用于插入元组(行)
- 另一条用于查询。
import "github.com/tal-tech/cds/tools/ckgroup"
// Fill the config
var (
ckgroupConfig = config.Config{
ShardGroups: []config.ShardGroupConfig{
{ShardNode: "tcp://localhost:9000", ReplicaNodes: []string{"tcp://localhost:9001"}},
{ShardNode: "tcp://localhost:9002", ReplicaNodes: []string{"tcp://localhost:9003"}},
},
}
)
func main() {
group := ckgroup.MustCKGroup(c)
// Ready data
var args [][]interface{}
for _, item := range generateUsers() {
args = append(args, []interface{}{item.Id, item.RealName, item.City})
}
// Batch insert, ckgroup will help you to make the shard
err := group.ExecAuto(`insert into user (id,real_name,city) values (?,?,?)`, 0, args)
if err != nil {
panic(err) // Just for example purpose
}
// Query multi rows of the user in Shanghai
datas := make([]user, 0)
err := group.QueryRows(&datas, `select id,real_name,city from user where city=?`, "上海")
if err != nil {
panic(err)
}
for i := range datas {
fmt.Println(datas[i])
}
}
func generateUsers() []user {
var users []user
for i := 0; i < 10000; i++ {
item := user{
Id: i,
RealName: fmt.Sprint("real_name_", i),
City: "test_city",
}
users = append(users, item)
}
return users
}
Feel free to contribute your own examples!
TODO
- 改为接口实现 , 方便 test mock
- 改进 Insert 的易用性
- 流式查询
- 。。
项目地址
转载自:https://juejin.cn/post/6916531191440998413