最近公司有個需求需要借助InfluxDB實現(或者更准確的說,使用該數據庫可以更容易的實現),因此稍微看了下這個數據庫,把比較重要的一些東西先簡單記錄一下,日后如果踩坑,也會繼續在下面補充。
零、下載&安裝
官方地址:https://portal.influxdata.com/downloads/
一、什么是時序數據庫,它可以用來做什么?
簡單來說時序數據庫就是存儲帶有時間戳且包含隨時間發生變化的數據,InfluxDB屬於一種時序數據庫。這類數據具體指什么數據呢?這里舉幾個例子:
①監控,比如某一時段CPU占用率、服務QPS、服務耗時等監控數據
②某飛機在運行過程中各時刻的高度、速度、機艙內溫度、濕度
③某地區每時每刻的室外溫度記錄
上述的數據都滿足某一個具體的事物(監控指標、飛機、地區)隨着時間的變化而需要記錄的一些數據(監控數據、速度、溫度等),這些數據被稱為時序性數據,這些數據可以被用來做大數據分析、人工智能等。
疑問點:通過上述的例子,這些無非是一些指標數據嘛,為什么非得用時序性數據庫記錄?不可以用Mysql這種關系型數據庫來做嗎?
不可以,首先對於這種數據,量級上是巨大的,比如每時每刻都要記錄某飛機的坐標變化,類似這種指標,量級很龐大,關系型數據庫設計之初並不是為了處理這種大數據量級的,即便是后來的分表分庫,也是為了優化數據量和訪問量大了以后查詢和寫入性能問題的,所以不適合用來存儲數量如此龐大的數據,所以這並不是能不能實現的問題,而是適不適合的問題,而時序型數據庫的寫入速度非常快,其次內部還會對存儲數據進行壓縮,同時提供了更高的可用性,比如數據保留策略、數據聚合函數、連續查詢等。數據保留策略可以保證超過指定期限的數據被回收,釋放磁盤空間。
二、InfluxDB基本概念
2.1:建表
不需要專門建表,一般insert執行過去時,如果發現表不存在,就自動創建。
2.2:基本概念與mysql中概念的對應關系
database | 數據庫 | 類似mysql里的庫 |
measurement | 表 | 類似mysql里的表 |
point | 一行數據記錄 | 類似mysql里的一行數據 |
2.3:point詳解
point的概念就類似於mysql里一行數據(data point,直譯為數據點,可以理解成一行數據),point的構成部分有三個:
time | 時間戳 | influxdb自帶字段,單位:納秒 |
tags | 有各種索引的屬性 | 可設置多個,逗號隔開 |
fields | 沒有索引的屬性 | 可設置多個,逗號隔開 |
tags和fileds的區別:
1. field無索引,一般表示會隨着時間戳而發生改變的屬性,比如溫度、經緯度等,類型可以是 string, float, int, bool
2. tag有索引,一般表示不會隨着時間戳而發生改變的屬性,比如城市、編號等,類型只可為 string
比較值得注意的是:在InfluxDB中,tags決定了series的數量,而series的數量越大,對於系統CPU和InfluxDB的性能影響越大,series估算方法下面會說。
2.4:sql語句示范
建庫:
create database "庫名"
一般來說,新建完庫,還要對該庫設置自己的時效策略(RP),下面會詳細說明RP的設置方法,一般建好庫后都會有一個默認的RP策略
插入數據:
insert results,hostname=index2,name=index2333 value=2,value2=4
---------- ---------------------------------------- -------------------------
表名 tags的設置 fields的設置
上面這句sql的意思就是說在名為result的表里,插入兩個分別命名為hostname和name取值分別為index2和index2333的tags,以及兩個分別命名為value和value2取值分別為2和4的fields的數據。
注意:這里的指令直接輸入整數,influxDB是按照浮點型處理的,如果一定要讓上面的value=2和value2=4中的2和4是整型數據,那么需要在后面加上修飾詞:value=2i,i就代表整型。
檢索數據:
select * from results
打印結果為:
name: results2
time hostname name value value2
---- -------- ---- ----- ------
1560928150794920700 index2 index2333 2 4
分頁檢索:
SELECT * FROM 表 WHERE 條件 LIMIT rows OFFSET (page - 1)*rows
rows代表每頁展示行數,page表示頁碼
刪除表:
drop measurement "表名"
三、InfluxDB的時效設置
很遺憾,InfluxDB是不支持刪除和修改的,刪除有專門的操作,但是性能很低,不建議使用。
InfluxDB支持定期清除數據策略。
查看當前庫對應策略配置的命令為:
show retention policies on "庫名"
結果:
name duration shardGroupDuration replicaN default
------- --------- ------------------ --------- -------
autogen 0s 168h0m0s 1 true
name:過期策略名
duration:保留多長時間的數據,比如3w,意思就是把三周前的數據清除掉,只保留三周內的,支持h(小時),d(天),w(星期)這幾種配法。
shardGroupDuration:存儲數據的分組結構,比如設置為1d,表示的是每組存儲1d的數據量,也就是說數據將按照天為單位划分存儲分組,然后根據每條數據的時間戳決定把它放到哪個分組里,因此這個概念還會影響到過期策略,因為InfluxDB在清除過期數據時不可能逐條清理,而是通過清除整個ShardGroup的方式進行,因為通過跟當前時間對比就可以知道哪些分組里的數據一定是過期的,從而進行整組清理,效率往往更高。
replicaN:副本數量,一般為1個(這個大概就是備份的意思?這個配置在單實例模式下不起作用)
default:是否是默認配置,設置為true表示默認的意思,主要用於查詢時是否指定策略,如果不指定,則這個查詢就是針對default=true的策略進行的,注意,本文章里的sql都沒有指明保留策略的名字,如果有需要,那么請指定。一個庫允許有多套策略配置(每套策略里都可以有自己的一份數據,比如同樣一張表的數據在策略A和策略B的情況下是不同的,可以理解為一個庫對應N個策略,每個策略里有自己的N多張表,相互獨立),在不指定策略名稱的情況下寫的sql,默認使用default=true的策略。
當然,策略也支持修改,指令如下:
alter retention policy "autogen" on "test" duration 30d default
上面這條指令就會把上面策略明為autogen的策略有效保存時間改為30天。
四、InfluxDB的存儲結構
4.1:結構層級
了解完策略,結合上面提到的series、tags、fields等概念,畫一下單個InfluxDB庫的存儲結構:
圖1
圖里沒有體現出tags、fields這些數據層面的東西,它們最終被存放在了Shard里,之前說過tags會影響series的大小,其實series就相當於一個唯一化分類,series估算方式為:
series的個數 = RP × measurement × tags(tags去重后的個數)
比如一個database中有一個measurement,叫test,有兩個RP(7d, 30d),tag有host,server。host的值有hostA、hostB,server為server1,server2。那么這個database的series值為2RP x 4tags = 8
4.2:LSM-Tree
回歸圖1前,先來了解下LSM-Tree,幾乎所有的k-v存儲的寫密集型數據庫都采用該數據結構實現,該數據結構的結構如下(注意下面說的層級並非樹的層級,而是合並邏輯發生時的層級):
圖2
如圖2,該結構寫入流程如下:
寫入的數據首先加到0層,0層的數據存儲在內存中,短期內查詢的數據一般在0層,由於是內存操作,因此效率會非常高,當0層的數據達到一定大小時,此時會把0層 和位於它下面的1層進行合並,然后合並出來的新的數據(0層+1層的數據)會順序寫磁盤(這里由於是順序寫入磁盤,因此寫性能會非常好),然后替換掉原來老的1層數據,當1層達到一定大小的時候,將繼續和它的下層合並,以此類推,一級一級的往下遞,除此之外還可以將合並之后舊文件全部刪掉,留下最新的。
LSM-Tree參考文章:LSM-tree 基本原理及應用
然后回歸圖1,最終的數據會被存儲進Shard,Shard里存在幾個區域,就是最終存放數據的地方,下面針對這幾個區域說明下它們的作用:
4.3:Cache
相當於上面說的LSM-Tree的0層,存放於內存中,寫入的數據時首先被該模塊接收並存儲,該模塊在內存中表現為一個map結構,k = seriesKey + 分隔符 + fieldsName,v = 具體的filed對應的值的數組,具體結構體如下(參考網上資料):
type Cache struct {
commit sync.Mutex
mu sync.RWMutex
store map[string]*entry
size uint64 // 當前使用內存的大小
maxSize uint64 // 緩存最大值
// snapshots are the cache objects that are currently being written to tsm files
// they're kept in memory while flushing so they can be queried along with the cache.
// they are read only and should never be modified
// memtable 快照,用於寫入 tsm 文件,只讀
snapshot *Cache
snapshotSize uint64
snapshotting bool
// This number is the number of pending or failed WriteSnaphot attempts since the last successful one.
snapshotAttempts int
stats *CacheStatistics
lastSnapshot time.Time
}
基於前面說的LSM-Tree,可以知道這里的cache不是持續增長的,而是達到一定值就會進行跟下層存儲在磁盤上的數據(1~n層)進行合並,然后清空cache,在InfluxDB中,這一部分存儲在磁盤上的數據,就是指TSM File模塊,下面會介紹。
4.4:WAL
在上面對於Cache的描述中,Cache是基於內存做的寫入數據接收方,那么如果中途機器宕掉,那么就會造成Cache里數據丟失的問題,為了解決這個問題,InfluxDB就設計了WAL模塊,實際在寫入一個數據時,不僅會先寫進Cache,還會寫入WAL,可以簡單理解WAL就是對Cache里數據的備份,防止數據丟失,在Cache做完一次合並清除掉自身時,舊的WAL文件也會隨之刪除,然后新建一個WAL,迎接新一輪的寫入。同樣的,在InfluxDB啟動時,也會先去讀取WAL文件初始化Cache模塊。
4.5:TSM File
用於組成TSM-Tree結構的主要磁盤文件(可以對應圖2的1~n層),內部做了很多存儲以及壓縮優化,單個TSM File的最大大小為2GB。
4.6:Compactor
在后台持續運行的一個task(頻率為1s),主要做以下事情:
①在Cache達到閾值時,進行快照,然后將數據合並並保存在TSM File中
②合並TSM File,將多個小型TSM File進行合並,使得每個文件的大小盡可能達到單個文件最大大小(也就是上面說到的2GB)
③檢查並刪除一些已關閉的WAL文件
五、具體案例,以及實際的存儲目錄
結合圖1的結構以及圖2的數據結構,再加上對shard內部各組件的介紹,下面通過一個實際的例子來探索下InfluxDB實際的存儲目錄。
現在創建出來一個叫style_rank的庫,且設置默認RP為7d,保存周期為7天,默認分組策略為24h分一次,如圖:
圖3
圖中名為7d的rp策略被設置為默認策略,其中duration被設置為168h,shardGroupDuration被設置為了24h,意味着該策略里的表數據將以7天為一個周期過期,期間以1天為單位分組。
5.1:InfluxDB配置文件
下載下來一個InfluxDB后,通過配置文件influxdb.conf可以配置各個文件存放的目錄:
圖4
這幾個配置決定了你將產生的數據存放在哪里,建議自定義這個,默認的目錄很迷=_=
5.2:具體的數據目錄
配置完上面的文件,啟動,然后錄入數據,然后再去具體的目錄下查找具體的數據文件:
元數據層:用來存放當前庫的屬性,比如RP、默認RP、分組策略等。
圖5
wal層:用來存放剛寫入數據的信息,會先寫入內存,然后異步寫入wal文件,wal文件用來重啟InfluxDB時恢復內存數據用,當wal文件達到一定大小時,會壓入data層,wal層的結構和data層的幾乎一致:
圖6
上面進入wal目錄后,需要選擇正確的庫,這里選擇style_rank,進入庫后,會顯示出該庫的所有RP,這里選擇7d,然后對應圖1,你現在來到了ShardGroup層,圖6中的編號,就是生成的ShardGroup,隨便進入一個,則可以看到最終的wal文件(事實上只有當天建的group內的wal才有數據)。
data層:用來最終存放數據的目錄,所有wal文件內的數據達到一定大小后,均會被壓縮進data層,現在進入data層,然后進入style_rank:
圖7
可以看到下方除了有兩個RP策略外,還有_series目錄,這個目錄就是存放索引(tags字段所產生的索引數據)的地方,用於服務重啟時恢復索引數據用。
進入7d,可以看到跟wal層差不多的ShardGroup分層,你現在又來到了圖1的ShardGroup層:
圖8
然后再次進入一個分組內:
圖9
最終的tsm文件就存在該目錄下,fields.idx存放的是表里的fields元數據,用於寫入數據時做效驗用。
六、執行計划
然后繼續由上面的style_rank庫,在7d這個RP下,新建兩張表:
insert season_views,date="201907" season_id=666,views=56789
insert season_views_v2,season_id=666 views=56789
目的很簡單,只是為了記錄下season_id=xxx的數據在某一時刻對應的views值,倆表的區別在於,season_view僅僅以日期為tag(作為索引字段,可以忽略不計),season_views_v2則以season_id作為tag,某一時刻,兩張表的數據量均達到了5kw且數據一致,下面,讓我們以最簡單的方式,查詢下這兩張表:
ps:sql前加explain關鍵字可以查看其執行計划,加explain analyze可以看到具體每一步執行的耗時。
圖10
同樣的查詢,season_id加了索引和不加索引的區別:
NUMBER OF SERIES(掃描系列數,加了索引后根據series的哈希算法,同一個season_id都被分在了同一個Shard里,這里之所以為8,是因為ShardGroup不同)由不加索引的64個,減少到了8個
NUMBER OF FILES(掃描文件數)由不加索引的54個減少到11個
NUMBER OF BLOCKS(掃描的數據塊)由不加索引的152058減少到了184
七、總結
①在使用時,盡可能按照某具體時間段進行過濾,如果過濾條件篩選出的數據量過大,則會嚴重影響查詢效率。
②適度使用函數,若使用,請保證篩選條件篩選出的數據量,如果過大,效率會極低,比如在season_views_v2中啟用TOP函數,查出前三名,查詢耗時超過10s,加上season_id條件后,則迅速返回。
③加索引時需要注意,盡量避免大數據量的屬性做tag,否則會產生大量series,生成大量索引數據,占用過多內存,比如用戶級別的tag(這里的用戶級別是指大型網站用戶數量級,一般1億以上的情況)。
④免費版的InfluxDB不支持集群,主從需要借助influx-proxy實現,也可以自己通過監聽wal文件寫入,通過消息隊列的方式實現主從同步,wal類似mysql里的binlog。
⑤建議使用的時候,先把自己的庫建好,然后再把RP建好,如果嫌麻煩,可以直接把自己新建的RP定義成默認RP,如果RP設置為默認,只會對查詢產生影響(當然寫的時候也需要指定),在sql中不指定RP的情況下執行,則認為就是走的默認RP。
⑥分庫,建議按照日期分。