最近參與的項目一直使用到ClickHouse,所以趁最近比較閑來對ClickHouse來做個總結 。 首先,來談談,我使用了這么多天ClickHouse的感受吧:
-
ClickHouse查詢超快,ClickHouse是列式存儲數據庫,同時ClickHouse支持bitmap這樣的壓縮算法,大大提升了查詢速度。 ClickHouse支持幾十億數據進行秒級運算
-
ClickHouse支持向量處理,Clickhouse支持array數據格式
-
ClickHouse支持多類復雜的數據格式,同一般的數據庫相比較,ClickHouse除了支持普通的數據類型之外,還支持 嵌套-Nested ,Json ,Array , Bitmap等格式
-
支持多類型表
- Kafka引擎表 - Clickhouse可以直接接入Kafka的數據到ClickHouse中
- Mysql引擎表 - 和Mysql數據庫的表進行同步
- MergeTree家族
- MergeTree : 普通引擎表
- replacingMergeTree : 主要用於去重數據
- AggregatingMergeTree : 用於增量數據的更新
- SummingMergeTree : 主要用於數據的預聚合
雖然,ClickHouse優點很多 ,但是 還是感覺有很多不方便的地方 ,應該是ClickHouse的作用主要是進行實時點擊場景吧,其和傳統的HDFS並不相同吧,ClickHouse還是有以下的缺點:
-
不支持高並發,雖然ClickHouse查詢速度超快,但是並不支持高並發,官方建議qps為100,可以通過修改配置文件增加連接數,但是在服務器足夠好的情況下;
-
ClickHouse 沒法支持事務 ,不支持真正意義的刪除和更新
-
ClickHouse 無法做到向 HDFS一樣,通過shell腳本來定時觸發sql任務 , ClickHouse面向的是實時場景,在計算時 可以 搭配 物化視圖預聚合進行操作,雖然 物化視圖很強大,但是也不是萬能的 😂
-
Clickhouse中刪除數據是異步的,正常來說ClickHouse並不贊同刪除超過50多G的表或者視圖,其實正常來說,一般也不會刪除這么大的表和物化視圖,但是可能難免有這樣的需求,此使再刪大表,就會發現,雖然
drop table table_name
當時執行成功了,但是在短時間內還是會查到這張表,同時這個表里還是有數據的,這與ClickHouse底層的Attention有關系。
大致談了一下自己使用ClickHouse的感受,接下來從建表入手具體談一下ClickHouse的應用
建表
普通表
- 單機
create table table_name (
col_1 String ,
col_2 Nullable(String) ,
col_3 UInt64,
col_4 Nullable(UInt64) ,
col_5 DateTime ,
col_6 Date
) ENGINE = MergeTree() | AggregatingMergeTree() | SummingMergeTree() -- 三個里面選擇一個即可
PARTITION BY (toDate(col_5))
ORDER BY (col_1, col_2)
;
- 集群 (單副本)
-- 本地表
create table table_name on CLUSTER '{cluster}' (
col_1 String ,
col_2 Nullable(String) ,
col_3 UInt64,
col_4 Nullable(UInt64) ,
col_5 DateTime ,
col_6 Date
) ENGINE = MergeTree() | AggregatingMergeTree() | SummingMergeTree()
PARTITION BY (toDate(col_5))
ORDER BY (col_1, col_2)
;
-- 分布式表
CREATE TABLE table_name_distribute ON CLUSTER default
AS table_name
ENGINE = Distributed(default , default_database, table_name , rand())
;
- 集群(雙副本)
create table table_name on CLUSTER '{cluster}' (
col_1 String ,
col_2 Nullable(String) ,
col_3 UInt64,
col_4 Nullable(UInt64) ,
col_5 DateTime ,
col_6 Date
) ENGINE = replacingMergeTree(/clickhouse/tables/cdp.xmp_track_new/{shard}', '{replica}') | replacingAggregatingMergeTree() | replacingSummingMergeTree()
PARTITION BY (toDate(col_5))
ORDER BY (col_1, col_2)
;
-- 分布式表 (邏輯表)
CREATE TABLE table_name_distribute ON CLUSTER default
AS table_name
ENGINE = Distributed(default , default_database, table_name , rand())
;
在建表的時候,主要關注以下幾個概念:
- 分布式表
對於,單機版的ClickHouse很好理解,就是在ClickHouse建了一張普通表 , 沒有分布式和本地表概念之分
對於, 集群版來說,則需要通過 on CLUSTER '{cluster}'
來在每個節點上建表,向本地表導入數據的話,ClickHouse會將數據分散在不同的節點存儲,因此,如果直接select
本地表只能查看到一半的數據; 此使則需要分布式表,分布式表是一個邏輯上的表, 可以理解為數據庫中的視圖, 一般查詢都查詢分布式表. 分布式表引擎會將我們的查詢請求路由本地表進行查詢, 然后進行匯總最終返回給用戶; 因此我們在ClickHouse中除了最終查詢的表是分布式表之外,中間計算過程則用本地表進行;
- 分區/ 分片
partition by
后面接的是該表的分區,例 :partition by (col_1, col_2)
, 和hive中的partition
有相同的含義, 其主要按照具體的字段來對數據進行分區/分片存放,但是有點不太一樣的是,CK里面的partition是建表是的一個字段 ,建表的時候指定以該字段進行分區分片 ,但是需要注意一點的是 ,在往該表中寫入數據的時候,分區越多其寫入速度會變慢;
- 排序/ 主鍵
order by
類似於主鍵的作用,不過其和Mysql不一樣的是其沒有主鍵約束,索引的含義更多一點 , 同時需要注意的時候,order by (字段)
后接的字段必須是非空類型,對於Null值類型的字段並不支持用於索引中,同樣的Nullable
類型的字段也不行。
物化視圖
數據庫中的視圖(view)是從一張或多張數據庫表查詢導出的虛擬表,反映基礎表中數據的變化,且本身不存儲數據。而物化視圖(materialized view)則和普通視圖有很大的區別。
物化視圖是查詢結果集的一份持久化存儲,其和普通視圖不一樣的是,普通視圖並不存儲數據,而物化視圖則可以存儲數據,其趨於一張表。物化視圖可以是原始表的一個簡單copy, 也可以是原始表的簡單聚合結果集,也可以是通過一些處理的結果集, 其會隨着原始更新而發生變化,這種更新僅限於insert
操作,對於update
和 delete
並不會及時發生變化,而需要手動去進行更新,但是物化視圖還是應用於多種場景:
- 接入kafka外表的數據,可以通過物化視圖將kafka的數據進行相應的處理,最終導入到一張表中
- 適用於預聚合場景, 但是這個地方的預聚合-僅限於簡單的一些
count , sum , uniq, max , min
這些操作,對於更復雜的場景可不太適合,利用物化視圖進行預聚合的時候,可以聯合countState, sumState,uniqState ,maxState ,minState
以及countMerge , sumMerge , uniqMerge , maxMerge,minMerge
進行操作。 利用這種預聚合的能力,來實現實時點擊流的計算。
使用物化視圖,主要是利用空間來換時間的概念,來快速的進行查詢,但是物化視圖還是有如下幾個限制:
join
場景,當要對多個表進行關聯的時候,物化視圖並不會里面更新,而是當左表有更新的時候才會發生相應的更新,當如果左邊不變右邊新插入數據,物化視圖則不會立馬更新。- 物化視圖不能立馬感應源表的更新和刪除操作,需要手動進行更新
- 必須指定物化視圖的engine 用於數據存儲
- TO [db].[table]語法的時候,不得使用POPULATE。但是需要注意的是 使用POPULATE可能會引起大批量數據的讀寫,導致數據庫連接不上等問題😂,不要問為什么,說多了都是累
- 查詢語句(select)可以包含下面的子句:DISTINCT, GROUP BY, ORDER BY, LIMIT…
- 物化視圖的alter操作有些限制,操作起來不大方便。
- 若物化視圖的定義使用了TO [db.]name 子語句,則可以將目標表的視圖 卸載 DETACH 在裝載 ATTACH
創建物化視圖
- 使用
TO [db].[table]
命令
使用TO [db].[table]
這種語法建表時,只需要指定 物化視圖的表名db.view_table_name
,寫入數據目標表的表名db.goal_table_name
以及讀取表的邏輯select 語句
CREATE MATERIALIZED VIEW db.view_table_name
TO db.goal_table_name
AS
SELECT
*
FROM
db.orign_table_name
- 使用
POPULATE
使用POPULATE
語法建表時,需要指定物化視圖對應的引擎, 以及 分區 和 索引 ,此使物化視圖和一張普通表沒什么太大的區別
CREATE MATERIALIZED VIEW db.view_table_name
ENGINE = AggregatingMergeTree()
PARTITION BY (stat_day )
order by (stat_day)
POPULATE
AS
SELECT
*
from
db.origin_table_name
利用物化視圖進行預聚合運算
方式一,直接把物化視圖作為最終要查詢的表,即用POPULATE
進行建表, 利用物化視圖來求pv & uv
, countState & uniqState
則會把uid進行匯總,在最終進行查詢的時候,再利用countMerge & uniqMerge
函數將用戶idmerge
在一起,此種用法還可以應用於多項目的pv & uv
的計算。
CREATE MATERIALIZED VIEW db.view_table_name
ENGINE = AggregatingMergeTree()
PARTITION BY ( stat_day)
ORDER BY (stat_day)
POPULATE
AS
select
toDate(now()) as run_day ,
project as project_id,
toDate(server_time) as stat_day ,
countState(uid) as pv ,
uniqState(uid) as uv
from
db.origin_table_name
group by
project,toDate(server_time)
;
假設要求,多個項目共同的pv & uv
,則可以使用上面的物化視圖進行結果查詢,使用以下查詢語句即可,對了切忌不要直接select * from db.view_table_name
查詢物化視圖,因為countState
這些函數都存的是亂碼。
select
countMerge(pv) as pv ,
uniqMerge(uv) as uv
from
db.view_table_name
where
project_id in ('11','22','33')
方式二,即用TO [db].[table_name] 將結果存入到對應的目標表中,和上面一致,再不贅述
外表
clickhouse可以通過一些引擎來直接連接其他數據源的數據,就我最常用的兩種來舉例說明,有興趣的朋友看官網
kafka外表
直接配置相關信息來連接實時接收kafka的數據, 具體的參數看官網吧,此處接收kafka的數據時候,如果字段固定的話,可以直接使用JSONEachRow
,不過采用以下兩種建表方式,如果字段不固定,則直接使用JSONAsString。
JSONAsString建表
CREATE TABLE db.kafka_base
(
`eventStr` String
)
ENGINE = Kafka
SETTINGS kafka_broker_list = 'host:9092',
kafka_topic_list = 'topic',
kafka_group_name = 'group',
kafka_format = 'JSONAsString',
kafka_row_delimiter = '\n',
kafka_schema = '',
kafka_num_consumers =1,
kafka_skip_broken_messages=10
;
JSONEachRow建表
需要將所有的字段都列出來
CREATE TABLE db.kafka_base
(
'a' String ,
'b' String ,
'c' String
)
ENGINE = Kafka
SETTINGS
kafka_broker_list = 'host:9092',
kafka_topic_list = 'topic1',
kafka_group_name = 'group1',
kafka_format = 'JSONEachRow',
kafka_row_delimiter = '\n',
kafka_schema = '',
kafka_num_consumers = 1
Mysql
CREATE TABLE db.mysql_base
(
't1' String ,
't2' String
) ENGINE = MySQL('host:port', 'database', 'table', 'user', 'password'[, replace_query, 'on_duplicate_clause']);
參數設置
- host:port :MySQL server的地址。
- database :MySQL數據庫名稱。
- table : MySQL表名。
- user : MySQL用戶名。
- password : MySQL用戶密碼。
- replace_query :將INSERT INTO查詢轉換為REPLACE INTO查詢的標識。如果replace_query=1, 查詢將被替換。
- on_duplicate_clause : 將ON DUPLICATE KEY 'on_duplicate_clause’表達式添加到INSERT查詢中。
總的來說,一旦踏入Clickhouse地界,無數個坑在等你,當然也有驚喜,Over !!!