設計理念
Everything is table(萬物皆表),數據表就是ClickHouse和外部交互的接口。在數據表背后無論連接的是本地文件、HDFS、Zookeeper還是其它服務,終端用戶始終只需要面對數據表,只需要用SQL查詢語言。
處理各種數據場景走內部集成的路線,既與外部系統(其它數據庫,消息中間件或者是服務接口)的集成直接在數據庫內部實現。
面向表編程,一張數據表最終能夠提供哪些功能、擁有哪些特性、數據會議什么格式被保存以及數據會怎樣被加載,這些都將由它的表引擎決定。
表引擎特性
表引擎 | 概念 | 引擎 |
---|---|---|
合并樹類型 | MergeTree提供主鍵索引,數據分區,數據副本和數據采樣等基本能力,其它表引擎在MergeTree上各有所長;ReplacingMergeTree具有刪除重復數據的特性;SummingMergeTree會按照排序鍵自動聚合數據;Replicated系列支持數據副本 | MergeTree,SummingMergeTree ... |
外部存儲類型 | 外部存儲表引擎直接從其他的存儲系統讀取數據,例如直接讀取HDFS的文件或者MySQL數據庫的表。這些引擎只負責元數據管理和數據查詢,而它們自身通常并不負責數據的寫入,數據文件直接由外部系統提供。 | HDFS,MySQL,JDBC,Kafka,File |
內存類型 | 面向內存查詢,數據從內存中直接訪問。除了Memory引擎之外,其它幾款都會將數據寫入磁盤以防數據丟失。在數據被加載時,數據會被全部加載到內存、以供查詢使用。全部加載到內存:意味著有更好的查詢性能;但是如果加載的數據量過大,就會有極大的內存消耗和負擔 | Memory,Set,Join,Buffer, |
日志類型 | 數據量很小(100萬以下),同時查詢場景也比較簡單,并且是“一次”寫入多次查詢方式,可以使用日志類型;共性:不支持索引和分區等高級特性;不支持并發讀寫,當針對一張日志寫入數據時,針對這張表的查詢會被阻塞,直至寫入動作結束;擁有物理存儲,數據會被保存到本地文件中 | TinyLog,StripeLog,Log |
接口類型 | 本身不保存數據,而是像粘合劑一樣整合其它數據表;使用時,不用關心底層復雜性,像接口一樣為用戶提供統一訪問頁面 | Merge,Dictionary,Distribute |
其它類型 | 擴充ClickHouse的能力邊界 | Live View,NULL,URL |
引擎使用場景
表引擎使用
SummingMergeTree(MergeTree家族)
使用
數據聚合
-
聚合前數據
聚合前數據 -
聚合后數據
聚合后數據
使用說明
PRIMARY KEY可與ORDER BY不同,PRIMARY KEY作為主鍵索引,ORDER BY作為聚合條件。
PRIMARY KEY是ORDER BY的前綴,索引和數據仍然具有對應關系;因為數據以ORDER BY排序,索引以PRIMARY KEY排序,PRIMARY KEY是ORDER BY的前綴,那么索引也是ORDER BY有序的,同一個排序標準,產生相同的數據順序,所以,索引和數據仍然具有對應關系。
ORDER BY可以可以減少,GROUP BY(A, B, C, D) --> GROUP BY(A, B); ORDER BY可以增加新的列。
工作中使用
- SummingMergeTree聚合ClickHouse Shard中數據,同時通過Distribute表向外提供服務
- PARTITION BY toStartOfHour(event_date) + PRIMARY KEY (event_date, network_id)
- ORDER BY (event_date, network_id, a, c, d) ...
- TTL event_date + INTERVAL 35 DAY DELETE
- SETTINGS index_granularity = 8192, replicated_deduplication_window = 0
原理
- 用ORDER BY排序鍵作為聚合數據的條件Key
- 只有在合并分區的時候才會觸發匯總的邏輯
- 以數據分區為單位來聚合數據。當分區合并時,同一個數據分區內聚合Key相同的數據會被合并匯總,而不同分區之間的數據不會被匯總
- 如果在定義引擎時指定了columns匯總列(非主鍵的數值類型字段),則SUM匯總這些列字段;如果未指定,則聚合所有非主鍵的數值類型字段
- 在進行數據匯總時,因為分區內的數據已經基于ORDER BY排序,所以能夠找到相鄰且擁有相同聚合Key的數據
- 在匯總數據時,同一個分區內,相同聚合Key的多行數據會合并成一行。其中,匯總字段進行SUM計算;對于那些非匯總地段,則會使用第一行數據的取值。
- 支持嵌套結構,但列字段名稱必須以Map后綴結尾。
Kafka(外部存儲類型)
使用
kafka環境準備
生產消息
數據data.json:
{ "id": "A001", "city": "wuhan", "v1": 10, "v2": 20, "create_time": "2019-08-10 17:00:00" }
{ "id": "A001", "city": "wuhan", "v1": 20, "v2": 30, "create_time": "2019-08-20 17:00:00" }
{ "id": "A001", "city": "zhuhai", "v1": 20, "v2": 30, "create_time": "2019-08-10 17:00:00" }
{ "id": "A001", "city": "wuhan", "v1": 10, "v2": 20, "create_time": "2019-02-10 09:00:00" }
{ "id": "A002", "city": "wuhan", "v1": 60, "v2": 50, "create_time": "2019-10-10 17:00:00" }
生產
kafka-console-producer --topic test --bootstrap-server localhost:9092 < data.json
創建kafka表
CREATE TABLE test_kafka
(
`id` String,
`city` String,
`v1` UInt32,
`v2` Float64,
`create_time` DateTime
)
ENGINE = Kafka()
SETTINGS kafka_broker_list = '172.18.0.3:9092', kafka_topic_list = 'test', kafka_group_name = 'test', kafka_format = 'JSONEachRow', kafka_skip_broken_messages = 100
消費消息
SELECT * FROM test_kafka
查看kafka消費日志
cat /var/log/clickhouse-server/clickhouse-server.log | grep kafka
2021.06.30 16:23:03.749646 [ 75 ] {af851164-e7ce-46cd-be01-64f10ddec924} <Debug> executeQuery: (from 127.0.0.1:34022) select * from default.test_kafka;
2021.06.30 16:23:03.750176 [ 75 ] {af851164-e7ce-46cd-be01-64f10ddec924} <Debug> StorageKafka (test_kafka): Starting reading 1 streams
2021.06.30 16:23:07.262678 [ 91 ] {af851164-e7ce-46cd-be01-64f10ddec924} <Trace> StorageKafka (test_kafka): Polled batch of 98 messages. Offsets position: [ test[0:98] ]
2021.06.30 16:23:07.282639 [ 91 ] {af851164-e7ce-46cd-be01-64f10ddec924} <Warning> StorageKafka (test_kafka): Parsing of message (topic: test, partition: 0, offset: 77) return no rows.
2021.06.30 16:23:07.285524 [ 91 ] {af851164-e7ce-46cd-be01-64f10ddec924} <Trace> StorageKafka (test_kafka): Polled offset 98 (topic: test, partition: 0)
2021.06.30 16:23:07.339373 [ 91 ] {af851164-e7ce-46cd-be01-64f10ddec924} <Trace> StorageKafka (test_kafka): Committed offset 98 (topic: test, partition: 0)
原理
- 只負責元數據的管理和數據查詢,不存儲數據(外部存儲引擎共性),支持從kafa消費消息,也可以向kafka中插入數據(Demo)
- 默認情況下,Kafka表引擎每隔500毫秒會拉取一次數據,時間由stream_poll_timeout_ms參數控制,數據首先會被放入緩存,在時機成熟時,緩存數據會被刷新到數據表
- 滿足下列條件之一,觸發刷新動作:
a. 當完成一個數據塊兒的寫入(數據塊兒的大小由kafka_max_block_size參數控制,默認情況下65536)
b. 等待時間超過7500毫秒(stream_flush_interval_ms參數控制,默認7500ms)
Join(內存類型)
使用
CREATE TABLE join_tb1 (
id UInt8,
name String,
time DateTime
) ENGINE = Log
INSERT INTO TABLE join_tb1 VALUES(1, 'ClickHouse', '2019-05-01 12:00:00'),
(2, 'Spark', '2019-05-01 12:30:00'), (3, 'ElasticSearch', '2019-05-01 13:00:00');
CREATE TABLE id_join_tb1 (
id UInt8,
price UInt32,
time DateTime
) ENGINE = Join(ANY, LEFT, id)
INSERT INTO TABLE id_join_tb1 VALUES (1, 100, '2019-05-01 11:55:00'),
(1, 105, '2019-05-01 11:10:00'),
(2, 90, '2019-05-01 12:01:00'),
(3, 80, '2019-05-01 11:55:00'),
(5, 70, '2019-05-01 11:55:00'),
(6, 60, '2019-05-01 11:55:00');
SELECT id, name, price FROM join_tb1 LEFT JOIN id_join_tb1 USING(id);
SELECT joinGet('id_join_tb1', 'price', toUInt8(1));
原理
- ENGINE = Join(join_strictness, join_type, key1[, key2, ...])
- join_strictness: 連接的精度,它決定了JOIN查詢在連接數據時所使用的策略,目前支持ALL,ANY和ASOF三種類型
- join_type: 連接左右兩個數據集合的策略;交集,并集,笛卡爾積或其他形式,目前支持INNER,OUTER和CROSS;當join_type類型為ANY時,在數據寫入時,join_key重復的數據會被自動忽略
- join_key: 連接鍵,決定使用哪個列字段進行關聯
Merge(接口類型)
使用
# 數據以年分表,使用Merge引擎進行粘合
CREATE TABLE test_table_2018(
id String,
create_time DateTime,
code String
)ENGINE = MergeTree()
PARTITION BY toYYYYMM(create_time)
ORDER BY id
CREATE TABLE test_table_2019(
id String,
create_time DateTime,
code String
)ENGINE = Log()
PARTITION BY toYYYYMM(create_time)
ORDER BY id
CREATE TABLE test_table_all as test_table_2018
ENGINE = MergeTree(currentDatebase(), '^test_table_')
原理
- 不存儲數據,而是像粘合劑一樣可以整合其他的數據表
- 被代理查詢的數據表要在同一個數據庫內,且擁有相同的表結構,但是它們可以使用不同的表引擎以及不同的分區定義(對于MergeTree而言)
URL(其它類型)
使用
/* GET users listing. */
router.get('/users', func (req, res, next) {
var result = ''
for(let i=0; i<5; i++){
result += '{"name":"nauu'+i+'"}\n';
}
res.send(result)
})
/* POST user. */
router.post('/users', func (req, res) {
res.sendStatus(200)
})
CREATE TABLE url_table (
name String
)
ENGINE = URL('http://localhost:9688/users', JSONEachRow)
SELECT * FROM url_table
INSERT INTO TABLE url_table VALUES('nauu-insert')
原理
- URL表引擎等價于HTTP客戶端,它可以通過HTTP/HTTPS協議,直接訪問遠端的REST服務。
- SELECT查詢會被底層轉換為GET請求
- INSERT查詢會被轉換為POST請求
綜合使用例子
Kafka + MATERIALIZED VIEW + ReplicateSummingMergeTree + Distributed
Kafka Engine Table: 外部存儲表,消費kafka消息
Materialize View: 當數據插入到kafka表時,執行select語句將數據進行transform后,插入到To表
SummingMergeTree: MergeTree家族表,支持partition summing,主鍵索引,數據分區,replica和數據采樣;
Distribute 表: 進行數據粘合,為用戶提供統一的數據視圖
引用
Docker HDFS
Docker HDFS 2
apt install netcat
Docker中容器之間通訊方式
安裝ifconfig apt install net-tools
查看docker容器ip地址 docker inspect kafka-docker_clickhouse-server_1 | grep IP
Kafka引擎
ClickHouse原理解析與應用實踐