目前,數(shù)據(jù)庫(kù)的存儲(chǔ)引擎可以粗略分為兩大類:一類是基于 B-Tree 的,另一類是基于 LSM Tree 的。前者常見于傳統(tǒng) OLTP 數(shù)據(jù)庫(kù),比如 MySQL、PQ 這類的默認(rèn)引擎,更適用于讀多寫少的場(chǎng)景;如 HBase、LevelDB、RocksDB 一類 Database 使用的是 LSM Tree,在寫多讀少的場(chǎng)景下比較適合。實(shí)際上,現(xiàn)代數(shù)據(jù)庫(kù)的存儲(chǔ)引擎,基本都會(huì)在某種程度下對(duì)這兩者融合。LSM Tree 上怎么就不可以建 B-Tree Index 了?(HBase 在 region 上也有 B-Tree Index)B-Tree 怎么就一定要直寫硬盤,不能先寫 WAL 和走內(nèi)存 Cache 呢?
對(duì)于存儲(chǔ)引擎,時(shí)序數(shù)據(jù)庫(kù) Time Series Database(TSDB) 的先行者 InfluxDB 曾經(jīng)做過(guò)很多嘗試,在各個(gè)存儲(chǔ)引擎(LevelDB、RocksDB、BoltDB 等)之間反復(fù)橫跳,遇到過(guò)的問(wèn)題也有很多,比如 BoltDB 中 mmap+BTree 模型中隨機(jī) IO 導(dǎo)致的吞吐量低、RocksDB 這類純 LSM Tree 存儲(chǔ)引擎沒辦法很優(yōu)雅快速地按時(shí)間分區(qū)刪除、多個(gè) LevelDB + 劃分時(shí)間分區(qū)的方法又會(huì)產(chǎn)生大量句柄……踩了這一系列的坑后,最終 InfluxDB 換成了自研的存儲(chǔ)引擎 TSM??梢妼?duì) TSDB 來(lái)說(shuō),一個(gè)好的存儲(chǔ)引擎有多么重要,又是多么難得,要想做到極致,還得自己研發(fā)。
同為時(shí)序數(shù)據(jù)庫(kù)Time Series Database,不同于 InfluxDB 的是,TDengine 從一開始就是自研的——從 LSM Tree 中汲取了 WAL、先寫內(nèi)存的 skip list 等技術(shù),但把 LSM Tree 的樹層級(jí)結(jié)構(gòu)去掉了,而只是按時(shí)間段分區(qū)、按表分塊的 log 塊。
讀到這里,細(xì)心的讀者可能會(huì)發(fā)現(xiàn),按表分塊的設(shè)計(jì)和 OpenTSDB 的行聚合有些相似。 OpenTSDB 的行聚合是把相同 tag 以一小時(shí)為時(shí)間范圍,將這些數(shù)據(jù)都放到一行中存儲(chǔ),這樣大大減少了聚合查詢要掃描的數(shù)據(jù)量。不過(guò)不同的是,TDengine 是多列模型,而 OpenTSDB 是單列模型,單列模型下是多行的聚合,多列模型下聚合會(huì)自然形成數(shù)據(jù)塊。
而熟悉 LSM Tree 的 KV 分離設(shè)計(jì)的朋友應(yīng)該也能夠從 TDengine 的存儲(chǔ)引擎設(shè)計(jì)中看到一些熟悉的影子。如果把數(shù)據(jù)塊作為 TSDB 存儲(chǔ)引擎的 value,那么 key 就應(yīng)該是塊的起止時(shí)間 ,把 key 提出來(lái)自然就得到了 TDengine 的 BRIN 索引。從這種視角來(lái)看,TDengine 的 .head 文件就是 key,而 .data 和 .last 文件就是 value,而 key 自身又可以結(jié)合時(shí)間序列數(shù)據(jù)的特征組合成有序文件。 在時(shí)序場(chǎng)景下,有了 BRIN 索引,也就可以不需要 bloom filter,這樣一看,TDengine 的存儲(chǔ)引擎設(shè)計(jì)就很清晰了。
此外,TDengine 會(huì)將 tag 數(shù)據(jù)和時(shí)間序列數(shù)據(jù)分離開來(lái),這樣就能夠大大減少 tag 數(shù)據(jù)占用的存儲(chǔ)空間,在數(shù)據(jù)量大的情況下尤其顯著。
TDengine 的 tag 與時(shí)間序列數(shù)據(jù)的劃分,和數(shù)倉(cāng)的維度建模里面維度表與事實(shí)表的劃分有些類似,tag 數(shù)據(jù)類似維度表,而時(shí)間序列數(shù)據(jù)類似事實(shí)表。但又有所不同,因?yàn)?TDengine 中表的數(shù)目是和設(shè)備數(shù)目相同的,上億設(shè)備就是上億張表,這樣頻繁創(chuàng)建、又極其龐大的表,并不容易處理,主要的麻煩是其產(chǎn)生了大量的元數(shù)據(jù),超過(guò)了單點(diǎn)的處理能力,這就要求 TDengine 能將這部分元數(shù)據(jù)也進(jìn)行分片存儲(chǔ)。
當(dāng)數(shù)據(jù)與元數(shù)據(jù)進(jìn)行分片、多副本操作時(shí),就自然涉及到一致性與可用性的問(wèn)題。在 TSDB 中,時(shí)間序列數(shù)據(jù)通常是最終一致同步的,因?yàn)樽罱K一致算法的吞吐量高延遲低、可用性也比強(qiáng)一致算法好,比如InfluxDB集群版會(huì)用 Dynamo 這種無(wú)主風(fēng)格的數(shù)據(jù)同步。但元數(shù)據(jù)(也就是我們上面提到的標(biāo)簽和表數(shù)據(jù))需要強(qiáng)一致,強(qiáng)一致通常會(huì)用 Raft、Paxos 這類算法來(lái)保證正確性。
由于元數(shù)據(jù)量的巨大需要分片,而當(dāng)時(shí)序數(shù)據(jù)與元數(shù)據(jù)都做分片(甚至?xí)r間序列數(shù)據(jù)和其關(guān)聯(lián)的元數(shù)據(jù)應(yīng)該在同一分片),但又有截然不同的一致性要求,這就導(dǎo)致 TDengine 的副本復(fù)制并不是簡(jiǎn)單地使用 Raft 這類算法就能夠駕馭得了的,除非犧牲時(shí)序數(shù)據(jù)的寫入吞吐和可用性,也做強(qiáng)一致復(fù)制。這就是 TDengine 使用自研復(fù)制算法的根本原因。當(dāng)然,這些算法在復(fù)雜的實(shí)時(shí)數(shù)據(jù)庫(kù)分布式環(huán)境下的一致性保證又是另外的問(wèn)題了,也是我們要著重解決的挑戰(zhàn)。



互聯(lián)網(wǎng).png)



-1.png)




.png)


證.png)


伙伴.png)
伙伴.png)
伙伴.png)



