背景
最近在做ClickHouse相关的数据集成工作,发现在ClickHouse中一个分片本身就代表集群中的一个节点。而我们使用的分布式表其实类似一张视图,真实的数据存储在不同分片节点上的本地表中(一张本地表就是一份数据的分片)。并且针对分布式表的操作是推荐写本地表读分布式表。那么就需要考虑如何将数据均匀写入不同的本地表也就是分片中。于是去查阅了seatunnel(github.com/apache/incu…)中关于ClickHouse connector的源码(公司暂时使用其他集成工具),发现只需最基础的几个参数就可以搞定上面的功能。 这里不会分析源码,重点是介绍如何利用system库中相关的信息来帮我们实现此功能
具体实现方式
在数据集成的时候我们可以只提供一些简单的数据如:HOST(连接地址:端口), DATABASE(数据库), TABLE(分布式表名称),USER(用户名),PASS(密码). 因为我们要支持对分布式表可以按不同分片去写数据,一般我们会按某个字段如ID进行hash后来获取其应该写入的分片节点,因此可以新增一个参数shardKey,代表了分片字段。
1-查询分布式表相关信息信息
如何我们指定了分片字段,则可以使用下面的SQL来判断是否是分布式表引擎Distributed,因为只有分布式表才有分片的情况.
SELECT
engine,
engine_full,
data_paths
FROM system.tables
WHERE (database = 'test') AND (name = 'test01_all')
┌─engine──────┬─engine_full────────────────────────────────────────────────────────────────────────┬─data_paths────────────────────────────────────────────────────────────────────┐
│ Distributed │ Distributed('{cluster}', 'test', 'test01', rand()) │ ['/var/lib/clickhouse/data/test/test01_all/'] │
└─────────────┴────────────────────────────────────────────────────────────────────────────────────┴───────���───────────────────────────────────────────────────────────────────────┘
SQL的返回可以获取到表的engine信息和完整的engine信息
通过engine_full信息可以获取到分布式表对应的本地表信息.
SELECT engine_full
FROM system.tables
WHERE (database = 'test') AND (name = 'test01_all') AND (engine = 'Distributed')
Query id: 74e5c201-4192-4422-bf5c-af328c930f47
┌─engine_full────────────────────────────────────────────────────────────────────────┐
│ Distributed('{cluster}', 'test', 'test01', rand()) │
└────────────────────────────────────────────────────────────────────────────────────┘
engine_full中 Distributed里面的 '{cluster}'其实就代表了集群名称。
代码中可以按固定格式去截断获取。
为什么这里集群名称是'{cluster}',这个问题在下面会有解答。
2-ClickHouse宏配置介绍
上面提出了问题为什么集群名称是是'{cluster}' ? :
这是因为在部署ClickHouse的时候可以配置宏变量,{}大括号中的内容会自动被替换,取自服务器配置文件的宏部分。 配置文件路径:cat ./conf.d/chop-generated-macros.xml
内容如下:
<yandex>
<macros>
<installation>repl-05</installation>
<all-sharded-shard>0</all-sharded-shard>
<cluster>replicated</cluster>
<shard>0</shard>
<replica>chi-xxx-01-replicated-0-0</replica>
</macros>
</yandex>
其实就是在这里定义了可以使用的宏变量。
我们也可以基于SQL来查询相关的宏变量:
SELECT *
FROM system.macros
Query id: 7561f5bf-78e1-493c-bbcc-f30f4bb6017e
┌─macro─────────────┬─substitution───────────────┐
│ all-sharded-shard │ 0 │
│ cluster │ replicated │
│ installation │ xxx-01 │
│ replica │ chi-xxx-01-replicated-0-0 │
│ shard │ 0 │
└───────────────────┴────────────────────────────┘
通过上面的说明就可以知道 '{cluster}' 这个集群名称其实就是 replicated。 需要注意的是如果建表的时候指定了是使用了宏变量来指定了集群名称 如: CREATE TABLE test ON CLUSTER '{cluster}',那么基于上面SQL查询出来的engine_full中的集群名称就是'{custer}',但是在system.clusters表中存的集群名称是真实的集群名称。因此这里建议在创建分布式表的时候集群信息使用真实的名称,如上面的cluster就是replicated。
2-获取指定集群的分片信息
Get the shard of the given cluster.我们可以通过下面的SQL来获取指定集群对应的分片信息。
SELECT
shard_num,
shard_weight,
replica_num,
host_name,
host_address,
port
FROM system.clusters
WHERE cluster = 'replicated'
┌─shard_num─┬─shard_weight─┬─replica_num─┬─host_name──────────────────┬─host_address─┬─port─┐
│ 1 │ 1 │ 1 │ chi-xxx-05-replicated-0-0 │ 127.0.0.1 │ 9000 │
│ 2 │ 1 │ 1 │ chi-xxx-05-replicated-1-0 │ 101.94.93.53 │ 9000 │
└───────────┴──────────────┴─────────────┴────────────────────────────┴──────────────┴──────┘
上面其实就获取到了系统中当前集群对应的分片信息,shard_num代表分片的ID. 上面是两个分片一个副本。
这里其实就可以构建为一个集合来维护所有的分片信息.
3-根据shardKey做写入
有了上面的信息,其实我们就可以实现一个路由方法,根据具体分片的值来获取对应的分片信息:
statement = shardRouter.getShard(shardKey)
statement代表着当前数据对应分片节点的jdbc statement,他是从当前分片节点的jdbc connection中来获取的。然后就可以实现数据写入不同分片的逻辑了。
具体路由方式还有很多方式: 比如spark中,其实可以在repartition的时候获取到分区的ID,根据分区ID来路由对应的分片信息,并将当前分区的数据写入到对应的分片中。
总结
上面在实现ClickHouse分片集成的思路中主要对system库常用的查询进行了说明,通过system库中提供的表和集群信息,可以方便我们在开发过程中对表的集群和分片信息的动态获取,而不用全部写在配置中。 如果需要看具体的实现可以参考seatunnel中的源码。