时序数据库技术体系 – InfluxDB TSM存储引擎之数据写入
之前兩篇文章筆者分別從TSM?File文件存儲格式、倒排索引文件存儲格式這兩個方面對InfluxDB最基礎(chǔ)、最底層也最核心的存儲模塊進行了介紹,接下來筆者會再用兩篇文章在存儲文件的基礎(chǔ)上分別介紹InfluxDB是如何處理用戶的寫入(刪除)請求和讀取請求的。在閱讀這兩篇文章之前,強烈建議看官先行閱讀之前的多篇文章,不然可能會有一定的閱讀障礙。
InfluxDB寫入總體框架
InfluxDB提供了多種接口協(xié)議供外部應(yīng)用寫入,比如可以使用collected采集數(shù)據(jù)上傳,可以使用opentsdb作為輸入,也可以使用http協(xié)議以及udp協(xié)議批量寫入數(shù)據(jù)。批量數(shù)據(jù)進入到InfluxDB之后總體會經(jīng)過三個步驟的處理,如下圖所示:
?
批量時序數(shù)據(jù)Shard路由
通常來說時序數(shù)據(jù)都會以批量的形式寫入數(shù)據(jù)庫,很少會像關(guān)系型數(shù)據(jù)庫那樣一條一條寫入,這對于追求高吞吐的時序系統(tǒng)來說至關(guān)重要。批量數(shù)據(jù)寫入InfluxDB之后做的第一件事情是分組,將時序數(shù)據(jù)點按照所屬shard劃分為多組(稱為Shard?Map),每組時序數(shù)據(jù)點將會發(fā)送給對應(yīng)的shard引擎并發(fā)處理。
這里我們簡單回顧下InfluxDB的Sharding策略(詳見文章《時序數(shù)據(jù)庫技術(shù)體系?–?初識InfluxDB》中Sharding策略一節(jié))。InfluxDB雖說是單機數(shù)據(jù)庫,但是每個表依然會被分為多個shard。簡單來說,InfluxDB中sharding屬于兩層sharding:首先按照時間進行Range?Sharding,即按時間分片,比如7天一個分片的話,最近7天的數(shù)據(jù)會分到一個shard,一周前到兩周前的數(shù)據(jù)會被分到上一個shard,以此類推;在時間分片的基礎(chǔ)上還可以再執(zhí)行Hash?Sharding,按照SeriesKey執(zhí)行Hash(保證同一個SeriesKey對應(yīng)的所有數(shù)據(jù)都落到同一個shard),再將數(shù)據(jù)分散到指定的多個shard中。
當(dāng)然,經(jīng)過筆者深進一步了解,發(fā)現(xiàn)單機InfluxDB只有第一層sharding,即只有根據(jù)時間進行Range?Sharding,并沒有執(zhí)行Hash?Sharding。Hash?Sharding只會在分布式InfluxDB中才會用到。
倒排索引引擎構(gòu)建倒排索引
InfluxDB中倒排索引引擎使用LSM引擎構(gòu)建,上篇文章《時序數(shù)據(jù)庫技術(shù)體系 – InfluxDB 多維查詢之倒排索引》其實已經(jīng)對引擎的工作原理進行了深入的介紹。這里重點將整個流程做一個串聯(lián)梳理,其中細(xì)節(jié)部分不會展開來講,有興趣的話可以參考上一篇文章。
這里首先思考一個問題:為什么InfluxDB倒排索引需要構(gòu)建成LSM引擎?其實很簡單,LSM引擎天生對寫友好,寫多讀少的系統(tǒng)第一選擇就是LSM引擎,所以大數(shù)據(jù)時代的各種數(shù)據(jù)存儲系統(tǒng)就是LSM引擎的天下,HBase、Kudu、Druid、TiKV這些系統(tǒng)無一不是這樣。InfluxDB作為一個時序數(shù)據(jù)庫更是寫多讀少的典型,無論倒排索引引擎還是時序數(shù)據(jù)處理引擎選用LSM引擎更是無可厚非。
既然是LSM引擎,工作機制必然是這樣的:首先將數(shù)據(jù)追加寫入WAL再寫入Cache就可以返回給用戶寫入成功,WAL可以保證即使發(fā)生異常宕機也可以恢復(fù)出來Cache中丟失的數(shù)據(jù)。一旦滿足特定條件系統(tǒng)會將Cache中的時序數(shù)據(jù)執(zhí)行flush操作落盤形成文件。文件數(shù)量超過一定閾值系統(tǒng)會將這些文件合并形成一個大文件。那具體到倒排索引引擎整個流程是什么樣的,簡單來看一下:
1.WAL追加寫入:Inverted?Index?WAL格式很簡單,由一個一個LogEntry構(gòu)成,如下圖所示:
每個LogEntry由Flag、Measurement、一系列Key\Value以及Checksum組成。其中Flag表示更新類型,包括寫入、刪除等,Measurement表示數(shù)據(jù)表,Key\Value表示寫入的Tag?Set以及Checksum,其中Checksum用于根據(jù)WAL回放數(shù)據(jù)時驗證LogEntry的完整性。注意,LogEntry中并沒有時序數(shù)據(jù)列,只有維度列(Tag?Set)。
?
2. Inverted Index在內(nèi)存中構(gòu)建
(1)拼SeriesKey:?時序數(shù)據(jù)寫入到系統(tǒng)之后先將measurement和所有的維度值拼成一個seriesKey
(2)確認(rèn)SeriesKey是否已經(jīng)構(gòu)建過索引:在文件中確認(rèn)該seriesKey是否已經(jīng)存在,如果已經(jīng)存在就忽略,不需要再將其加入到內(nèi)存倒排索引。那問題轉(zhuǎn)化為如何在文件中查找某個seriesKey是否已經(jīng)存在?這就是Series?Block中Bloom?Filter的核心作用,首先使用Bloom?Filter進行判斷,如果不存在,肯定不存在。如果存在,不一定存在,需要進一步判斷。再進一步使用B+樹以及HashIndex進一步查找判斷。
(3)如果seriesKey在文件中不存在,需要將其寫入內(nèi)存。倒排索引內(nèi)存結(jié)構(gòu)主要包含兩個Map:<measurement,?List<tagKey>>?和?<tagKey,?<tagValue,?List<SeriesKey>>>,前者表示時序表與對應(yīng)維度集合的映射,即這個表中有多少維度列。后者表示每個維度列都有哪些可枚舉的值,以及這些值都對應(yīng)哪些SeriesKey。InfluxDB中SeriesKey就是一把鑰匙,只有拿到這把鑰匙才能找到這個SeriesKey對應(yīng)的數(shù)據(jù)。而倒排索引就是根據(jù)一些線索去找這把鑰匙。
?
3.?Inverted?Index?Cache?Flush流程
(1)觸發(fā)時機:當(dāng)Inverted?Index?WAL日志的大小超過閾值(默認(rèn)5M),就會執(zhí)行flush操作將緩存中的兩個Map寫成文件
(2)基本流程:
- 緩存Map排序:<measurement,?List<tagKey>>以及<tagKey,?<tagValue,?List<SeriesKey>>都需要經(jīng)過排序處理,排序的意義在于有序數(shù)據(jù)可以結(jié)合Hash?Index實現(xiàn)范圍查詢,另外Series?Block中B+樹的構(gòu)建也需要SeriesKey排序。
- 構(gòu)建并持久化Series?Block:在排序的基礎(chǔ)上首先持久化<tagKey,?tagValue,?List<SeriesKey>>結(jié)構(gòu)中所有的SeriesKey,也就是先構(gòu)建Series?Block。依次持久化SeriesKey到SeriesKeyChunk,當(dāng)Chunk滿了之后,根據(jù)Chunk中最小的SeriesKey構(gòu)建B+樹中的Index?Entry節(jié)點。當(dāng)然,Hash?Index以及Bloom?Filter是需要實時構(gòu)建的。需要注意的是,Series?Block在構(gòu)建的同時需要記錄下SeriesKey與該Key在文件中偏移量的對應(yīng)關(guān)系,即<SeriesKey,?SeriesKeyOffset>,這一點至關(guān)重要。
- 內(nèi)存中將SeriesKey映射為SeriesId:將<tagKey,?<tagValue,?List<SeriesKey>>結(jié)構(gòu)中所有的SeriesKey由上一步中得到的<SeriesKey,?SeriesKeyOffset?>中的SeriesKeyOffset代替。形成新的結(jié)構(gòu):<tagKey,?<tagValue,?List<SeriesKeyOffset>>,即<tagKey,?<tagValue,?List<SeriesKeyId>>>,其中SeriesKeyId就是SeriesKeyOffset。
- 構(gòu)建并持久化Tag?Block:在新結(jié)構(gòu)<tagKey,?<tagValue,?List<SeriesKeyId>>>的基礎(chǔ)上首先持久化tagValue,將同一個tagKey下的所有tagValue持久化在一起并生成對應(yīng)Hash?Index寫入文件,接著持久化下一個tagKey的所有tagValue。所有tagValue都持久話完成之后再依次持久化所有的tagKey,形成Tag?Block。
- 構(gòu)建并持久化Measurement?Block:最后持久化measurement形成Measurement?Block。
時序數(shù)據(jù)寫入流程
時序數(shù)據(jù)的維度信息經(jīng)過倒排索引引擎構(gòu)建完成之后,接著就需要將數(shù)據(jù)寫入系統(tǒng)。和倒排索引引擎一樣,數(shù)據(jù)寫入引擎也是一個LSM引擎,基本流程也是先寫WAL,再寫Cache,最后滿足一定閾值條件之后將Cache中的數(shù)據(jù)flush到文件。
?
1. WAL追加寫入:時間線數(shù)據(jù)數(shù)據(jù)會經(jīng)過兩重處理,首先格式化為WriteWALEntry對象,該對象字段元素如下圖所示。然后經(jīng)過snappy壓縮后寫入WAL并持久話到文件。
?
?
2. 時序數(shù)據(jù)寫入內(nèi)存結(jié)構(gòu)
(1)時序數(shù)據(jù)點格式化:將所有時間序列數(shù)據(jù)點按時間線組織形成一個Map:<SeriesKey+FieldKey,?List<Value>>,即將相同Key(SeriesKey+FieldKey)的時序數(shù)據(jù)集中放在一個List中。
(2)時序數(shù)據(jù)點寫入Cache:InfluxDB中Cache是一個crude?hash?ring,這個ring由256個partition構(gòu)成,每個partition負(fù)責(zé)存儲一部分時序數(shù)據(jù)Key對應(yīng)的值。就相當(dāng)于數(shù)據(jù)寫入Cache的時候又根據(jù)Key?Hash了一次,根據(jù)Hash結(jié)果映射到不同的partition。為什么要這么處理?個人認(rèn)為有點像Java中ConcurrentHashMap的思路,將一個大HashMap切分成多個小HashMap,每個HashMap內(nèi)部在寫的時候需要加鎖。這樣處理可以減小鎖粒度,提高寫性能。
?
?
3. Data?Cache?Flush流程(參考engine.compactCache)
(1)觸發(fā)時機:Cache執(zhí)行flush操作有兩個基本觸發(fā)條件,其一是當(dāng)cache大小超過一定閾值,可以通過參數(shù)’cache-snapshot-memory-size’配置,默認(rèn)是25M大小;其二是超過一定時間閾值沒有時序數(shù)據(jù)寫入WAL也會觸發(fā)flush,默認(rèn)時間閾值為10分鐘,可以通過參數(shù)’cache-snapshot-write-cold-duration’配置。
(2)基本流程:在了解了TSM文件的基本結(jié)構(gòu)之后,我們再簡單看看時序數(shù)據(jù)是如何從內(nèi)存中的Map持久化成TSM文件的,整個過程可以表述為:
- 內(nèi)存中構(gòu)建Series?Data?Block:順序遍歷內(nèi)存Map中的時序數(shù)據(jù),分別對時序數(shù)據(jù)的時間列和數(shù)值列進行相應(yīng)的編碼,按照Series?Data?Block的格式進行組織,當(dāng)Block大小超過一定閾值就構(gòu)建成功。并記錄這個Block內(nèi)時間列的最小時間MinTime以及最大時間MaxTime。
- 將構(gòu)建好的Series?Data?Block寫入文件:使用輸出流將內(nèi)存中數(shù)據(jù)輸出到文件,并返回該Block在文件中的偏移量Offset以及總大小Size。
- 構(gòu)建文件級別B+索引:在內(nèi)存中為該Series?Data?Block構(gòu)建一個索引節(jié)點Index?Entry,使用數(shù)據(jù)Block在文件中的偏移量Offset、總大小Size以及MinTime、MaxTime構(gòu)建一個Index?Entry對象,寫入到內(nèi)存Series?Index?Block對象。
這樣,每構(gòu)建一個Series?Data?Block并寫入文件之后都會在內(nèi)存中順序構(gòu)建一個Index?Entry,寫入內(nèi)存Series?Index?Block對象。一旦一個Key對應(yīng)的所有時序數(shù)據(jù)都持久化完成,一個Series?Index?Block就構(gòu)建完成,構(gòu)建完成之后填充Index?Block?Meta信息。接著新建一個新的Series?Index?Block開始構(gòu)建下一個Key對應(yīng)的數(shù)據(jù)索引信息。
InfluxDB數(shù)據(jù)刪除操作(DropMeasurement,DropTagKey)
一般LSM引擎處理刪除通常都采用Tag標(biāo)記的方式,即刪除操作和寫入操作流程基本一致,只是數(shù)據(jù)上會多一個Tag標(biāo)記?–?deleted,表示該值已經(jīng)被deleted。這種處理方案可以最小化刪除代價,但萬物有得必有失,減小了寫入代價必然會增加讀取代價,Tag標(biāo)簽方案在讀取的時候需要對標(biāo)記有deleted的數(shù)值進行特殊處理,這個代價還是很大的。HBase中刪除操作就是采用Tag標(biāo)記方案。
InfluxDB比較奇葩,對于刪除操作處理的比較異類,通常InfluxDB不會刪除一條記錄,而是會刪除某段時間內(nèi)或者某個維度下的所有記錄,甚至一張表的所有記錄,這和通常的數(shù)據(jù)庫有所不同。比如:
?
DROP SERIES FROM h2o_feet WHERE location = ‘santa_monica' DELETE FROM "cpu" DELETE FROM "cpu" WHERE time < '2000-01-01T00:00:00Z' DELETE WHERE time < '2000-01-01T00:00:00Z'?
上文我們知道InfluxDB中一個shard有兩個LSM引擎,一個是倒排索引引擎(存儲維度列到SeriesKey的映射關(guān)系,方便多維查找),一個是TSM?Engine,用來存儲實際的時序數(shù)據(jù)。如果是刪除一條記錄,通常只需要TSM?Engine執(zhí)行刪除就可以,倒排索引引擎是不需要執(zhí)行刪除的。而如果是Drop?Measurement這樣的操作,那么兩個LSM引擎都需要執(zhí)行相應(yīng)的刪除。問題是,這兩個引擎的刪除策略完全不同,TSM?Engine采用了一種同步刪除策略,Inverted?Index?Engine采用了標(biāo)記刪除策略。如下圖所示:
1. TSM?Engine同步刪除策略,整個刪除流程可以分為如下四步:
(1)刪除所有TSM?File中滿足條件的series,系統(tǒng)會遍歷當(dāng)前shard中所有TSM?File,檢查該File中是否存在滿足刪除條件的File,如果有會執(zhí)行如下兩個操作:
- TSM?File?Index相關(guān)處理:在內(nèi)存中刪除滿足條件的Index?Entry,通常刪除會帶有Time?Range以及Key?Range,而且TSM?File?Index會在引擎啟動之后加載到內(nèi)存。因此刪除操作會將滿足條件的Index?Entry從內(nèi)存中刪除。
- 生成tombstoner文件:tombstoner文件會記錄當(dāng)前TSM?File中所有被刪除的時序數(shù)據(jù),時序數(shù)據(jù)用[key,?min,?max]三個字段表示,其中key即SeriesKey+FieldKey,[min,?max]表示要刪除的時間段。如下圖所示:
(2)刪除Cache中滿足條件的series
(3)在WAL中生成一條刪除series的記錄并持久化到硬盤
?
2. Inverted?Index?Engine?標(biāo)記Tag刪除策略,標(biāo)記Tag刪除非常簡單,和一次寫入流程基本相同:
(1)在WAL中生成一條flag為deleted的LogEntry并持久化到硬盤
(2)將要刪除的維度信息寫入Cache,需要標(biāo)記deleted(設(shè)置type=deleted)
(3)當(dāng)WAL大小超過閾值之后標(biāo)記為deleted的維度信息會隨Cache?Flush到倒排索引文件
(4)和HBase一樣,Inverted?Index?Engine中索引信息真正被刪除發(fā)生在compact階段
總結(jié)
InfluxDB因為其特有的雙LSM引擎而顯得內(nèi)部結(jié)構(gòu)更加復(fù)雜,寫入流程相比其他數(shù)據(jù)庫來說更加繁瑣。但只要理解了它的數(shù)據(jù)文件內(nèi)部組織格式以及倒排索引文件內(nèi)部組織格式,相信對于整體的把握也并不是很難。這篇文章將之前講過的相關(guān)知識點通過寫入流程系統(tǒng)地串聯(lián)了起來,希望看官能夠借此深入理解InfluxDB的工作原理。
總結(jié)
以上是生活随笔為你收集整理的时序数据库技术体系 – InfluxDB TSM存储引擎之数据写入的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 初窥Linux 之 我最常用的20条命令
- 下一篇: Mysql写入数据时,adapter 日