環境: CentOS6.5_x64
InfluxDB版本:1.1.0
InfluxDB存儲引擎看起來很像一個LSM Tree,它包含預寫日志和類似存儲在LSM Tree中的SSTables只讀數據。 TSM文件包含已經排好序而且經過壓縮的序列化數據。
InfluxDB會為每個時間塊創建一個分區。例如,如果你有一個沒有時間限制的存儲策略,會以7天為時間塊來創建分區。 這些分區會映射到底層數據庫存儲引擎。 每個數據庫會有自己的WAL文件和TSM文件。
LSM Tree
如果要讓寫性能最優,最佳的實現方式是以追加的模式寫磁盤文件。
如果要優化讀性能,常見的幾種優化措施如下:
- 二分查找
將數據以key的方式排好序,然后存儲在文件中,通過二分查找的方式查找數據。
- Hash
將數據經過hash后放入特定的位置,以后可以通過哈希值直接讀取。
- B+ Tree
使用B+樹來組織數據,將數據完全排序,讀取時會非常快。 一般來說,索引本身也很大,不可能全部存儲在內存中,因此索引往往以索引文件的形式存儲的磁盤上。 所以對於數據庫, 基本都是用B+樹作為索引機制, 而沒有用二叉樹或他的變種紅黑樹的。
- 使用額外的索引文件
除了本身存儲的數據外,另外對其單獨建立索引以加速讀取。
二分查找要求數據完全排序,顯然會影響數據庫的寫入性能;
對於數據庫而言,因為要考慮到range查詢, 要使用樹系列(二叉樹, 紅黑樹, B樹, B+樹),所以hash索引不行;
B+樹將數據完全排序,讀取時很快,但是當要修改數據時,就需要將新入數據下面的數據重新排序,所以面對海量的隨機更新,B+樹的性能會明顯下降;
而使用外部索引的話,寫數據時需要維護維護索引,根本無法抵御隨機更新操作;
InfluxDB采用的是LSM Tree(Log Structured Merge Trees)存儲結構,這種數據組織方式被應用於多種數據庫,如Big Table、HBase、Cassandra、LevelDB等。
其實現思路和以上四中措施不太相同,它將隨機寫轉換為順序寫,盡量保持日志型數據庫的寫性能優勢,並提供相對較好的讀性能。
具體實現如下:
1、當有寫操作(含insert、update)時,先把數據寫入內存,內存中通過某種數據結構(如skiplist)保持key有序,
2、同時將數據追寫到磁盤Log文件中,以備必要時恢復;
3、內存數據定時(或按固定大小)排序后刷到磁盤,存儲到磁盤的數據文件不可寫且有序;
4、定時合並文件,將小樹合並為大樹,消除冗余數據,減少文件數量,優化查詢速度;
LSM Tree存儲結構執行寫操作時,只需更新內存,硬盤的那份數據只是簡單的追加操作,所以能處理大量隨機更新操作;
LSM Tree存儲結構執行讀操作時,先從內存數據開始訪問,如果在內存中訪問不到, 再順序從一個個磁盤文件中查找,由於文件本身有序,並且定期的合並減少了磁盤文件個數,因而查找過程相對較快速。
由於時序數據庫對寫入的速度要求比較高,而讀取一般是一段時間范圍內的數據,使用LSM Tree算法非常合適。
LSM Tree 描述:
http://www.benstopford.com/2015/02/14/log-structured-merge-trees/ http://blog.fatedier.com/2016/06/15/learn-lsm-tree/
存儲引擎模塊
存儲引擎將一些組件捆綁在一起並且提供一些額外的接口用來存儲和查詢序列化數據。
它由以下這些組件構成,每個組件扮演不同的角色:
- 內存索引
內存索引是分區的索引,主要用於快速訪問measurements,tags和series。
- 預寫日志
預寫日志是write-optimized存儲格式的數據,允許寫入持久化,但不易查詢,寫入時執行附加操作。
文件前綴: _
文件擴展名 : .wal
文件名稱看起來大概是這樣: _000001.wal
文件名字中的數字是遞增的,它是WAL段的索引。
當一個WAL段的大小大於10M時( DefaultSegmentSize = 10 * 1024 * 1024),它會關閉當前段並且創建一個新的段。
每個WAL段存儲多個經壓縮的寫和刪除的操作指令。
┌───────────────────────────────────────────────────────────┐ │ WALEntry │ ├──────────────┬──────────┬──────────┬───┬─────────────┬────┤ │ WALEntryType │ Data Len │ Data │...│WALEntryType │... │ │ 1 byte │ 4 bytes │ N bytes │ │ 1 byte │ │ └──────────────┴──────────┴──────────┴───┴─────────────┴────┘
WALEntryType : 用於標記數據操作類型(寫入、刪除、連續刪除)
const ( WriteWALEntryType WalEntryType = 0x01 DeleteWALEntryType WalEntryType = 0x02 DeleteRangeWALEntryType WalEntryType = 0x03 )
Data len : 后面數據的長度
Data :經過snappy壓縮后的數據
相關配置參數可以在文件中查看:
influxdb-1.1.0/tsdb/engine/tsm1/wal.go
WAL文件中的數據存儲結構如下:
┌────────────────────────────────────────────────────────────────────┐ │ WriteWALEntry │ ├──────┬─────────┬────────┬───────┬─────────┬─────────┬───┬──────┬───┤ │ Type │ Key Len │ Key │ Count │ Time │ Value │...│ Type │...│ │1 byte│ 2 bytes │ N bytes│4 bytes│ 8 bytes │ N bytes │ │1 byte│ │ └──────┴─────────┴────────┴───────┴─────────┴─────────┴───┴──────┴───┘
字段描述如下:
Type(1 byte) : 表示value的類型(支持的類型有:浮點型,整型,布爾型,字符串型)
Key Len(2 bytes) : 指定緊跟其后的Key的長度
Key(N bytes) :key內容
Count(4 bytes) :后面跟的(Time + Value作為一個整體)數據的個數
Time(8 bytes) :單個value的時間戳
Value(N bytes) :需要存儲的數據(支持的類型有:浮點型,整型,布爾型,字符串型)
當Value為字符串類型時,Value 由於兩部分構成:字符串長度(4Bytes) + 字符串內容(長度由前面決定)
case *StringValue: if curType != stringEntryType { return nil, fmt.Errorf("incorrect value found in %T slice: %T", v[0].Value(), vv) } binary.BigEndian.PutUint32(dst[n:n+4], uint32(len(vv.value))) n += 4 n += copy(dst[n:], vv.value)
- 緩存
緩存是存儲在WAL文件中的數據在內存中的一份拷貝,數據按key的方式組織,未壓縮。
系統重啟時,緩存會通過讀取磁盤的WAL文件恢復。
- TSM文件
TSM文件是Influxdb中最終存儲數據的載體,整體結構如下:
┌────────┬────────────────────────────────────┬─────────────┬──────────────┐ │ Header │ Blocks │ Index │ Footer │ │5 bytes │ N bytes │ N bytes │ 8 bytes │ └────────┴────────────────────────────────────┴─────────────┴──────────────┘
由四部分組成: Header,Blocks,Index,Footer
Header用於標識文件類型及版本號,Blocks用於存儲數據,Index為Blocks的索引信息,Footer用於標識Index在TSM文件中的偏移量,便於快速訪問。
Header結構如下:
┌───────────────────┐ │ Header │ ├─────────┬─────────┤ │ Magic │ Version │ │ 4 bytes │ 1 byte │ └─────────┴─────────┘
Magic用於標識存儲引擎類別,Version用於記錄版本號,在influxdb1.1版本中定義如下:
MagicNumber uint32 = 0x16D116D1 Version byte = 1
Blocks結構如下:
┌───────────────────────────────────────────────────────────┐ │ Blocks │ ├───────────────────┬───────────────────┬───────────────────┤ │ Block 1 │ Block 2 │ Block N │ ├─────────┬─────────┼─────────┬─────────┼─────────┬─────────┤ │ CRC │ Data │ CRC │ Data │ CRC │ Data │ │ 4 bytes │ N bytes │ 4 bytes │ N bytes │ 4 bytes │ N bytes │ └─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘
Blocks由多個Block構成,每個Block包含CRC32和Data兩部分,其中CRC32用於校驗Data的內容是否有問題, Data為存儲的數據,Data的長度記錄在之后的Index部分中。
Index結構如下:
┌────────────────────────────────────────────────────────────────────────────┐ │ Index │ ├─────────┬─────────┬──────┬───────┬─────────┬─────────┬────────┬────────┬───┤ │ Key Len │ Key │ Type │ Count │Min Time │Max Time │ Offset │ Size │...│ │ 2 bytes │ N bytes │1 byte│2 bytes│ 8 bytes │ 8 bytes │8 bytes │4 bytes │ │ └─────────┴─────────┴──────┴───────┴─────────┴─────────┴────────┴────────┴───┘
Kye Len (2 bytes) : 代表緊隨其后的key的長度
Key (N bytes) :Key的內容
Type :數據類型
influxdb-1.1.0/tsdb/engine/tsm1/encoding.go ... const ( // BlockFloat64 designates a block encodes float64 values BlockFloat64 = byte(0) // BlockInteger designates a block encodes int64 values BlockInteger = byte(1) // BlockBoolean designates a block encodes boolean values BlockBoolean = byte(2) // BlockString designates a block encodes string values BlockString = byte(3) // encodedBlockHeaderSize is the size of the header for an encoded block. There is one // byte encoding the type of the block. encodedBlockHeaderSize = 1 )
Count(2 bytes) : 后面跟的(Min Time + Max Time + Offset + Size作為一個整體)數據的個數
Min Time(8 bytes) : block中value的最小時間戳
Max Time(8 bytes) : block中value的最大時間戳
Offset(8 bytes) : 該block在tsm文件中的偏移量
Size(4Bytes) : block的大小
Footer結構如下:
┌─────────┐ │ Footer │ ├─────────┤ │Index Ofs│ │ 8 bytes │ └─────────┘
tsm文件示例:
16 D1 16 D1 01 2C 30 A0 35 00 09 1C 13 E6 C9 EF 89 2E E4 00 10 3F E4 7A E1 47 AE 14 7B C3 F4 01 C7 AE 14 7A E1 47 A0 A6 71 30 0F 00 09 1C 13 E6 C9 F4 31 46 AC 00 10 3F EF AE 14 7A E1 47 AE C3 FC 01 7A E1 47 AE 14 7A F0 00 34 63 70 75 5F 6C 6F 61 64 5F 73 68 6F 72 74 2C 68 6F 73 74 3D 73 65 72 76 65 72 30 31 2C 72 65 67 69 6F 6E 3D 75 73 2D 77 65 73 74 23 21 7E 23 76 61 6C 75 65 00 00 02 13 E6 C9 EF 89 2E E4 00 13 E6 C9 EF 89 2E E4 00 00 00 00 00 00 00 00 05 00 00 00 22 13 E6 C9 F4 31 46 AC 00 13 E6 C9 F4 31 46 AC 00 00 00 00 00 00 00 00 27 00 00 00 22 00 00 00 00 00 00 00 49
參考代碼:
curl -i -XPOST http://localhost:8086/query --data-urlencode "q=CREATE DATABASE mydb" curl -i -XPOST 'http://localhost:8086/write?db=mydb' --data-binary 'cpu_load_short,host=server01,region=us-west value=0.64 1434055562000000000'
Header部分 : 16 D1 16 D1 01
Blocks部分 :
2C 30 A0 35 00 09 1C 13 E6 C9 EF 89 2E E4 00 10 3F E4 7A E1 47 AE 14 7B C3 F4 01 C7 AE 14 7A E1 47 A0 A6 71 30 0F 00 09 1C 13 E6 C9 F4 31 46 AC 00 10 3F EF AE 14 7A E1 47 AE C3 FC 01 7A E1 47 AE 14 7A F0
Index部分 :
00 34 63 70 75 5F 6C 6F 61 64 5F 73 68 6F 72 74 2C 68 6F 73 74 3D 73 65 72 76 65 72 30 31 2C 72 65 67 69 6F 6E 3D 75 73 2D 77 65 73 74 23 21 7E 23 76 61 6C 75 65 00 00 02 13 E6 C9 EF 89 2E E4 00 13 E6 C9 EF 89 2E E4 00 00 00 00 00 00 00 00 05 00 00 00 22 13 E6 C9 F4 31 46 AC 00 13 E6 C9 F4 31 46 AC 00 00 00 00 00 00 00 00 27 00 00 00 22
00 34 : 后面的52個字符為key
key內容:
63 70 75 5F 6C 6F 61 64 5F 73 68 6F 72 74 2C 68 6F 73 74 3D 73 65 72 76 65 72 30 31 2C 72 65 67 69 6F 6E 3D 75 73 2D 77 65 73 74 23 21 7E 23 76 61 6C 75 65
data類型:0x00
count : 00 02
后面有兩個數據
第一個數據:
13 E6 C9 EF 89 2E E4 00 13 E6 C9 EF 89 2E E4 00 00 00 00 00 00 00 00 05 00 00 00 22
這個時間段(13 E6 C9 EF 89 2E E4 00 - 13 E6 C9 EF 89 2E E4)的數據在5( 00 00 00 00 00 00 00 00 05)這個地方存,占用34(00 00 00 22)個byte
第二個數據:
13 E6 C9 F4 31 46 AC 00 13 E6 C9 F4 31 46 AC 00 00 00 00 00 00 00 00 27 00 00 00 22
Footer部分 : 00 00 00 00 00 00 00 49
TSM存儲設計可參考如下文件:
influxdb-1.1.0/tsdb/engine/tsm1/DESIGN.md
- 文件存儲器
在替換和刪除TSM文件時,它確保TSM文件創建的原子性。
- 壓縮規划器
檢測那個TSM文件可以壓縮,並確保多個並發的壓縮互不干擾。
- 壓縮器
主要用於文件壓縮,優化存儲空間。
- 讀寫器
每種文件類型(WAL,TSM,tombstones等)都分別擁有自己格式的讀寫器。
好,就這些了,希望對你有幫助。
本文github地址:
https://github.com/mike-zhang/mikeBlogEssays/blob/master/2017/20170423_Influxdb數據存儲描述.rst
歡迎補充
