上篇我们搭建了clickhouse集群,4个节点ck01、ck02、ck03、ck04,ck01和ck02作为一个分片的两个副本,ck03和ck04作为另一个分片的两个副本。

集群创建好了,肯定要去使用集群,使用集群就要创建数据表,说到表就不得不提表引擎,clickhouse有很多表引擎,都有各自的应用场景,关于表引擎后面文章中再专门讨论,这里为了便于理解本篇内容只是简单说明一下,下面我们创建一个简单的表,基本语法如下,使用MergeTree表引擎,具体细节后面文章专门讨论。

create table if not exists t_profile_local(user_id String,content String) ENGINE = MergeTree() ORDER BY tuple()SETTINGS index_granularity = 8192;

以上语句只会在客户端本地节点创建表t_profile_local,所以称之为本地表,ck所有的操作都是作用在客户端所在的节点,比如在ck01节点上登录ck

clickhouse java批量写入 clickhouse写入数据_clickhouse


执行上面的创建表的SQL,或者执行下面的创建数据库的语句

create database test;

只有ck01节点有数据库test和数据表t_profile_local,相同分片的副本节点ck02上并没有。

clickhouse java批量写入 clickhouse写入数据_clickhouse java批量写入_02


当数据量不是很大的话,使用单机的clickhouse,可以只使用本地表,一份数据无法保障可用性,数据节点机器可能会出现故障无法使用,这时数据无法使用,所有的分布式系统保障高可用的机制都是多副本,比如hdfs,默认一份数据有3个副本,clickhouse也有副本机制-复制表,因为复制机制是建立在表级别的,不是集群级别的,也就是说,用户在创建表时可以通过指定引擎选择该表是否高可用而不是像hdfs那样集群创建之初就决定的,表级别无法自行决定自身是否做冗余副本,目前支持复制的是ReplicatedMergeTree引擎族(包括一系列引擎)。clickhouse的复制机制有两种:第一种依赖集群自身的复制机制,回忆一下上篇metrika.xml中的集群配置,internal_replication这个参数是控制写入数据到分布式表时是否的写入到所有副本中,true表示只写一个副本,false表示写所有副本。这个参数一般和表引擎搭配使用,如果表引擎选择ReplicatedMergeTree系列,该参数一般设置为true,因为复制表引擎本身有复制机制,也就是只需要往其中一个副本写数据,数据会自动复制到该分片的其他副本,如果该参数设为false表示通过分布式表(一定是通过分布式表,写本地表并没有复制机制)写入数据的时候,会把数据同时写入分片的所有副本,这样的话两种复制机制同时起作用,为了避免clickhouse内部复制机制和复制表的复制机制出现冲突,导致数据重复或者不一致。

在节点ck02上创建分布式表,注意关键字ON CLUSTER,所以这是一个分布式DDL,会尝试在集群的所有节点的对应的数据库下面创建该表,如果某个节点该数据库不存在(实际存在此情况,clickhouse机制允许创建本地对象,包括数据库、表等,clickhouse允许用着这么做,也确实有这种应用场景),则该语句会报错,但不影响其他正常节点的建表成功。

create table if not exists t_profile_replica ON CLUSTER test_ck_cluster (user_id String,content String) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/t_profile_replica', '{replica}') ORDER BY tuple() SETTINGS index_granularity = 8192;

从节点ck02导入数据到分布式表

cat profile_test.dat | clickhouse-client --host=ck01 --port=9000 --database=test --query="INSERT INTO t_profile_replica  FORMAT CustomSeparated SETTINGS format_custom_escaping_rule = 'CSV',format_custom_field_delimiter = '\t'" --input_format_allow_errors_num=10000

可以看到同一个分片的另一个副本ck01也有数据了

clickhouse java批量写入 clickhouse写入数据_大数据_03


但是如果数据量很大,一个节点已经无法容纳这么多数据,只能建多节点集群,也就是横向扩展,这样的话就要把原来的单个表的数据进行分片(上面也提到了),每个集群节点可以看作一个分片,如此理论上集群的容量是可以无限扩展的(实际会有问题,后面讨论),集群中的每一个分片应该具有相同的表结构,想要集群中所有分片节点都创建表,有两种方式:

一是每个节点都执行一边创建语句(比较土,没人用,节点多的话可操作性差)

二是使用ON CLUSTER关键字,表示分布式DDL,即集群任意一个节点执行一次就可在集群所有实例上创建同样的本地对象。

clickhouse java批量写入 clickhouse写入数据_clickhouse java批量写入_04


ck02节点上也有了表

clickhouse java批量写入 clickhouse写入数据_大数据_05


集群会根据配置文件(回忆下上篇的metrika.xml)在集群test_ck_cluster的每个节点上创建表t_profile_local,表有了还不够,下面就要往表里面写数据,而且数据要分散到不同的分片上,这就要用到分布式表,创建分布式表的方法是使用分布式引擎Distributed。

分布式表是一个逻辑上的表,可以理解为数据库中的视图,,一般查询都查询分布式表。分布式表引擎会将我们的查询请求路由本地表进行查询,然后进行汇总最终返回给用户。

创建分布式表的语法如下:

CREATE TABLE IF NOT EXISTS {distributed_table} as {local_table}
ENGINE = Distributed({cluster}, ‘{local_database}’, ‘{local_table}’, ‘{sharding_key}’)

Distributed引擎需要以下几个参数:

  • cluster:集群标识符,注意不是复制表宏中的标识符,而是<remote_servers>中指定的那个。
  • local_database:本地表所在的数据库名称
  • local_table:本地表名称(可选的)
  • sharding_key:分片键,该键与config.xml中配置的分片权重(weight)一同决定写入分布式表时的路由,即数据最终落到哪个物理表上。它可以是表中一列的原始数据(如site_id),也可以是函数调用的结果,如随机值rand()。注意该键要尽量保证数据均匀分布,另外一个常用的操作是采用区分度较高的列的哈希值,如intHash64(user_id),murmurHash3_64(code)等。

创建分布式表

create table t_profile_dist ON CLUSTER test_ck_cluster AS test.t_profile_replica ENGINE = Distributed(test_ck_cluster, test, t_profile_replica, rand());

直接写分布式表,集群会根据分片键把数据分散到对应的所有分片上去,至于分散效果要看分片键的散列效果,分片键要考虑表连接的时候不要产生shuffle(不同分片之间传数据)。一般情况下不建议直接写分布式表,分布式表设计是用来读数据的而不是写数据的。有以下几点原因:

  • 分布式表接收到数据后会将数据拆分成多个parts,并转发数据到其它服务器,会引起服务器间网络流量增加、服务器merge的工作量增加,导致写入速度变慢,并且增加了Too many parts的可能性。
  • 数据的一致性问题, 先在分布式表所在的机器进行落盘, 然后异步的发送到本地表所在机器进行存储,中间没有一致性的校验, 而且在分布式表所在机器时如果机器出现down机,,会存在数据丢失风险。
  • 数据写入默认是异步的,短时间内可能造成不一致。
  • 对zookeeper的压力比较大(待验证),没经过正式测试,只是看到了有人提出。

以上就是clickhouse表的基本使用,下一篇继续介绍clickhouse的基础优化。