聯(lián)網(wǎng)數(shù)據(jù)庫 IoTDB —— 存儲引擎原理篇
IotDB簡介
Apache IoTDB(物聯(lián)網(wǎng)數(shù)據(jù)庫)是一體化收集、存儲、管理與分析物聯(lián)網(wǎng)時序數(shù)據(jù)的軟件系統(tǒng)。Apache IoTDB 采用輕量式架構(gòu),具有高性能和豐富的功能,并與Apache Hadoop、Spark和Flink等進(jìn)行了深度集成,可以滿足工業(yè)物聯(lián)網(wǎng)領(lǐng)域的海量數(shù)據(jù)存儲、高速數(shù)據(jù)讀取和復(fù)雜數(shù)據(jù)分析需求。
應(yīng)用場景
高端設(shè)備
在高端制造業(yè)中,有很多設(shè)備配備有傳感器來收集工作狀態(tài)數(shù)據(jù),例如氣象站,風(fēng)力渦輪機(jī)是常見的高端設(shè)備。這些設(shè)備如果支持Java或Go(正在開發(fā)中),則可以運(yùn)行TsFile在本地存儲數(shù)據(jù)。通過這種方式,TsFile可以提供具有高吞吐、高壓縮率和毫秒級查詢延遲的數(shù)據(jù)管理功能。結(jié)合TsFile-Sync工具,可以將TsFiles同步到數(shù)據(jù)中心。
本地控制器
在工廠現(xiàn)場,LAN網(wǎng)絡(luò)下有數(shù)十臺設(shè)備。IoTDB可以安裝在工廠的本地控制器服務(wù)器上,以從這些設(shè)備接收數(shù)據(jù)。安裝有IoTDB的本地服務(wù)器(普通PC或工作站)可以使用類SQL存儲和查詢數(shù)據(jù)。此外,使用TsFile-Sync工具,可以將本地控制器上的TsFile文件傳輸?shù)皆粕习惭b有IoTDB實(shí)例的數(shù)據(jù)中心。
云數(shù)據(jù)管理
在高速網(wǎng)絡(luò)(車聯(lián)網(wǎng)等)的場景中,安裝有傳感器的汽車可以以一定頻率收集自身的監(jiān)視信息(行駛狀態(tài)等)。通常,這些汽車設(shè)備的硬件配置有限,并且難以進(jìn)行復(fù)雜的應(yīng)用。輕量級的IoTDB(IoTDB客戶端)應(yīng)運(yùn)而生。借助JDBC API(或MQTT),它可以使用窄帶IoT或4G/5G發(fā)送數(shù)據(jù),從而將設(shè)備和云連接在一起。
存儲架構(gòu)
IoTDB 存儲引擎基于 LSM Tree 結(jié)構(gòu)設(shè)計,寫入的數(shù)據(jù)先記錄 WAL,再寫到內(nèi)存 memtable,在后臺逐步刷到磁盤 TsFile;磁盤上的 TsFile 通過一定的規(guī)則進(jìn)行 Compaction,保證查詢效率。
那么我們就先從LSM Tree 說起吧。
LSM Tree
LSM Tree(Log Structured Merge Tree) 并不像B+樹、紅黑樹一樣是一顆嚴(yán)格的樹狀數(shù)據(jù)結(jié)構(gòu),其實(shí)它是一種存儲結(jié)構(gòu),目前HBase,LevelDB,RocksDB這些NoSQL存儲都是采用的LSM樹。
Mentable
MemTable是在內(nèi)存中的數(shù)據(jù)結(jié)構(gòu),用于保存最近更新的數(shù)據(jù),會按照Key有序地組織這些數(shù)據(jù),LSM樹對于具體如何組織有序地組織數(shù)據(jù)并沒有明確的數(shù)據(jù)結(jié)構(gòu)定義,例如Hbase使跳躍表來保證內(nèi)存中key的有序。
因為數(shù)據(jù)暫時保存在內(nèi)存中,內(nèi)存并不是可靠存儲,如果斷電會丟失數(shù)據(jù),因此通常會通過WAL(Write-ahead logging,預(yù)寫式日志)的方式來保證數(shù)據(jù)的可靠性。
SSTable(Sorted String Table)
有序鍵值對集合,是LSM樹組在磁盤中的數(shù)據(jù)結(jié)構(gòu)。為了加快SSTable的讀取,可以通過建立key的索引以及布隆過濾器來加快key的查找。
我們在寫入數(shù)據(jù)時,首先將對數(shù)據(jù)的修改增量保存Memtable中,同時會提交wal,當(dāng)Memtable達(dá)到指定大小限制之后批量把數(shù)據(jù)刷到磁盤(SSTable)中,磁盤中樹定期可以做merge操作,合并成一棵大樹,以優(yōu)化讀性能。不過讀取的時候稍微麻煩一些,讀取時看這些數(shù)據(jù)在內(nèi)存中,如果未能命中內(nèi)存,則需要訪問較多的磁盤文件。極端的說,基于LSM樹實(shí)現(xiàn)的hbase寫性能比mysql高了一個數(shù)量級,讀性能卻低了一個數(shù)量級。
綜上所述,LSM樹會將數(shù)據(jù)的所有增、刪、改等操作,記錄到內(nèi)存中,再順序刷新到磁盤里,這就造成了其與B+樹最大的不同,B+樹會直接在數(shù)據(jù)的位置更新,而LSM樹則可能追到到不同的SSTable中,當(dāng)然,最新的那條記錄才是準(zhǔn)確的。這樣設(shè)計的雖然大大提高了寫性能,但同時也會帶來一些問題:
1)冗余存儲,對于某個key,實(shí)際上除了最新的那條記錄外,其他的記錄都是冗余無用的,但是仍然占用了存儲空間。因此需要進(jìn)行Compact操作(合并多個SSTable)來清除冗余的記錄。2)讀取時需要從最新的倒著查詢,直到找到某個key的記錄。最壞情況需要查詢完所有的SSTable,這里可以通過前面提到的索引/布隆過濾器來優(yōu)化查找速度。
壓縮(Compact)策略
在正式介紹壓縮策略以前,我們有必要先了解一下這3個基礎(chǔ)概念,這也是以下壓縮策略需要權(quán)衡的關(guān)鍵:讀放大(Read Amplifier)、寫放大(Write Amplifier)、空間放大(Space Amplifier)
- 讀放大(Read Amplifier):是指我們要找到一個我們所需的數(shù)據(jù),需要進(jìn)行多少次磁盤的讀操作。例如我們出門前需要找鑰匙,你可能要每個房間,每件衣服口袋翻一遍。這并不應(yīng)該是一個正常操作。
- 寫放大(Write Amplifier):寫入數(shù)據(jù)時實(shí)際寫入的數(shù)據(jù)量大于真正的數(shù)據(jù)量。例如在LSM樹中寫入時可能觸發(fā)Compact操作,導(dǎo)致實(shí)際寫入的數(shù)據(jù)量遠(yuǎn)大于該key的數(shù)據(jù)量。
- 空間放大(Space Amplifier):數(shù)據(jù)實(shí)際占用的磁盤空間比數(shù)據(jù)的真正大小更多。上面提到的冗余存儲,對于一個key來說,只有最新的那條記錄是有效的,而之前的記錄都是可以被清理回收的。
size-tiered 策略
size-tiered策略相對簡單粗暴,其主旨保證每層SSTable的大小相近,同時限制每層SSTable的數(shù)量。如下圖所示,每層限制SSTable為N,當(dāng)每層SSTable達(dá)到N后,則觸發(fā)Compact操作合并這些SSTable,并將合并后的結(jié)果寫入到下一層成為一個更大的sstable。
由此可以看出,當(dāng)層數(shù)達(dá)到一定數(shù)量時,最底層的單個SSTable的大小會變得非常大。并且size-tiered策略會導(dǎo)致空間放大比較嚴(yán)重。即使對于同一層的SSTable,每個key的記錄是可能存在多份的,只有當(dāng)該層的SSTable執(zhí)行compact操作才會消除這些key的冗余記錄。
leveled策略
leveled策略也是采用分層的思想,每一層限制總文件的大小。但與size-tiered策略不同的是,leveled會將每一層切分成多個大小相近的SSTable。這些SSTable是這一層是全局有序的,這意味著一個key在每一層至多只有1條記錄,不存在冗余記錄。并且每層維持“唯一一個” Run。我們來了解一下Run這個LSM里面重要的概念,以下摘自Wiki:
Each run contains data *sorted* by the index key. A run can be represented on disk as a single file, or alternatively as a collection of files with *non-overlapping* key ranges.
run需要滿足什么條件呢?第一sorted,第二non-overlapping key ranges
如下圖所示:
每層的大小是上一級的T(下圖是10) 倍,層數(shù)越高,data越多但是也越舊.每層都有target size
整個的過程可以簡化成:in memory的table寫滿了,那么就flush到第一級的Disk SStable并且做compaction,要是第一級又滿了,就又開始flush到第二級做compaction,以此類推直到max level, 下面幾張圖描述了整個過程。
假設(shè)如下圖是起始狀態(tài)
level0 有數(shù)據(jù)寫入,這個時候觸發(fā)level0到level1的compact
level1 超出限制,觸發(fā)level1到level2compact
此時會從level1中選擇至少一個文件,然后把它跟level2有交集的部分(非常關(guān)鍵)進(jìn)行合并。生成的文件會放在level2
由于level1第二SSTable的key的范圍覆蓋了level2中前三個SSTable,那么就需要將level1中第二個SSTable與level2中前三個SSTable執(zhí)行Compact操作。
level2合并完成后,如果其超出了level2閾值的限制,那么會觸發(fā)level2到level3的compact
以此類推,上一層達(dá)到閾值以后,就出觸發(fā)到下一層的compact操作。
值得一提的是,無論是同級還是不同級,不重疊的key,可以并行進(jìn)行。
簡單總結(jié),Level compaction目標(biāo)就是維持每個level都保持住one data sorted run,所以每個level都可以和下一個level做compaction,同時很有可能會被上一個level做compaction。這樣做好處就是level之間的compaction可以multithread來做(除了memory到level0),提高效率。
LSM Tree vs. B+ Tree vs. Hash
對比三種引擎的實(shí)現(xiàn):
- hash存儲引擎:哈希表持久化的實(shí)現(xiàn),可以快速支持增刪改查等隨機(jī)操作,且時間復(fù)雜度為o(1),但是不支持順序讀取掃描,對應(yīng)的存儲系統(tǒng)為k-v存儲系統(tǒng)的實(shí)現(xiàn)。
- b樹存儲引擎是b樹的持久化實(shí)現(xiàn),不僅支持單條記錄的增刪改查操作,還支持順序掃描,對應(yīng)的存儲系統(tǒng)就是mysql。
- lsm樹存儲引擎和b樹存儲引擎,一樣支持,增刪改查,也支持順序掃描操作。LSM犧牲了讀性能,提高寫性能。
Iotdb寫入流程
相關(guān)代碼
- org.apache.iotdb.db.engine.StorageEngine
負(fù)責(zé)一個 IoTDB 實(shí)例的寫入和訪問,管理所有的 StorageGroupProsessor。
- org.apache.iotdb.db.engine.storagegroup.StorageGroupProcessor
負(fù)責(zé)一個存儲組一個時間分區(qū)內(nèi)的數(shù)據(jù)寫入和訪問。管理所有分區(qū)的TsFileProcessor。
- org.apache.iotdb.db.engine.storagegroup.TsFileProcessor
負(fù)責(zé)一個 TsFile 文件的數(shù)據(jù)寫入和訪問。
這篇文章,我們先介紹到這里,下一篇文章再見。
參考鏈接
http://iotdb.apache.org/zh/SystemDesign/StorageEngine/StorageEngine.html
http://iotdb.apache.org/zh/SystemDesign/TsFile/Format.html
http://iotdb.apache.org/zh/SystemDesign/StorageEngine/DataPartition.html
https://www.scylladb.com/2018/01/31/compaction-series-leveled-compaction/
https://github.com/facebook/rocksdb/wiki/Leveled-Compaction
https://www.cnblogs.com/yankang/p/11041173.html
https://zhuanlan.zhihu.com/p/181498475
https://zhuanlan.zhihu.com/p/151234708
https://zhuanlan.zhihu.com/p/112574579
本文轉(zhuǎn)載自微信公眾號「麒思妙想」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系麒思妙想公眾號。