在Redis中如何保存時(shí)間序列數(shù)據(jù)詳解
我們現(xiàn)在做互聯(lián)網(wǎng)產(chǎn)品的時(shí)候,都有這么一個(gè)需求:記錄用戶在網(wǎng)站或者App上的點(diǎn)擊行為數(shù)據(jù),來分析用戶行為。這里的數(shù)據(jù)一般包括用戶ID、行為類型(例如瀏覽、登錄、下單等)、行為發(fā)生的時(shí)間戳:
UserID, Type, TimeStamp
我之前做過的一個(gè)物聯(lián)網(wǎng)項(xiàng)目的數(shù)據(jù)存取需求,和這個(gè)很相似。我們需要周期性地統(tǒng)計(jì)近萬臺(tái)設(shè)備的實(shí)時(shí)狀態(tài),包括設(shè)備ID、壓力、溫度、濕度,以及對應(yīng)的時(shí)間戳:
DeviceID, Pressure, Temperature, Humidity, TimeStamp
這些與發(fā)生時(shí)間相關(guān)的一組數(shù)據(jù),就是時(shí)間序列數(shù)據(jù)。這些數(shù)據(jù)的特點(diǎn)是沒有嚴(yán)格的關(guān)系模型,記錄的信息可以表示成鍵和值的關(guān)系(例如,一個(gè)設(shè)備ID對應(yīng)一條記錄),所以,并不需要專門用關(guān)系型數(shù)據(jù)庫(例如MySQL)來保存。而Redis的鍵值數(shù)據(jù)模型,正好可以滿足這里的數(shù)據(jù)存取需求。Redis基于自身數(shù)據(jù)結(jié)構(gòu)以及擴(kuò)展模塊,提供了兩種解決方案。
這節(jié)課,我就以物聯(lián)網(wǎng)場景中統(tǒng)計(jì)設(shè)備狀態(tài)指標(biāo)值為例,和你聊聊不同解決方案的做法和優(yōu)缺點(diǎn)。
俗話說,“知己知彼,百戰(zhàn)百勝”,我們就先從時(shí)間序列數(shù)據(jù)的讀寫特點(diǎn)開始,看看到底應(yīng)該采用什么樣的數(shù)據(jù)類型來保存吧。
時(shí)間序列數(shù)據(jù)的讀寫特點(diǎn)
在實(shí)際應(yīng)用中,時(shí)間序列數(shù)據(jù)通常是持續(xù)高并發(fā)寫入的,例如,需要連續(xù)記錄數(shù)萬個(gè)設(shè)備的實(shí)時(shí)狀態(tài)值。同時(shí),時(shí)間序列數(shù)據(jù)的寫入主要就是插入新數(shù)據(jù),而不是更新一個(gè)已存在的數(shù)據(jù),也就是說,一個(gè)時(shí)間序列數(shù)據(jù)被記錄后通常就不會(huì)變了,因?yàn)樗痛砹艘粋€(gè)設(shè)備在某個(gè)時(shí)刻的狀態(tài)值(例如,一個(gè)設(shè)備在某個(gè)時(shí)刻的溫度測量值,一旦記錄下來,這個(gè)值本身就不會(huì)再變了)。
所以,這種數(shù)據(jù)的寫入特點(diǎn)很簡單,就是插入數(shù)據(jù)快,這就要求我們選擇的數(shù)據(jù)類型,在進(jìn)行數(shù)據(jù)插入時(shí),復(fù)雜度要低,盡量不要阻塞??吹竭@兒,你可能第一時(shí)間會(huì)想到用Redis的String、Hash類型來保存,因?yàn)樗鼈兊牟迦霃?fù)雜度都是O(1),是個(gè)不錯(cuò)的選擇。但是,我在第11講中說過,String類型在記錄小數(shù)據(jù)時(shí)(例如剛才例子中的設(shè)備溫度值),元數(shù)據(jù)的內(nèi)存開銷比較大,不太適合保存大量數(shù)據(jù)。
那我們再看看,時(shí)間序列數(shù)據(jù)的“讀”操作有什么特點(diǎn)。
我們在查詢時(shí)間序列數(shù)據(jù)時(shí),既有對單條記錄的查詢(例如查詢某個(gè)設(shè)備在某一個(gè)時(shí)刻的運(yùn)行狀態(tài)信息,對應(yīng)的就是這個(gè)設(shè)備的一條記錄),也有對某個(gè)時(shí)間范圍內(nèi)的數(shù)據(jù)的查詢(例如每天早上8點(diǎn)到10點(diǎn)的所有設(shè)備的狀態(tài)信息)。
除此之外,還有一些更復(fù)雜的查詢,比如對某個(gè)時(shí)間范圍內(nèi)的數(shù)據(jù)做聚合計(jì)算。這里的聚合計(jì)算,就是對符合查詢條件的所有數(shù)據(jù)做計(jì)算,包括計(jì)算均值、最大/最小值、求和等。例如,我們要計(jì)算某個(gè)時(shí)間段內(nèi)的設(shè)備壓力的最大值,來判斷是否有故障發(fā)生。
那用一個(gè)詞概括時(shí)間序列數(shù)據(jù)的“讀”,就是查詢模式多。
弄清楚了時(shí)間序列數(shù)據(jù)的讀寫特點(diǎn),接下來我們就看看如何在Redis中保存這些數(shù)據(jù)。我們來分析下:針對時(shí)間序列數(shù)據(jù)的“寫要快”,Redis的高性能寫特性直接就可以滿足了;而針對“查詢模式多”,也就是要支持單點(diǎn)查詢、范圍查詢和聚合計(jì)算,Redis提供了保存時(shí)間序列數(shù)據(jù)的兩種方案,分別可以基于Hash和Sorted Set實(shí)現(xiàn),以及基于RedisTimeSeries模塊實(shí)現(xiàn)。
接下來,我們先學(xué)習(xí)下第一種方案。
基于Hash和Sorted Set保存時(shí)間序列數(shù)據(jù)
Hash和Sorted Set組合的方式有一個(gè)明顯的好處:它們是Redis內(nèi)在的數(shù)據(jù)類型,代碼成熟和性能穩(wěn)定。所以,基于這兩個(gè)數(shù)據(jù)類型保存時(shí)間序列數(shù)據(jù),系統(tǒng)穩(wěn)定性是可以預(yù)期的。
不過,在前面學(xué)習(xí)的場景中,我們都是使用一個(gè)數(shù)據(jù)類型來存取數(shù)據(jù),那么,為什么保存時(shí)間序列數(shù)據(jù),要同時(shí)使用這兩種類型?這是我們要回答的第一個(gè)問題。
關(guān)于Hash類型,我們都知道,它有一個(gè)特點(diǎn)是,可以實(shí)現(xiàn)對單鍵的快速查詢。這就滿足了時(shí)間序列數(shù)據(jù)的單鍵查詢需求。我們可以把時(shí)間戳作為Hash集合的key,把記錄的設(shè)備狀態(tài)值作為Hash集合的value。
可以看下用Hash集合記錄設(shè)備的溫度值的示意圖:
當(dāng)我們想要查詢某個(gè)時(shí)間點(diǎn)或者是多個(gè)時(shí)間點(diǎn)上的溫度數(shù)據(jù)時(shí),直接使用HGET命令或者HMGET命令,就可以分別獲得Hash集合中的一個(gè)key和多個(gè)key的value值了。
舉個(gè)例子。我們用HGET命令查詢202008030905這個(gè)時(shí)刻的溫度值,使用HMGET查詢202008030905、202008030907、202008030908這三個(gè)時(shí)刻的溫度值,如下所示:
HGET device:temperature 202008030905 "25.1" HMGET device:temperature 202008030905 202008030907 202008030908 1) "25.1" 2) "25.9" 3) "24.9"
你看,用Hash類型來實(shí)現(xiàn)單鍵的查詢很簡單。但是,Hash類型有個(gè)短板:它并不支持對數(shù)據(jù)進(jìn)行范圍查詢。
雖然時(shí)間序列數(shù)據(jù)是按時(shí)間遞增順序插入Hash集合中的,但Hash類型的底層結(jié)構(gòu)是哈希表,并沒有對數(shù)據(jù)進(jìn)行有序索引。所以,如果要對Hash類型進(jìn)行范圍查詢的話,就需要掃描Hash集合中的所有數(shù)據(jù),再把這些數(shù)據(jù)取回到客戶端進(jìn)行排序,然后,才能在客戶端得到所查詢范圍內(nèi)的數(shù)據(jù)。顯然,查詢效率很低。
為了能同時(shí)支持按時(shí)間戳范圍的查詢,可以用Sorted Set來保存時(shí)間序列數(shù)據(jù),因?yàn)樗軌蚋鶕?jù)元素的權(quán)重分?jǐn)?shù)來排序。我們可以把時(shí)間戳作為Sorted Set集合的元素分?jǐn)?shù),把時(shí)間點(diǎn)上記錄的數(shù)據(jù)作為元素本身。
我還是以保存設(shè)備溫度的時(shí)間序列數(shù)據(jù)為例,進(jìn)行解釋。下圖顯示了用Sorted Set集合保存的結(jié)果。
使用Sorted Set保存數(shù)據(jù)后,我們就可以使用ZRANGEBYSCORE命令,按照輸入的最大時(shí)間戳和最小時(shí)間戳來查詢這個(gè)時(shí)間范圍內(nèi)的溫度值了。如下所示,我們來查詢一下在2020年8月3日9點(diǎn)7分到9點(diǎn)10分間的所有溫度值:
ZRANGEBYSCORE device:temperature 202008030907 202008030910 1) "25.9" 2) "24.9" 3) "25.3" 4) "25.2"
現(xiàn)在我們知道了,同時(shí)使用Hash和Sorted Set,可以滿足單個(gè)時(shí)間點(diǎn)和一個(gè)時(shí)間范圍內(nèi)的數(shù)據(jù)查詢需求了,但是我們又會(huì)面臨一個(gè)新的問題,也就是我們要解答的第二個(gè)問題:如何保證寫入Hash和Sorted Set是一個(gè)原子性的操作呢?
所謂“原子性的操作”,就是指我們執(zhí)行多個(gè)寫命令操作時(shí)(例如用HSET命令和ZADD命令分別把數(shù)據(jù)寫入Hash和Sorted Set),這些命令操作要么全部完成,要么都不完成。
只有保證了寫操作的原子性,才能保證同一個(gè)時(shí)間序列數(shù)據(jù),在Hash和Sorted Set中,要么都保存了,要么都沒保存。否則,就可能出現(xiàn)Hash集合中有時(shí)間序列數(shù)據(jù),而Sorted Set中沒有,那么,在進(jìn)行范圍查詢時(shí),就沒有辦法滿足查詢需求了。
那Redis是怎么保證原子性操作的呢?這里就涉及到了Redis用來實(shí)現(xiàn)簡單的事務(wù)的MULTI和EXEC命令。當(dāng)多個(gè)命令及其參數(shù)本身無誤時(shí),MULTI和EXEC命令可以保證執(zhí)行這些命令時(shí)的原子性。關(guān)于Redis的事務(wù)支持和原子性保證的異常情況,我會(huì)在第30講中向你介紹,這節(jié)課,我們只要了解一下MULTI和EXEC這兩個(gè)命令的使用方法就行了。
- MULTI命令:表示一系列原子性操作的開始。收到這個(gè)命令后,Redis就知道,接下來再收到的命令需要放到一個(gè)內(nèi)部隊(duì)列中,后續(xù)一起執(zhí)行,保證原子性。
- EXEC命令:表示一系列原子性操作的結(jié)束。一旦Redis收到了這個(gè)命令,就表示所有要保證原子性的命令操作都已經(jīng)發(fā)送完成了。此時(shí),Redis開始執(zhí)行剛才放到內(nèi)部隊(duì)列中的所有命令操作。
你可以看下下面這張示意圖,命令1到命令N是在MULTI命令后、EXEC命令前發(fā)送的,它們會(huì)被一起執(zhí)行,保證原子性。
以保存設(shè)備狀態(tài)信息的需求為例,我們執(zhí)行下面的代碼,把設(shè)備在2020年8月3日9時(shí)5分的溫度,分別用HSET命令和ZADD命令寫入Hash集合和Sorted Set集合。
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> HSET device:temperature 202008030911 26.8 QUEUED 127.0.0.1:6379> ZADD device:temperature 202008030911 26.8 QUEUED 127.0.0.1:6379> EXEC 1) (integer) 1 2) (integer) 1
可以看到,首先,Redis收到了客戶端執(zhí)行的MULTI命令。然后,客戶端再執(zhí)行HSET和ZADD命令后,Redis返回的結(jié)果為“QUEUED”,表示這兩個(gè)命令暫時(shí)入隊(duì),先不執(zhí)行;執(zhí)行了EXEC命令后,HSET命令和ZADD命令才真正執(zhí)行,并返回成功結(jié)果(結(jié)果值為1)。
到這里,我們就解決了時(shí)間序列數(shù)據(jù)的單點(diǎn)查詢、范圍查詢問題,并使用MUTLI和EXEC命令保證了Redis能原子性地把數(shù)據(jù)保存到Hash和Sorted Set中。接下來,我們需要繼續(xù)解決第三個(gè)問題:如何對時(shí)間序列數(shù)據(jù)進(jìn)行聚合計(jì)算?
聚合計(jì)算一般被用來周期性地統(tǒng)計(jì)時(shí)間窗口內(nèi)的數(shù)據(jù)匯總狀態(tài),在實(shí)時(shí)監(jiān)控與預(yù)警等場景下會(huì)頻繁執(zhí)行。
因?yàn)镾orted Set只支持范圍查詢,無法直接進(jìn)行聚合計(jì)算,所以,我們只能先把時(shí)間范圍內(nèi)的數(shù)據(jù)取回到客戶端,然后在客戶端自行完成聚合計(jì)算。這個(gè)方法雖然能完成聚合計(jì)算,但是會(huì)帶來一定的潛在風(fēng)險(xiǎn),也就是大量數(shù)據(jù)在Redis實(shí)例和客戶端間頻繁傳輸,這會(huì)和其他操作命令競爭網(wǎng)絡(luò)資源,導(dǎo)致其他操作變慢。
在我們這個(gè)物聯(lián)網(wǎng)項(xiàng)目中,就需要每3分鐘統(tǒng)計(jì)一下各個(gè)設(shè)備的溫度狀態(tài),一旦設(shè)備溫度超出了設(shè)定的閾值,就要進(jìn)行報(bào)警。這是一個(gè)典型的聚合計(jì)算場景,我們可以來看看這個(gè)過程中的數(shù)據(jù)體量。
假設(shè)我們需要每3分鐘計(jì)算一次的所有設(shè)備各指標(biāo)的最大值,每個(gè)設(shè)備每15秒記錄一個(gè)指標(biāo)值,1分鐘就會(huì)記錄4個(gè)值,3分鐘就會(huì)有12個(gè)值。我們要統(tǒng)計(jì)的設(shè)備指標(biāo)數(shù)量有33個(gè),所以,單個(gè)設(shè)備每3分鐘記錄的指標(biāo)數(shù)據(jù)有將近400個(gè)(33 * 12 = 396),而設(shè)備總數(shù)量有1萬臺(tái),這樣一來,每3分鐘就有將近400萬條(396 * 1萬 = 396萬)數(shù)據(jù)需要在客戶端和Redis實(shí)例間進(jìn)行傳輸。
為了避免客戶端和Redis實(shí)例間頻繁的大量數(shù)據(jù)傳輸,我們可以使用RedisTimeSeries來保存時(shí)間序列數(shù)據(jù)。
RedisTimeSeries支持直接在Redis實(shí)例上進(jìn)行聚合計(jì)算。還是以剛才每3分鐘算一次最大值為例。在Redis實(shí)例上直接聚合計(jì)算,那么,對于單個(gè)設(shè)備的一個(gè)指標(biāo)值來說,每3分鐘記錄的12條數(shù)據(jù)可以聚合計(jì)算成一個(gè)值,單個(gè)設(shè)備每3分鐘也就只有33個(gè)聚合值需要傳輸,1萬臺(tái)設(shè)備也只有33萬條數(shù)據(jù)。數(shù)據(jù)量大約是在客戶端做聚合計(jì)算的十分之一,很顯然,可以減少大量數(shù)據(jù)傳輸對Redis實(shí)例網(wǎng)絡(luò)的性能影響。
所以,如果我們只需要進(jìn)行單個(gè)時(shí)間點(diǎn)查詢或是對某個(gè)時(shí)間范圍查詢的話,適合使用Hash和Sorted Set的組合,它們都是Redis的內(nèi)在數(shù)據(jù)結(jié)構(gòu),性能好,穩(wěn)定性高。但是,如果我們需要進(jìn)行大量的聚合計(jì)算,同時(shí)網(wǎng)絡(luò)帶寬條件不是太好時(shí),Hash和Sorted Set的組合就不太適合了。此時(shí),使用RedisTimeSeries就更加合適一些。
好了,接下來,我們就來具體學(xué)習(xí)下RedisTimeSeries。
基于RedisTimeSeries模塊保存時(shí)間序列數(shù)據(jù)
RedisTimeSeries是Redis的一個(gè)擴(kuò)展模塊。它專門面向時(shí)間序列數(shù)據(jù)提供了數(shù)據(jù)類型和訪問接口,并且支持在Redis實(shí)例上直接對數(shù)據(jù)進(jìn)行按時(shí)間范圍的聚合計(jì)算。
因?yàn)镽edisTimeSeries不屬于Redis的內(nèi)建功能模塊,在使用時(shí),我們需要先把它的源碼單獨(dú)編譯成動(dòng)態(tài)鏈接庫redistimeseries.so,再使用loadmodule命令進(jìn)行加載,如下所示:
loadmodule redistimeseries.so
當(dāng)用于時(shí)間序列數(shù)據(jù)存取時(shí),RedisTimeSeries的操作主要有5個(gè):
- 用TS.CREATE命令創(chuàng)建時(shí)間序列數(shù)據(jù)集合;
- 用TS.ADD命令插入數(shù)據(jù);
- 用TS.GET命令讀取最新數(shù)據(jù);
- 用TS.MGET命令按標(biāo)簽過濾查詢數(shù)據(jù)集合;
- 用TS.RANGE支持聚合計(jì)算的范圍查詢。
下面,我來介紹一下如何使用這5個(gè)操作。
1.用TS.CREATE命令創(chuàng)建一個(gè)時(shí)間序列數(shù)據(jù)集合
在TS.CREATE命令中,我們需要設(shè)置時(shí)間序列數(shù)據(jù)集合的key和數(shù)據(jù)的過期時(shí)間(以毫秒為單位)。此外,我們還可以為數(shù)據(jù)集合設(shè)置標(biāo)簽,來表示數(shù)據(jù)集合的屬性。
例如,我們執(zhí)行下面的命令,創(chuàng)建一個(gè)key為device:temperature、數(shù)據(jù)有效期為600s的時(shí)間序列數(shù)據(jù)集合。也就是說,這個(gè)集合中的數(shù)據(jù)創(chuàng)建了600s后,就會(huì)被自動(dòng)刪除。最后,我們給這個(gè)集合設(shè)置了一個(gè)標(biāo)簽屬性{device_id:1},表明這個(gè)數(shù)據(jù)集合中記錄的是屬于設(shè)備ID號為1的數(shù)據(jù)。
TS.CREATE device:temperature RETENTION 600000 LABELS device_id 1 OK
2.用TS.ADD命令插入數(shù)據(jù),用TS.GET命令讀取最新數(shù)據(jù)
我們可以用TS.ADD命令往時(shí)間序列集合中插入數(shù)據(jù),包括時(shí)間戳和具體的數(shù)值,并使用TS.GET命令讀取數(shù)據(jù)集合中的最新一條數(shù)據(jù)。
例如,我們執(zhí)行下列TS.ADD命令時(shí),就往device:temperature集合中插入了一條數(shù)據(jù),記錄的是設(shè)備在2020年8月3日9時(shí)5分的設(shè)備溫度;再執(zhí)行TS.GET命令時(shí),就會(huì)把剛剛插入的最新數(shù)據(jù)讀取出來。
TS.ADD device:temperature 1596416700 25.1 1596416700 TS.GET device:temperature 25.1
3.用TS.MGET命令按標(biāo)簽過濾查詢數(shù)據(jù)集合
在保存多個(gè)設(shè)備的時(shí)間序列數(shù)據(jù)時(shí),我們通常會(huì)把不同設(shè)備的數(shù)據(jù)保存到不同集合中。此時(shí),我們就可以使用TS.MGET命令,按照標(biāo)簽查詢部分集合中的最新數(shù)據(jù)。在使用TS.CREATE創(chuàng)建數(shù)據(jù)集合時(shí),我們可以給集合設(shè)置標(biāo)簽屬性。當(dāng)我們進(jìn)行查詢時(shí),就可以在查詢條件中對集合標(biāo)簽屬性進(jìn)行匹配,最后的查詢結(jié)果里只返回匹配上的集合中的最新數(shù)據(jù)。
舉個(gè)例子。假設(shè)我們一共用4個(gè)集合為4個(gè)設(shè)備保存時(shí)間序列數(shù)據(jù),設(shè)備的ID號是1、2、3、4,我們在創(chuàng)建數(shù)據(jù)集合時(shí),把device_id設(shè)置為每個(gè)集合的標(biāo)簽。此時(shí),我們就可以使用下列TS.MGET命令,以及FILTER設(shè)置(這個(gè)配置項(xiàng)用來設(shè)置集合標(biāo)簽的過濾條件),查詢device_id不等于2的所有其他設(shè)備的數(shù)據(jù)集合,并返回各自集合中的最新的一條數(shù)據(jù)。
TS.MGET FILTER device_id!=2 1) 1) "device:temperature:1" 2) (empty list or set) 3) 1) (integer) 1596417000 2) "25.3" 2) 1) "device:temperature:3" 2) (empty list or set) 3) 1) (integer) 1596417000 2) "29.5" 3) 1) "device:temperature:4" 2) (empty list or set) 3) 1) (integer) 1596417000 2) "30.1"
4.用TS.RANGE支持需要聚合計(jì)算的范圍查詢
最后,在對時(shí)間序列數(shù)據(jù)進(jìn)行聚合計(jì)算時(shí),我們可以使用TS.RANGE命令指定要查詢的數(shù)據(jù)的時(shí)間范圍,同時(shí)用AGGREGATION參數(shù)指定要執(zhí)行的聚合計(jì)算類型。RedisTimeSeries支持的聚合計(jì)算類型很豐富,包括求均值(avg)、求最大/最小值(max/min),求和(sum)等。
例如,在執(zhí)行下列命令時(shí),我們就可以按照每180s的時(shí)間窗口,對2020年8月3日9時(shí)5分和2020年8月3日9時(shí)12分這段時(shí)間內(nèi)的數(shù)據(jù)進(jìn)行均值計(jì)算了。
TS.RANGE device:temperature 1596416700 1596417120 AGGREGATION avg 180000 1) 1) (integer) 1596416700 2) "25.6" 2) 1) (integer) 1596416880 2) "25.8" 3) 1) (integer) 1596417060 2) "26.1"
與使用Hash和Sorted Set來保存時(shí)間序列數(shù)據(jù)相比,RedisTimeSeries是專門為時(shí)間序列數(shù)據(jù)訪問設(shè)計(jì)的擴(kuò)展模塊,能支持在Redis實(shí)例上直接進(jìn)行聚合計(jì)算,以及按標(biāo)簽屬性過濾查詢數(shù)據(jù)集合,當(dāng)我們需要頻繁進(jìn)行聚合計(jì)算,以及從大量集合中篩選出特定設(shè)備或用戶的數(shù)據(jù)集合時(shí),RedisTimeSeries就可以發(fā)揮優(yōu)勢了。
小結(jié)
在這節(jié)課,我們一起學(xué)習(xí)了如何用Redis保存時(shí)間序列數(shù)據(jù)。時(shí)間序列數(shù)據(jù)的寫入特點(diǎn)是要能快速寫入,而查詢的特點(diǎn)有三個(gè):
- 點(diǎn)查詢,根據(jù)一個(gè)時(shí)間戳,查詢相應(yīng)時(shí)間的數(shù)據(jù);
- 范圍查詢,查詢起始和截止時(shí)間戳范圍內(nèi)的數(shù)據(jù);
- 聚合計(jì)算,針對起始和截止時(shí)間戳范圍內(nèi)的所有數(shù)據(jù)進(jìn)行計(jì)算,例如求最大/最小值,求均值等。
關(guān)于快速寫入的要求,Redis的高性能寫特性足以應(yīng)對了;而針對多樣化的查詢需求,Redis提供了兩種方案。
第一種方案是,組合使用Redis內(nèi)置的Hash和Sorted Set類型,把數(shù)據(jù)同時(shí)保存在Hash集合和Sorted Set集合中。這種方案既可以利用Hash類型實(shí)現(xiàn)對單鍵的快速查詢,還能利用Sorted Set實(shí)現(xiàn)對范圍查詢的高效支持,一下子滿足了時(shí)間序列數(shù)據(jù)的兩大查詢需求。
不過,第一種方案也有兩個(gè)不足:一個(gè)是,在執(zhí)行聚合計(jì)算時(shí),我們需要把數(shù)據(jù)讀取到客戶端再進(jìn)行聚合,當(dāng)有大量數(shù)據(jù)要聚合時(shí),數(shù)據(jù)傳輸開銷大;另一個(gè)是,所有的數(shù)據(jù)會(huì)在兩個(gè)數(shù)據(jù)類型中各保存一份,內(nèi)存開銷不小。不過,我們可以通過設(shè)置適當(dāng)?shù)臄?shù)據(jù)過期時(shí)間,釋放內(nèi)存,減小內(nèi)存壓力。
我們學(xué)習(xí)的第二種實(shí)現(xiàn)方案是使用RedisTimeSeries模塊。這是專門為存取時(shí)間序列數(shù)據(jù)而設(shè)計(jì)的擴(kuò)展模塊。和第一種方案相比,RedisTimeSeries能支持直接在Redis實(shí)例上進(jìn)行多種數(shù)據(jù)聚合計(jì)算,避免了大量數(shù)據(jù)在實(shí)例和客戶端間傳輸。不過,RedisTimeSeries的底層數(shù)據(jù)結(jié)構(gòu)使用了鏈表,它的范圍查詢的復(fù)雜度是O(N)級別的,同時(shí),它的TS.GET查詢只能返回最新的數(shù)據(jù),沒有辦法像第一種方案的Hash類型一樣,可以返回任一時(shí)間點(diǎn)的數(shù)據(jù)。
所以,組合使用Hash和Sorted Set,或者使用RedisTimeSeries,在支持時(shí)間序列數(shù)據(jù)存取上各有優(yōu)劣勢。我給你的建議是:
- 如果你的部署環(huán)境中網(wǎng)絡(luò)帶寬高、Redis實(shí)例內(nèi)存大,可以優(yōu)先考慮第一種方案;
- 如果你的部署環(huán)境中網(wǎng)絡(luò)、內(nèi)存資源有限,而且數(shù)據(jù)量大,聚合計(jì)算頻繁,需要按數(shù)據(jù)集合屬性查詢,可以優(yōu)先考慮第二種方案。
每課一問
按照慣例,我給你提個(gè)小問題。
在這節(jié)課上,我提到,我們可以使用Sorted Set保存時(shí)間序列數(shù)據(jù),把時(shí)間戳作為score,把實(shí)際的數(shù)據(jù)作為member,你覺得這樣保存數(shù)據(jù)有沒有潛在的風(fēng)險(xiǎn)?另外,如果你是Redis的開發(fā)維護(hù)者,你會(huì)把聚合計(jì)算也設(shè)計(jì)為Sorted Set的一個(gè)內(nèi)在功能嗎?
總結(jié)
到此這篇關(guān)于在Redis中如何保存時(shí)間序列數(shù)據(jù)的文章就介紹到這了,更多相關(guān)Redis保存時(shí)間序列數(shù)據(jù)內(nèi)容請搜索本站以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持本站!
版權(quán)聲明:本站文章來源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請保持原文完整并注明來源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來,僅供學(xué)習(xí)參考,不代表本站立場,如有內(nèi)容涉嫌侵權(quán),請聯(lián)系alex-e#qq.com處理。