likes
comments
collection
share

简约的clickhouse操作库 —— CKgroup | 七日打卡

作者站长头像
站长
· 阅读数 42

从实习开始接触 clickhouse ,算是打开之前对数据库的认知。“原来数据库也有这么不一样的地方”,具体不一样在哪,可以先看看 clickhouse官网 的介绍。简单来说说 clickhouse 优秀设计的地方。

clickhouse

列式存储

行式存储的特点是将一条数据记录集中存在一起,这种方式更加贴近于关系模型。写入的效率较高,在读取时也可以快速获得一个完整数据记录,呈现数据的局部性。

但是行式存储对于 OLAP 分析查询并不友好:

  1. 在查询的时候会将整体全部的字段查询出来,而返回给用户的时候一般是某几个字段,这样其实大量的IO操作是无效的。
  2. 读取到内存是成块结构。而根据计算机存储介质,其实会有很多无效的数据被填充到 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。但是还不够:

  1. 如何运用 batch insert 提升写入性能?
  2. 分布式表和本地表,应该选择那个写入?
  3. 返回数据量过大,如何借助 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

简约的clickhouse操作库 —— CKgroup | 七日打卡

如上图所示,demo.sh 完成了以下这些:

  • 创建以 ReplicatedMergeTree 引擎的表。数据会以副本形式存储在 clickhouse 中。
  • ExecAuto() 插入时会预先把每一个 shard 的数据分割好,这个可以在打印的每一个节点数据量见得。
  • QueryRow() 查询结果

使用

ckgroup 是对 clickhouse-go 的封装。在使用上开发者只需要导入 ckgroup,不需要导入其他的 driver,就可以操作 clickhouse

在本例中,我们准备了两条语句:

  1. 一条用于插入元组(行)
  2. 另一条用于查询。
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 的易用性
  • 流式查询
  • 。。

项目地址

ckgroup

转载自:https://juejin.cn/post/6916531191440998413
评论
请登录