網站首頁 編程語言 正文
Redis - 時間序列數據類型的保存方案和消息隊列實現
- 一. 用 Redis 保存時間序列類型數據方案
- 1.1 內存和范圍查找的支持性問題
- 1.2 聚合操作的支持性問題(僅供參考)
- 二. 用 Redis 實現消息隊列
- 2.1 消息保序的實現
- 2.2 重復消費問題解決
- 2.3 消息可靠性保證
- 2.4 Redis 做中間件的優劣勢
一. 用 Redis 保存時間序列類型數據方案
我們日常開發中,有很多這種類似的場景,記錄某一個時刻下,某個目標的相關屬性或者狀態。 那么常規的,我們可以用時間戳作為Key
,而這個目標的相關屬性我們可以將其轉化為JSON
串或者通過字符串拼接的方式來保存為String
類型,然后作為Value
。
但是,這樣的存儲是針對于一個時間戳而言的,而實際環境中,往往需要記錄非常多的這樣的數據,甚至可能需要對這種數據進行統計、聚合、范圍查找等操作。那么可想而知,Redis
中的String
類型,雖然可以提供存儲功能,但是卻難以提供統計、聚合、范圍查找等這些復雜操作。 除此之外,String
類型我在String內存開銷問題以及基本/擴展數據類型的使用這篇文章有提到,如果存儲的數據量太大,那么內存的占用是非常龐大的。耗內存。
同時用時間戳作為Key
的數據,往往也有以下特點:
- 數據的插入比較頻繁。
- 讀操作的查詢模式的種類比較多(統計、聚合…)。
1.1 內存和范圍查找的支持性問題
場景:按照一定的時間間隔記錄某一個設備集群中每臺機器的溫度。
針對內存占用問題,我們可以選擇Hash
類型去替代String
。
# 傳統的String類型存儲
set 1659947381000 31
set 1659947392000 35
set 1659947413000 28
# 改為Hash存儲
set temperature 1659947381000 31 1659947392000 35 1659947413000 28
雖然Hash
結構彌補了String
類型在內存開銷上的短板。但是僅僅這樣是無法滿足數據的一些范圍查詢或者操作的。
那么針對范圍查找問題:因此我們可以再使用Sorted Set
去保存相同的一份數據。這里做個解釋,為何要同時用兩個數據結構來存儲相同的數據:
-
Hash
結構:用來提供單值查詢。 -
Sorted Set
:提供范圍查詢。雖然將范圍的前后指定為一個相同的值,看起來像是單個值查詢,但是本質上依舊是范圍查詢,效率不如人家Hash
來的快。
那么此時就可以用這樣的命令來進行查找:
zrangebyscore temperature 1659947381000 1659947413000
此外,我們既然使用了兩種數據結構來保存相同的數據,就應該保證數據一致性。我們應該保證兩個數據結構中的數據是完全一樣的,不能出現哪個結構中的數據有少或者不一致的情況。因此我們可以在進行數據插入的時候,保障兩個操作的原子性。即使用簡單的事務操作:
-
multi
:事務開始。之后的操作將會放入一個隊列中,而不會真正的去執行。 -
exec
:事務結束,開始執行隊列中的一系列命令。
例如:
multi
hset temperature 1659947381000 25
zadd temperature 1659947381000 25
exec
那么Java
對應的操作就是:
Transaction multi = jedis.multi();
multi.hset("temperature", "1659947381000", "25");
System.out.println("事務執行中:hash:" + multi.hget("temperature", "1659947381000"));
multi.zadd("temperatureZSet", 1659947381000L, "25");
System.out.println("事務執行中:sorted set:" + multi.zrangeByScore("temperatureZSet", 1659947381000L, 1659947381000L));
multi.exec();
System.out.println("**************事務執行完畢******************");
System.out.println("事務執行中:hash:" + jedis.hget("temperature", "1659947381000"));
System.out.println("事務執行中:sorted set:" + jedis.zrangeByScore("temperatureZSet", 1659947341000L, 1659947392000L));
注意:
hash
和sorted set
兩個集合使用的key
不能是同一個。- 并且事務中不能使用普通的
jedis
對象。multi
對象擁有和jedis
對象同樣的API
操作。因為Redis
中事務開啟后,執行的操作是放到隊列中的,并不是馬上執行的,因此需要做區分。
結果如下:
到這里,內存問題和范圍查找的問題已經解決了。雖然我們用了兩個數據類型來保存相同的一份數據,但是整體的內存消耗,是比全部用String
類型存儲要節省的。那么接下來要解決的,就是聚合操作問題。
備注:
- 到這里為止,如果業務上只涉及到時序的范圍查找,是可以同時用
Hash
和Sorted Set
去替代傳統的String
的。如果僅僅限于此,我個人建議1.2節可以不看。 -
Redis
中對于事務的使用,在文章中提到的原子性問題也是有一定缺陷的。因為Redis
中的事務并不像Mysql
那樣,倘若在一個事務中,先后執行了A和B操作,但是在執行C操作的時候發生了錯誤,A和B的操作是不會回滾的。 -
Redis
主要還是拿來做緩存比較多,這種專門的時序數據處理最好交給專門的時序數據庫處理,例如influxDB
。 - 1.2節內容僅供參考,并且實用性和實際操作起來是否簡單這個問題上,有待商榷,因為并不容易實現。(至少我寫這篇文章的時候,關于
RedisTimeSeries
的JavaAPI
操作沒有找到)
1.2 聚合操作的支持性問題(僅供參考)
首先,我們當然可以在客戶端將相關的數據全部讀取過來,然后再客戶端自行完成聚合操作。但是倘若有這么幾個點:
- 數據量很大。
- 聚合操作的頻率很高。
那么這種情況下,就會有很多請求(包含了大量數據)在Redis
和客戶端之間來回穿梭,就會造成資源的競爭,降低Redis
的性能。
那么針對聚合操作問題:我們可以使用RedisTimeSeries
,它支持在Redis
實例上對時間維度進行聚合計算。
但是使用這個,卻比較麻煩,需要了解這么幾個點:
-
RedisTimeSeries
是Redis
的擴展模塊,原生Redis
并不支持。 - 使用的時候需要將
Redis
源碼單獨編譯成動態鏈接庫redistimeseries.so
,再使用loadmodule
命令進行加載。
loadmodule redistimeseries.so
那么針對上述的聚合場景,使用RedisTimeSeries
的大致流程如下:
# 創建時間序列數據集合,創建一個key為temperature ,數據的有效期為800s。(過后會自動刪除),同時這個集合的標簽屬性uid為1
ts.create temperature retention 800000 labels uid 1
# 插入數據
ts.add temperature 1659947381000 25
# 最新數據的獲取 ts.get只能返回最新的數據
ts.get temperature
# 按照標簽過濾查詢
ts.mget FILTER uid = xxx
# 聚合計算 在[1659947371000 ,1659947381000]范圍內,按照每180s的時間間隔,對這個時間窗口內的數據做均值計算
ts.range temperature 1659947371000 1659947381000 AGGREGATION avg 180000
二. 用 Redis 實現消息隊列
前言:Redis
是可以做消息隊列的,但是對于一些不允許出現消息丟失的情形下,例如金融支付操作。不要用Redis
作為中間件,請使用專門的中間件去做存儲。例如Kafka、ActiveMQ、RabbitMQ
等。具體原因下面分析。
首先消息隊列需要解決三個問題:
- 消息保序。
- 消息的重復消費問題。
- 消息的可靠性保證。
2.1 消息保序的實現
那么如何用Redis
作為消息隊列呢?利用Redis
中的List
數據結構。
-
List
這個數據結構本身就是FIFO
,先進先出的順序對數據進行存儲的。 - 實際操作上,生產者通過
lpush
命令將數據寫入List
中。消費者端則通過rpop
命令將其彈出。
這是一般的操作。但是光憑這樣的操作并不滿足一個合格的消息中間件具備的條件。因為在生產者向Redis
中寫入數據的時候,Redis
并不會主動地通知消費者有新消息寫入了。此時消費者只能通過這樣的偽代碼來實現輪詢:
while(true){
String json = jedis.rpop('key');
process(json);
}
問題:這樣的無限循環,會導致CPU
一直消耗在這里執行rpop
命令。造成性能損失。
解決:建議使用brpop
命令,即阻塞式讀取,客戶端在沒有讀到隊列數據時,自動阻塞,直到有新的數據寫入隊列,再開始讀取新數據。
2.2 重復消費問題解決
對于消息的重復消費問題,我們只需要提供一個唯一標識,然后消費的時候做判斷即可。
- 生產者端:發送消息的時候,給消息里面塞一個唯一標識。
- 消費者端:將消費完成的消息的唯一標識記錄下來。在后續消費的時候,都要反查一遍先。
# 唯一標識:主題:內容
lpush key 1000001:title:helloworld
2.3 消息可靠性保證
背景:當消費者程序從Redis
中讀取一條消息并做處理,但是還沒處理完成的時候就發生了宕機,那么Redis
中這條數據已經被剔除,但這個數據并沒有被真正的消費掉。怎么辦?
解決:生產者在推消息給Redis
的時候,使用 BRPOPLPUSH
命令,其作用如下:
- 在生產者推消息的時候,
Redis
會把這個消息再插入到另一個List
留存。 - 這樣一來,如果消費者程序讀了消息但沒能正常處理,等它重啟后,就可以從備份
List
中重新讀取消息并處理。
綜上所述,常規情況下:
- 生產者端使用
BRPOPLPUSH
命令往Redis
中推數據,同時塞入唯一標識。 - 消費者端使用
brpop
命令。防止無限循環調用rpop()
命令。將消費過的消息的唯一標識做數據存儲。 - 消費者倘若消費某個消息成功,由于生產者端往兩個
List
都插入了數據,此時最好將備份隊列中的消息刪除,避免備份隊列中存儲過多過期數據,造成內存浪費。
2.4 Redis 做中間件的優劣勢
先來說下Redis
做中間件的優勢:
- 用
Redis
作為消息隊列,由于Redis
的特性,在內存上操作,因此性能高。 -
API
操作起來非常方便,沒有復雜的操作,部署輕量。Kafka
的操作相比之下就會復雜許多。維護成本也要更高點。
Redis
做中間件的劣勢:可能出現數據丟失。 有這么個幾個場景:
-
AOF
策略為每秒寫盤。該過程為異步,若Redis
發生宕機,會丟失1秒的數據。若改為同步寫盤,則會導致性能下降。 - 在主從集群下,倘若寫操作的頻率非常大,那么主從的數據同步就會存在延遲,那么在進行主從切換的時候,也可能存在數據丟失問題。詳細可以看Redis - Redis主從數據一致性和哨兵機制。
- 無法保證數據的完整性,而像Kafka這樣的專業中間件,副本等機制保證了數據的可靠性。哪怕集群的某個節點掛掉了,也不會丟失數據。詳細可以參考Kafka復習計劃 - Kafka基礎知識以及集群參方案和參數。
原文鏈接:https://blog.csdn.net/Zong_0915/article/details/126228122
相關推薦
- 2022-07-20 詳解Go程序添加遠程調用tcpdump功能_Golang
- 2023-03-18 詳解Flutter中key的正確使用方式_Android
- 2022-03-30 Flutter有狀態組件使用詳解_Android
- 2022-06-16 詳解Python如何循環遍歷Numpy中的Array_python
- 2022-06-06 element ui表單el-form的label自適應寬度
- 2022-10-12 基于PyQt5實現狀態欄(statusBar)顯示和隱藏功能_python
- 2022-09-21 使用Python遍歷文件夾實現查找指定文件夾_python
- 2022-02-21 el-table-column的formatter的使用
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支