? ? ? ? 本次技術(shù)分享為您帶來(lái)的是,JUST(https://just.urban-computing.cn/)是如何使用ClickHouse實(shí)現(xiàn)時(shí)序數(shù)據(jù)管理和挖掘的。ClickHouse是一個(gè)高效的開源聯(lián)機(jī)分析列式數(shù)據(jù)庫(kù)管理系統(tǒng),由俄羅斯IT公司Yandex開發(fā)的,并于2016年6月宣布開源。
一、時(shí)序數(shù)據(jù)簡(jiǎn)介
時(shí)序數(shù)據(jù)全稱是時(shí)間序列(TimeSeries)數(shù)據(jù),是按照時(shí)間順序索引的一系列數(shù)據(jù)點(diǎn)。最常見的是在連續(xù)的等時(shí)間間隔時(shí)間點(diǎn)上獲取的序列,因此,它是一系列離散數(shù)據(jù)[1]。
時(shí)序數(shù)據(jù)幾乎無(wú)處不在,在目前單向的時(shí)間流中,人的脈搏、空氣的濕度、股票的價(jià)格等都隨著時(shí)間的流逝不斷變化。時(shí)序數(shù)據(jù)是數(shù)據(jù)的一種,因?yàn)樗@著而有價(jià)值的特點(diǎn),成為我們特別分析的對(duì)象。
將時(shí)序數(shù)據(jù)可以建模為如下部分組成:
Metric:度量的數(shù)據(jù)集,類似于關(guān)系型數(shù)據(jù)庫(kù)中的 table,是固定屬性,一般不隨時(shí)間而變化
Timestamp:時(shí)間戳,表征采集到數(shù)據(jù)的時(shí)間點(diǎn)
Tags:維度列,用于描述Metric,代表數(shù)據(jù)的歸屬、屬性,表明是哪個(gè)設(shè)備/模塊產(chǎn)生的,一般不隨著時(shí)間變化
Field/Value:指標(biāo)列,代表數(shù)據(jù)的測(cè)量值,可以是單值也可以是多值
一個(gè)具體的多值模型時(shí)序數(shù)據(jù)案例如表1所示:
二、時(shí)序數(shù)據(jù)管理概述
2.1 時(shí)序時(shí)序管理的流程
一切數(shù)據(jù)的本質(zhì)都是為價(jià)值服務(wù)的,獲取價(jià)值的這個(gè)過程就是數(shù)據(jù)管理與分析。從技術(shù)上來(lái)說,任何數(shù)據(jù)從產(chǎn)生到滅亡都會(huì)經(jīng)歷如圖1所示的過程。

時(shí)序數(shù)據(jù)也不例外,只是每個(gè)部分的處理不同。
(1)數(shù)據(jù)采集。同一個(gè)場(chǎng)景下時(shí)序數(shù)據(jù)產(chǎn)生的頻率一般恒定,但在不同場(chǎng)景下采集數(shù)據(jù)的頻率是變化的,每秒一千條和每秒一千萬(wàn)條數(shù)據(jù)使用的技術(shù)是完全不同的。所以,數(shù)據(jù)采集要考慮的主要是頻率和并發(fā)。
(2)數(shù)據(jù)存儲(chǔ)。數(shù)據(jù)存儲(chǔ)是為了查詢和分析服務(wù)的。以什么格式存儲(chǔ)、建什么索引、存儲(chǔ)數(shù)據(jù)量大小、存儲(chǔ)時(shí)長(zhǎng)是時(shí)序數(shù)據(jù)存儲(chǔ)要考慮的,一般時(shí)序數(shù)據(jù)寫多讀少,數(shù)據(jù)具有時(shí)效性,所以存儲(chǔ)時(shí)可以考慮冷熱存儲(chǔ)分離。
(3)數(shù)據(jù)查詢和分析。時(shí)序數(shù)據(jù)的查詢也具有顯著特點(diǎn),一般會(huì)按照時(shí)間范圍讀取,最近的數(shù)據(jù)讀取頻率高,并且按照不同的時(shí)間粒度做聚合查詢,比如統(tǒng)計(jì)最近一周每天的數(shù)據(jù)量。
分析是依賴于查詢的,時(shí)序數(shù)據(jù)的分析通常是多維的,比如網(wǎng)頁(yè)點(diǎn)擊流量、從哪個(gè)網(wǎng)站、來(lái)自哪個(gè)IP、點(diǎn)擊頻率等維度眾多,取決于具體場(chǎng)景。而時(shí)序數(shù)據(jù)也非常適合數(shù)據(jù)挖掘,利用歷史預(yù)測(cè)未來(lái)。
(4)數(shù)據(jù)刪除。這里的刪除并不是針對(duì)單條數(shù)據(jù)的,而是對(duì)特定時(shí)間范圍內(nèi)的批量數(shù)據(jù)進(jìn)行過期處理。因?yàn)闀r(shí)序數(shù)據(jù)具有時(shí)效性,歷史數(shù)據(jù)通常不再具有價(jià)值,不管是定時(shí)刪除還是手動(dòng)刪除,都代表著其短暫的生命周期的結(jié)束。
2.2 時(shí)序數(shù)據(jù)管理系統(tǒng)目標(biāo)
根據(jù)時(shí)序數(shù)據(jù)的特點(diǎn)和場(chǎng)景,我們需要一個(gè)能滿足以下目標(biāo)的時(shí)序數(shù)據(jù)管理平臺(tái):
高吞吐寫入:千萬(wàn)、上億數(shù)據(jù)的秒級(jí)實(shí)時(shí)寫入 & 持續(xù)高并發(fā)寫入。
無(wú)更新操作:數(shù)據(jù)大多表征設(shè)備狀態(tài),寫入后無(wú)需更新。
海量數(shù)據(jù)存儲(chǔ):從TB到PB級(jí)。
高效實(shí)時(shí)的查詢:按不同維度對(duì)指標(biāo)進(jìn)行統(tǒng)計(jì)分析,存在明顯的冷熱數(shù)據(jù),一般只會(huì)頻繁查詢近期數(shù)據(jù)
高可用
可擴(kuò)展性
易于使用
易于維護(hù)
2.3技術(shù)選型
說到數(shù)據(jù)庫(kù),大家第一個(gè)想到的肯定是MySQL、Oracle等傳統(tǒng)的已經(jīng)存在很多年的關(guān)系型數(shù)據(jù)庫(kù)。當(dāng)然關(guān)系模型依然有效且實(shí)用。對(duì)于小數(shù)據(jù)量(幾百萬(wàn)到幾千萬(wàn)),MySQL是可以搞定的,再大一些就需要分庫(kù)分表解決了。對(duì)時(shí)序數(shù)據(jù)一般按照時(shí)間分表,但是這對(duì)外部額外設(shè)計(jì)和運(yùn)維的工作提出了高要求。顯然,這不能滿足大數(shù)據(jù)場(chǎng)景,所以幾乎沒有人選擇這種方案。
縱觀db-engine上排名前十的時(shí)序數(shù)據(jù)庫(kù)[2],排除商用的,剩下開源的選擇并不多。接下來(lái)介紹幾款比較流行的時(shí)序數(shù)據(jù)庫(kù)。

(1)OpenTSDB。OpenTSDB開源快10年了,屬于早期的解決方案。因?yàn)槠浠贖adoop和HBase開發(fā)的索引,所以具有海量數(shù)據(jù)的存儲(chǔ)能力,也號(hào)稱每秒百萬(wàn)級(jí)別的寫入速度。但同樣因?yàn)槠湟蕾嚨腍adoop生態(tài)太重, 運(yùn)維成本很高,不夠簡(jiǎn)潔與輕量;另一個(gè)缺點(diǎn)就是它基于HBase的key-value存儲(chǔ)方式,對(duì)于聚合查詢并不友好高效,HBase存在的問題也會(huì)體現(xiàn)出來(lái)。
(2)InfluxDB。InfluxDB可以說是時(shí)序行業(yè)的典范了,其已經(jīng)發(fā)展成為一個(gè)平臺(tái),包括了時(shí)序數(shù)據(jù)應(yīng)有的一切:從數(shù)據(jù)存儲(chǔ)到界面展示。然而,InfluxDB雖然開源了其核心代碼,但重要的集群功能只有企業(yè)版才提供[3], 而企業(yè)版并不是免費(fèi)的。很多大公司要么直接使用,要么自己開發(fā)集群功能。
(3)TDengine。TDengine是濤思團(tuán)隊(duì)開發(fā)的一個(gè)高效存儲(chǔ)、查詢和分析時(shí)序大數(shù)據(jù)的平臺(tái),其創(chuàng)始人陶建輝年近5旬,依然開發(fā)出了這個(gè)數(shù)據(jù)庫(kù)。
TDengine的定位是物聯(lián)網(wǎng)、車聯(lián)網(wǎng)、運(yùn)維監(jiān)測(cè)等時(shí)序數(shù)據(jù),其設(shè)計(jì)也是專門針對(duì)每個(gè)設(shè)備。每個(gè)采集點(diǎn)一張表,比如空氣監(jiān)測(cè)站有1000萬(wàn)個(gè),那么就建1000萬(wàn)個(gè)表,為了對(duì)多個(gè)采集點(diǎn)聚合查詢,又提出了超表的概念,將同類型的采集點(diǎn)表通過標(biāo)簽區(qū)分,結(jié)構(gòu)一樣。這種設(shè)計(jì)確實(shí)非常有針對(duì)性,雖然限制了范圍,但極大提高了效率,根據(jù)其官方的測(cè)試報(bào)告[4], 其聚合查詢速度是InfluxDB的上百倍,CPU、內(nèi)存和硬盤消耗卻更少。

TDengine無(wú)疑是時(shí)序數(shù)據(jù)庫(kù)的一朵奇葩,加上在不久前開源了其集群功能[5],受到了更多用戶青睞。當(dāng)我們選型時(shí)其還沒有開源集群功能,后續(xù)也會(huì)納入觀察之中。
(4)ClickHouse。ClickHouse(之后簡(jiǎn)稱CK)是一個(gè)開源的大數(shù)據(jù)分析數(shù)據(jù)庫(kù),也是一個(gè)完整的DBMS。CK無(wú)疑是OLAP數(shù)據(jù)庫(kù)的一匹黑馬,開源不到4年,GitHub上的star數(shù)已經(jīng)超過12k(InfluxDB也不過19k+),而它們的fork數(shù)卻相差不大。
CK是俄羅斯的搜索引擎公司yandex開源的,最初是為了分析網(wǎng)頁(yè)點(diǎn)擊的流量,所以叫Click,迭代速度很快,每個(gè)月一版,開發(fā)者500+,很多都是開源共享者,社區(qū)非?;钴S。
CK是一個(gè)通用的分析數(shù)據(jù)庫(kù),并不是為時(shí)序數(shù)據(jù)設(shè)計(jì)的,但只要使用得當(dāng),依然能發(fā)揮出其強(qiáng)大的性能。
三、CK原理介紹
要利用CK的優(yōu)勢(shì),首先得知道它有哪些優(yōu)勢(shì),然后理解其核心原理。根據(jù)我們的測(cè)試結(jié)果,對(duì)于27個(gè)字段的表,單個(gè)實(shí)例每秒寫入速度接近200MB,超過400萬(wàn)條數(shù)據(jù)/s。因?yàn)閿?shù)據(jù)是隨機(jī)生成的,對(duì)壓縮并不友好。
而對(duì)于查詢,在能夠利用索引的情況下,不同量級(jí)下(百萬(wàn)、千萬(wàn)、億級(jí))都能在毫秒級(jí)返回。對(duì)于極限情況:對(duì)多個(gè)沒有索引的字段做聚合查詢,也就是全表掃描時(shí),也能達(dá)到400萬(wàn)條/s的聚合速度。
3.1 CK為什么快
可以歸結(jié)為選擇和細(xì)節(jié),選擇決定方向,細(xì)節(jié)決定成敗。
CK選擇最優(yōu)的算法,比如列式壓縮的LZ4[6];選擇著眼硬件,充分利用CPU和分級(jí)緩存;針對(duì)不同場(chǎng)景不同處理,比如SIMD應(yīng)用于文本和數(shù)據(jù)過濾;CK的持續(xù)迭代非常快,不僅可以迅速修復(fù)bug,也能很快納入新的優(yōu)秀算法。
3.2 CK基礎(chǔ)
(1)CK是一個(gè)純列式存儲(chǔ)的數(shù)據(jù)庫(kù),一個(gè)列就是硬盤上的一個(gè)或多個(gè)文件(多個(gè)分區(qū)有多個(gè)文件),關(guān)于列式存儲(chǔ)這里就不展開了,總之列存對(duì)于分析來(lái)講好處更大,因?yàn)槊總€(gè)列單獨(dú)存儲(chǔ),所以每一列數(shù)據(jù)可以壓縮,不僅節(jié)省了硬盤,還可以降低磁盤IO。
(2)CK是多核并行處理的,為了充分利用CPU資源,多線程和多核必不可少,同時(shí)向量化執(zhí)行也會(huì)大幅提高速度。
(3)提供SQL查詢接口,CK的客戶端連接方式分為HTTP和TCP,TCP更加底層和高效,HTTP更容易使用和擴(kuò)展,一般來(lái)說HTTP足矣,社區(qū)已經(jīng)有很多各種語(yǔ)言的連接客戶端。
(4)CK不支持事務(wù),大數(shù)據(jù)場(chǎng)景下對(duì)事務(wù)的要求沒這么高。
(5)不建議按行更新和刪除,CK的刪除操作也會(huì)轉(zhuǎn)化為增加操作,粒度太低嚴(yán)重影響效率。
3.3 CK集群
生產(chǎn)環(huán)境中通常是使用集群部署,CK的集群與Hadoop等集群稍微有些不一樣。如圖6所示,CK集群共包含以下幾個(gè)關(guān)鍵概念。
(1)CK實(shí)例??梢砸慌_(tái)主機(jī)上起多個(gè)CK實(shí)例,端口不同即可,也可以一臺(tái)主機(jī)一個(gè)CK實(shí)例。
(2)分片。數(shù)據(jù)的水平劃分,例如隨機(jī)劃分時(shí),圖5中每個(gè)分片各有大約一半數(shù)據(jù)。
(3)副本。數(shù)據(jù)的冗余備份,同時(shí)也可作為查詢節(jié)點(diǎn)。多個(gè)副本同時(shí)提供數(shù)據(jù)查詢服務(wù),能夠加快數(shù)據(jù)的查詢效率,提高并發(fā)度。圖5中CK實(shí)例1和示例3存儲(chǔ)了相同數(shù)據(jù)。
(4)多主集群模式。CK的每個(gè)實(shí)例都可以叫做副本,每個(gè)實(shí)體都可以提供查詢,不區(qū)分主從,只是在寫入數(shù)據(jù)時(shí)會(huì)在每個(gè)分片里臨時(shí)選一個(gè)主副本,來(lái)提供數(shù)據(jù)同步服務(wù),具體見下文中的寫入過程。
3.4 CK分布式引擎
要實(shí)現(xiàn)分片的功能,需要分布式引擎。在集群情況下,CK里的表分為本地表和分布式表,下面的兩條語(yǔ)句能夠創(chuàng)建一個(gè)分布式表。注意,分布式表是一個(gè)邏輯表,映射到多個(gè)本地表。
create table t_local on cluster shard2_replica2_cluster(t Datetime, id UInt64)??
ENGINE=ReplicatedMergeTree('/clickhouse/tables/{shard}/t_local','{replica}')
PARTITION BY toYYYYMM(t)??
ORDER BY id
create table t on cluster shard2_replica2_cluster? (t Datetime, id UInt64)?
ENGINE=Distributed(shard2_replica2_cluster,default,t_local,id)
這里的t_local就是本地表,t就是分布式表。ReplicatedMergeTree是實(shí)現(xiàn)副本同步的引擎,參數(shù)可以先忽略。Distributed引擎就是分布式引擎,參數(shù)分別為:集群名,數(shù)據(jù)庫(kù)名,本地表名,分片鍵(可以指定為rand()隨機(jī)數(shù))。
分布式引擎在寫入和查詢過程中都充當(dāng)著重要的角色,具體過程見下面。
3.5 CK寫入過程
根據(jù)使用的表引擎不同,寫入過程是不同的,上文的建表方式是比較常規(guī)的做法,按照上面的建表語(yǔ)句,需要同時(shí)開啟內(nèi)部復(fù)制項(xiàng)。
<shard2_replica2_cluster>
???????<shard>
???????????????<weight>1</weight>
???????????????<internal_replication>true</internal_replication>
???????????????<replica>
??????????????????????? …
???????????????</replica>
???????????????<replica>
??????????????????????? …
?????? ?????????</replica>
???????</shard>
…
寫入2條數(shù)據(jù):insert into t values(now(), 1), (now(),2),如圖7所示,寫入過程分為2步:分布式寫入和副本同步。
(1)分布式寫入
1)客戶端會(huì)選擇集群里一個(gè)副本建立連接,這里是實(shí)例1。寫入的所有數(shù)據(jù)先在實(shí)例1完成寫入,根據(jù)分片規(guī)則,屬于01分片的寫入實(shí)例1本地,屬于02分片的先寫入一個(gè)臨時(shí)目錄,然后向?qū)嵗?(shard02的主副本)建立連接,發(fā)送數(shù)據(jù)到實(shí)例2。
2)實(shí)例2接收到數(shù)據(jù),寫入本地分區(qū)。
3)實(shí)例1返回寫入成功給客戶端(每個(gè)分片寫入一個(gè)副本即可返回,可以配置)。
(2)副本同步
同步的過程是需要用到ZK的,上面建表語(yǔ)句的ReplicatedMergeTree第一個(gè)參數(shù)就是ZK上的路徑。創(chuàng)建表的時(shí)候會(huì)有一個(gè)副本選舉過程,一般先起的會(huì)成為主副本,副本的節(jié)點(diǎn)信息會(huì)注冊(cè)到ZK,ZK的作用只是用來(lái)維護(hù)副本和任務(wù)元數(shù)據(jù)以及分布式通信,并不傳輸數(shù)據(jù)。副本一旦注冊(cè)成功,就開始監(jiān)聽/log下的日志了,當(dāng)副本上線,執(zhí)行插入時(shí)會(huì)經(jīng)過以下過程:
1)實(shí)例1在寫入本地分區(qū)數(shù)據(jù)后,會(huì)發(fā)送操作日志到ZK的/log下,帶上分區(qū)名稱和源主機(jī)(實(shí)例1的主機(jī))。
2)01分區(qū)的其他副本,這里就實(shí)例3,監(jiān)聽到日志的變化,拉取日志,創(chuàng)建任務(wù),放入ZK上的執(zhí)行隊(duì)列/queue(這里都是異步進(jìn)行),然后再根據(jù)隊(duì)列執(zhí)行任務(wù)。
3)執(zhí)行任務(wù)的過程為:選擇一個(gè)副本(數(shù)據(jù)量最全且隊(duì)列任務(wù)最少的副本),建立到該副本(實(shí)例1)的連接,拉取數(shù)據(jù)。
注意,使用副本引擎卻不開啟內(nèi)部復(fù)制是不明智的做法,因?yàn)閿?shù)據(jù)會(huì)重復(fù)寫,雖然數(shù)據(jù)校驗(yàn)可以保證數(shù)據(jù)不重復(fù),但增加了無(wú)畏的開銷。
3.6 CK查詢過程
查詢的是分布式表,但要定位到實(shí)際的本地表,也就是副本的選擇,這里有幾種選擇算法,默認(rèn)采用隨機(jī)選擇。響應(yīng)客戶端查詢請(qǐng)求的只會(huì)有一個(gè)副本,但是執(zhí)行過程可能涉及多個(gè)副本。比如:select count(*) from t。因?yàn)閿?shù)據(jù)是分布在2個(gè)分片的,只查一個(gè)副本不能得到全部結(jié)果。

3.7 CK中重要的索引引擎
CK核心的引擎就是MergeTree,在此之上產(chǎn)生了很多附加引擎,下面介紹幾種比較常用的。
(1)ReplacingMergeTree。為了解決MergeTree主鍵可以重復(fù)的特點(diǎn),可以使用ReplacingMergeTree,但也只是一定程度上不重復(fù):僅僅在一個(gè)分區(qū)內(nèi)不重復(fù)。使用方式參考:https://clickhouse.tech/docs/en/engines/table-engines/mergetree-family/replacingmergetree/
(2)SummingMergeTree。對(duì)于確定的group by + sum查詢,若比較耗時(shí),那么可以建SummingMergeTree, 按照order by的字段進(jìn)行聚合或自定義聚合字段,其余字段求和。
(3)AggregatingMergeTree。聚合顯然是分析查詢的重點(diǎn),一般使用聚合MergeTree都會(huì)結(jié)合物化視圖,在插入數(shù)據(jù)時(shí)自動(dòng)同步到物化視圖里,這樣直接查詢物化視圖中聚合的結(jié)果即可。
(4)ReplicatedXXXMergeTree。在所有引擎前加一個(gè)Replicated前綴,將引擎升級(jí)為支持副本功能。
(5)物化視圖。物化視圖就是將視圖SQL查詢的結(jié)果存在一張表里,CK里特殊的一點(diǎn)是:只有insert的數(shù)據(jù)才能進(jìn)入觸發(fā)視圖查詢,進(jìn)入視圖表,分布式情況下同步過去的數(shù)據(jù)是不會(huì)被觸發(fā)的,為了在分布式下使用物化視圖,可以將物化視圖所依賴的表指定為分布式表。
四、CK與時(shí)序的結(jié)合
在了解了CK的基本原理后,我們看看其在時(shí)序數(shù)據(jù)方面的處理能力。
(1)時(shí)間:時(shí)間是必不可少的,按照時(shí)間分區(qū)能夠大幅降低數(shù)據(jù)掃描范圍;
(2)過濾:對(duì)條件的過濾一般基于某些列,對(duì)于列式存儲(chǔ)來(lái)說優(yōu)勢(shì)明顯;
(3)降采樣:對(duì)于時(shí)序來(lái)說非常重要的功能,可以通過聚合實(shí)現(xiàn),CK自帶時(shí)間各個(gè)粒度的時(shí)間轉(zhuǎn)換函數(shù)以及強(qiáng)大的聚合能力,可以滿足要求;
(4)分析挖掘:可以開發(fā)擴(kuò)展的函數(shù)來(lái)支持。
另外CK作為一個(gè)大數(shù)據(jù)系統(tǒng),也滿足以下基礎(chǔ)要求:
(1)高吞吐寫入;
(2)海量數(shù)據(jù)存儲(chǔ):冷熱備份,TTL;
(3)高效實(shí)時(shí)的查詢;
(4)高可用;
(5)可擴(kuò)展性:可以實(shí)現(xiàn)自定義開發(fā);
(6)易于使用:提供了JDBC和HTTP接口;
(7)易于維護(hù):數(shù)據(jù)遷移方便,恢復(fù)容易,后續(xù)可能會(huì)將依賴的ZK去掉,內(nèi)置分布式功能。
因此,CK可以很方便的實(shí)現(xiàn)一個(gè)高性能、高可用的時(shí)序數(shù)據(jù)管理和分析系統(tǒng)。下面是關(guān)鍵點(diǎn)的詳細(xì)介紹。
4.1 時(shí)序索引與分區(qū)
時(shí)序查詢場(chǎng)景會(huì)有很多聚合查詢,對(duì)于特定場(chǎng)景,如果使用的非常頻繁且數(shù)據(jù)量非常大,我們可以采用物化視圖進(jìn)行預(yù)聚合,然后查詢物化視圖。但是,對(duì)于一個(gè)通用的分析平臺(tái),查詢條件可以隨意改變的情況下,使用物化視圖的開銷就太大了,因此我們目前并沒有采用物化視圖的方式,而是采用原始的表。物化視圖的方案后續(xù)將會(huì)進(jìn)一步驗(yàn)證。
下面給出的是JUST建時(shí)序表的語(yǔ)法格式:第一個(gè)括號(hào)為TAG字段,第二個(gè)括號(hào)為VALUE字段(必須是數(shù)值型),大括號(hào)是對(duì)底層存儲(chǔ)的特殊配置,這里主要是CK的索引和參數(shù)。除了用戶指定的字段外,還有一個(gè)隱含的time字段,專為時(shí)序保留。
create?table?my_ts_table?as?ts (
????tag1?string,
????tag2 String [:primarykey][:comment=’描述’]
)
(
????value1?double,
????value2?double
)
在JUST底層,對(duì)應(yīng)了CK的2張表(一張本地表,一張分布式表),默認(rèn)會(huì)根據(jù)time分區(qū)和排序,如下面的一個(gè)例子:
create?table?airquality?as?ts (
????name?string,
????city String
)
(
????PM10?double,
????PM25?double
)
實(shí)際對(duì)應(yīng)的CK建表語(yǔ)句為:
CREATE TABLE just.username_dbname_airquality_local
(
??? `id` Int32,
??? `oid`Int32,
??? `name`String,
??? `city`String,
??? `time`DateTime,
??? `PM10`Float64,
??? `PM25`Float64
)
ENGINE =ReplicatedMergeTree('/clickhouse/tables/{shard}/24518511-2939-489b-94a8-0567384d927d','{replica}')
ORDER BY (time)
SETTINGS index_granularity = 8192
PARTITION BY toYYYYMM(time)
CREATE TABLE just.wangpeng417_test_airquality
(
??? `id` Int32,
??? `oid`Int32,
??? `name`String,
??? `city`String,
??? `time`DateTime,
??? `PM10`Float64,
??? `PM25`Float64
)
ENGINE = Distributed('just_default', 'just', ' username_dbname_airquality_local',rand())
這樣保證在使用時(shí)間范圍查詢時(shí)可以利用到索引,假如還有其他按照TAG的查詢條件,還可以自定義索引和排序字段[LL1]?(CK規(guī)定索引字段一定是排序字段的前綴)。
在不同場(chǎng)景下,還是需要根據(jù)數(shù)據(jù)量和數(shù)據(jù)特點(diǎn)來(lái)選擇索引分區(qū)和索引粒度。根據(jù)實(shí)驗(yàn)測(cè)試,假如在我們環(huán)境里CK每秒可以掃描1GB數(shù)據(jù)量,再乘以1-10倍的壓縮比,那么一個(gè)分區(qū)的數(shù)據(jù)量應(yīng)該大于千萬(wàn)到億級(jí)別可以保證較優(yōu)的速度,CK本身是多線程查詢的,可以保證同時(shí)對(duì)每個(gè)分區(qū)查詢的隔離性。但是根據(jù)查詢的場(chǎng)景,比如最多查到一個(gè)月,但大部分情況都是查一周,那么分區(qū)精確到周可能更好,這是個(gè)綜合權(quán)衡的過程。
4.2 部署與高可用
在JUST中,高可擴(kuò)展性和高可用性是我們的追求。為實(shí)現(xiàn)高可擴(kuò)展性,我們對(duì)數(shù)據(jù)進(jìn)行水平分片;為了實(shí)現(xiàn)高可用性,我們對(duì)每個(gè)分片存儲(chǔ)至少兩個(gè)副本。
關(guān)于集群部署,最小化的情況是2臺(tái)機(jī)器,這會(huì)產(chǎn)生2種情況1)交叉副本;2)一主一備;如圖9所示:
這2種方案對(duì)查詢和寫入影響的實(shí)驗(yàn)結(jié)果如圖10所示:
實(shí)驗(yàn)結(jié)果表明:寫入速度(橫坐標(biāo)為寫入示例數(shù),縱坐標(biāo)為速度MB/s)在達(dá)到極限時(shí)是差不多的,而查詢性能(橫坐標(biāo)為SQL編號(hào),SQL語(yǔ)句見附錄1,縱坐標(biāo)為耗時(shí),單位為秒)對(duì)于簡(jiǎn)單查詢差別不大,但是對(duì)于占用大量資源的復(fù)雜查詢,一主一備更加高效。因?yàn)镃K的強(qiáng)悍性能是建立在充分利用CPU的基礎(chǔ)上,在我們的測(cè)試中,裸機(jī)情況下CPU達(dá)到90%以上非常頻繁,如果有單獨(dú)的機(jī)器部署CK,那么無(wú)可厚非能夠充分利用機(jī)器資源。但在我們的環(huán)境中,與其他大數(shù)據(jù)平臺(tái)共有機(jī)器,就需要避免CK占用過多資源,從而影響其他服務(wù),于是我們選擇docker部署。docker容器部署也有開源的基于k8s的實(shí)現(xiàn):clickhouse-operator,對(duì)于小型環(huán)境,可以選擇手動(dòng)配置,通過簡(jiǎn)單的腳本即可實(shí)現(xiàn)自動(dòng)化部署。
基于以上測(cè)試結(jié)論,為了保證服務(wù)高可用,CK集群和數(shù)據(jù)冗余是必不可少的,我們的方案是保證至少2個(gè)副本的情況下,分片數(shù)盡量多,充分利用機(jī)器,且每個(gè)機(jī)器有且僅有一個(gè)CK實(shí)例。于是就有了以下分片數(shù)與副本數(shù)的公式:
其中f(n)代表當(dāng)有n臺(tái)機(jī)器時(shí),部署的分布情況,n>=2。f(2) = (1, 2)表示2臺(tái)機(jī)器采用1個(gè)分片2個(gè)副本部署的策略,f(3)=(1, 3)表示3臺(tái)機(jī)器時(shí)1個(gè)分片3個(gè)副本部署策略,f(4)=(2, 2)表示4臺(tái)機(jī)器使用2個(gè)分片,每個(gè)分片2個(gè)副本,以此類推。
4.3 動(dòng)態(tài)擴(kuò)容
隨著數(shù)據(jù)量增加,需要擴(kuò)展節(jié)點(diǎn)時(shí),可以在不停機(jī)的情況下動(dòng)態(tài)擴(kuò)容,主要利用的是分片之間的權(quán)重關(guān)系。
這里擴(kuò)容分為2種情況:
(1)增加副本:只需要修改配置文件,增加副本實(shí)例,數(shù)據(jù)會(huì)自動(dòng)同步,因?yàn)镃K的多主特性,副本也可以當(dāng)作查詢節(jié)點(diǎn),所以可以分擔(dān)查詢壓力;
(2)增加分片:增加分片要麻煩點(diǎn),需要根據(jù)當(dāng)前數(shù)據(jù)量、增加數(shù)據(jù)量計(jì)算出權(quán)重,然后在數(shù)據(jù)量達(dá)到均衡時(shí)將權(quán)重修改回去
假如開始時(shí)我們只有1個(gè)分片,已經(jīng)有100條數(shù)據(jù)了
<test_extend>
?????? <shard>
????????????? <weight>1</weight>
????????????? <internal_replication>true</internal_replication>
????????????? <replica>
???????????????????? <host>10.220.48.106</host>
???????????????????? <port>9000</port>
????????????? </replica>
????????????? <replica>
???????????????????? <host>10.220.48.105</host>
???????????????????? <port>9000</port>
????????????? </replica>
?????? </shard>
</test_extend>
現(xiàn)在要再加入一個(gè)分片,那么權(quán)重的計(jì)算過程如下(為了簡(jiǎn)化忽略這個(gè)期間插入的數(shù)據(jù)):
假如我們打算再插n條數(shù)據(jù)時(shí),集群數(shù)據(jù)能夠均衡,那么每個(gè)shard有(n+100)/2 條,現(xiàn)在shard01有100條,設(shè)權(quán)重為 w1、w2,那滿足公式:n * (w2/(w1+w2)) = (n+100)/2 ,其中n>100, 所以,假如 w1=1,n=200,那么 w2=3。
所以,將配置修改如下:
<test_extend>
?????? <shard>
????????????? <weight>1</weight>
????????????? <internal_replication>true</internal_replication>
????????????? <replica>
???????????????????? <host>10.220.48.106</host>
???????????????????? <port>9000</port>
????????????? </replica>
????????????? <replica>
????????????? ?????? <host>10.220.48.105</host>
???????????????????? <port>9000</port>
????????????? </replica>
?????? </shard>
?????? <shard>
????????????? <weight>3</weight>
????????????? <internal_replication>true</internal_replication>
????????????? <replica>
???????????????????? <host>10.220.48.103</host>
???????????????????? <port>9000</port>
????????????? </replica>
?????? </shard>
</test_extend>
等到數(shù)據(jù)同步均勻后再改回1:1
4.4系統(tǒng)介紹與不足
JUST時(shí)序分析底層使用了CK作為存儲(chǔ)查詢引擎,并開發(fā)了可復(fù)用的可視化分析界面,歡迎訪問https://just.urban-computing.cn/進(jìn)行體驗(yàn)。
用戶可以使用統(tǒng)一的查詢界面建立時(shí)序表,然后導(dǎo)入數(shù)據(jù),切換到時(shí)序分析模塊進(jìn)行可視化查詢。
目前提供的查詢功能主要有:按時(shí)間查詢、按TAG過濾,在數(shù)據(jù)量很多的情況下,可以按照大一些的時(shí)間粒度進(jìn)行降采樣,查看整個(gè)數(shù)據(jù)的趨勢(shì),同時(shí)提供了線性、拉格朗日等缺失值填補(bǔ)功能。
分析挖掘部分主要是按找特定值和百分比過濾,以及一些簡(jiǎn)單的函數(shù)轉(zhuǎn)換。
目前時(shí)序模塊的功能還比較簡(jiǎn)陋,對(duì)于時(shí)序數(shù)據(jù)的SQL查詢支持還不夠完備。未來(lái)還有集成以下功能:
(1)接入實(shí)時(shí)數(shù)據(jù);
(2)針對(duì)復(fù)雜查詢,面板功能可以采用聚合引擎預(yù)先聚合;
(3)更完善的分析和挖掘功能;
(4)對(duì)數(shù)據(jù)的容錯(cuò)與校驗(yàn)處理;
(5)與JUST一致的SQL查詢支持。
參考鏈接:
[1]https://en.wikipedia.org/wiki/Time_series
[2]https://db-engines.com/en/ranking/time+series+dbms
[3]https://www.influxdata.com/blog/influxdb-clustering/
[4]https://www.taosdata.com/downloads/TDengine_Testing_Report_cn.pdf
[5]https://www.taosdata.com/blog/2020/08/03/1703.html
[6]lz4.LZ4[EB/OL].https://lz4.github.io/lz4/,2014-08-10.
[7]https://clickhouse.tech/docs/en/engines/table-engines/mergetree-family/mergetree/
附錄:
-- SQL1:存在聚合函數(shù)
select
? avg(rainfall)
from
?t_air_one_one_dist_1;
-- SQL2:存在聚合函數(shù)以及排序
select
? county_name,
? count(*) ascnt
from
?t_air_one_one_dist_1
group by
? county_name
order by
? cnt desc,
? county_name
limit? 10;
-- SQL3:存在聚合函數(shù)以及排序
select
? county_name,
? avg(rainfall)as cnt
from
?t_air_one_one_dist_1
group by
? county_name
order by
? cnt desc,
? county_name
limit 10;
-- SQL4:存在聚合函數(shù)并且含有having子句
select
? county_name,
? count(*)
from
?t_air_one_one_dist_1
group by
? county_name
having
? count(*) >1
limit 10;
-- SQL5:存在聚合函數(shù)
select
? sum(rainfall)
from
?t_air_one_one_dist_1;
-- SQL6:存在聚合函數(shù)、排序
select
? avg(rainfall)as cnt
from
?t_air_one_one_dist_1
group by
? city_name,
? county_name
order by
? cnt desc
limit? 10;
-- SQL7:存在dist
select
? wind_speed,
? avg(rainfall)as cnt,
?count(distinct(county_name)) as avg1
from
?t_air_one_one_dist_1
group by
? city_name,
? county_name,
? wind_speed
order by
? cnt desc,
? avg1
limit? 10;