Redis Sorted Set類型使用及應(yīng)用場景
sorted set在沒有重復(fù)元素的集合基礎(chǔ)上,每個(gè)元素多了一個(gè)分?jǐn)?shù)score屬性作為權(quán)重,查詢時(shí)可以用score排序。
Sorted Set類型的常用操作
ZADD 添加元素
127.0.0.1:6379> ZADD rank 98 u1 (integer) 1 127.0.0.1:6379> ZADD rank 95 u2 (integer) 1 127.0.0.1:6379> ZADD rank 60 u3 (integer) 1 127.0.0.1:6379> ZADD rank 76 u4 (integer) 1 127.0.0.1:6379> ZADD rank 77 u5 (integer) 1
ZCARD 獲取集合成員數(shù)量
127.0.0.1:6379> ZCARD rank (integer) 5
ZCOUNT 獲取指定分?jǐn)?shù)段內(nèi)的成員數(shù)量
127.0.0.1:6379> ZCOUNT rank 80 100 (integer) 2
ZINCRBY 給集合中指定成員增加指定分?jǐn)?shù)
127.0.0.1:6379> ZINCRBY rank 2 u3 "62"
ZRANGE/ZREVRANGE 返回按索引排序的成員
127.0.0.1:6379> ZRANGE rank 0 1 # 分?jǐn)?shù)從小到大 1) "u3" 2) "u4" 127.0.0.1:6379> ZREVRANGE rank 0 2 # 分?jǐn)?shù)從大到下 1) "u1" 2) "u2" 3) "u5"
ZRANGEBYSCORE/ZREVRANGEBYSCORE 返回按指定分?jǐn)?shù)段內(nèi)的元素列表
127.0.0.1:6379> ZRANGEBYSCORE rank 80 100 # 分?jǐn)?shù)段從小到大 1) "u2" 2) "u1" 127.0.0.1:6379> ZREVRANGEBYSCORE rank 100 80 # 分?jǐn)?shù)段從大到小 1) "u1" 2) "u2"
ZREM 刪除指定元素
127.0.0.1:6379> ZREM rank u2 (integer) 1
實(shí)際應(yīng)用場景
1.排行榜
實(shí)現(xiàn)一個(gè)實(shí)時(shí)變化的積分排行榜,可以利用 ZREVRANGE 輕松展現(xiàn)最高排名,并實(shí)現(xiàn)分頁查詢
首先將成員名單錄入
127.0.0.1:6379> ZADD rank 0 player1 0 player2 0 player3 0 player4 0 player5 0 player6 (integer) 6
當(dāng)然也可以在合適的時(shí)機(jī)將新增的成員單個(gè)寫入
然后開始實(shí)時(shí)計(jì)算增加的分?jǐn)?shù) 使用ZINCRBY, 增加完后會(huì)直接返回當(dāng)前成員的最終分?jǐn)?shù)
127.0.0.1:6379> ZINCRBY rank 50 player1 "50" 127.0.0.1:6379> ZINCRBY rank 53 player3 "53" 127.0.0.1:6379> ZINCRBY rank 20 player5 "20" 127.0.0.1:6379> ZINCRBY rank 40 player6 "40" 127.0.0.1:6379> ZINCRBY rank 40 player5 "60" 127.0.0.1:6379> ZINCRBY rank -2 player6 # 負(fù)數(shù)也是可以的 相當(dāng)于減分 "38"
也可以不用初始化成員分?jǐn)?shù) 直接給一個(gè)不存在的的成員增加分?jǐn)?shù),默認(rèn)會(huì)創(chuàng)建并增加分?jǐn)?shù),默認(rèn)從0開始
127.0.0.1:6379> ZINCRBY rank 55 player7 "55"
最后獲取排行榜上前3的成員,使用ZREVRANGE
127.0.0.1:6379> ZREVRANGE rank 0 2 1) "player5" 2) "player7" 3) "player3"
如果要帶上分?jǐn)?shù),需要增加一個(gè)參數(shù) WITHSCORES
127.0.0.1:6379> ZREVRANGE rank 0 2 WITHSCORES 1) "player5" 2) "60" 3) "player7" 4) "55" 5) "player3" 6) "53"
2.定時(shí)/延遲任務(wù)
定時(shí)/延遲任務(wù),或是延遲隊(duì)列類功能,最重要的是在指定時(shí)間后才開始處理相關(guān)任務(wù)。
以下是大致實(shí)現(xiàn)方式
任務(wù)生產(chǎn)端:
每個(gè)任務(wù)生成一個(gè)任務(wù)ID(要有唯一性),將具體的任務(wù)信息可以放在其他數(shù)據(jù)庫里,當(dāng)然也可以放在Redis里。然后將任務(wù)ID作為sorted set的成員, 需要執(zhí)行任務(wù)時(shí)間點(diǎn)的時(shí)間戳作為score寫入(如果是延遲任務(wù)就計(jì)算出具體的時(shí)間戳)
127.0.0.1:6379> ZADD tasks 1000 t1 # 假設(shè)當(dāng)前時(shí)間戳從1000開始 (integer) 1 127.0.0.1:6379> ZADD tasks 1003 t2 # 3s后才能觸發(fā)的任務(wù) (integer) 1 127.0.0.1:6379> ZADD tasks 1004 t3 (integer) 1 127.0.0.1:6379> ZADD tasks 1004 t4 (integer) 1
這時(shí)sorted set里的任務(wù)順序大概是這樣(按分?jǐn)?shù)從小達(dá)到)
t1 | t2 | t3 | t4 |
---|---|---|---|
1000 | 1003 | 1004 | 1004 |
任務(wù)消費(fèi)端
啟動(dòng)一個(gè)進(jìn)程循環(huán)獲取任務(wù), 獲取分?jǐn)?shù)的范圍在當(dāng)前時(shí)間戳之前(表示已經(jīng)到期的任務(wù))
127.0.0.1:6379> ZRANGEBYSCORE tasks -inf 1000 1) "t1"
程序代碼大概可以這樣寫
# 偽代碼 while true { nowTimestamp = time.now() # 獲取當(dāng)前時(shí)間戳 taskIds = redis.ZRANGEBYSCORE("tasks", "-inf", nowTimestamp) for taskId in TaskIds { # TODO 執(zhí)行任務(wù)處理邏輯 } }
不過有個(gè)很明顯的問題, 當(dāng)時(shí)間戳到1003的時(shí)候,任務(wù)t1還是能夠獲取到
127.0.0.1:6379> ZRANGEBYSCORE tasks -inf 1003 1) "t1" 2) "t2"
這是因?yàn)閆RANGEBYSCORE只是查詢出來,并沒有刪除元素,刪除元素需要使用ZREM
完善一下代碼
# 偽代碼 while true { nowTimestamp = time.now() # 獲取當(dāng)前時(shí)間戳 taskIds = redis.ZRANGEBYSCORE("tasks", "-inf", nowTimestamp) for taskId in taskIds { # TODO 執(zhí)行任務(wù)處理邏輯 # 正常處理了調(diào)用ZREM redis.ZREM("tasks", taskId) } }
接下來隨著任務(wù)越來越多,上面的處理邏輯可能短時(shí)間內(nèi)也處理不完,或者單個(gè)任務(wù)處理時(shí)間較長,就會(huì)導(dǎo)致已經(jīng)到期的任務(wù)無法及時(shí)處理。
一般情況下,任務(wù)之間是沒有依賴關(guān)系,這時(shí)可以考慮橫向庫容消費(fèi)進(jìn)程,并行處理任務(wù)。
不過直接將上面的邏輯復(fù)制在多個(gè)進(jìn)程里,會(huì)有任務(wù)重復(fù)的問題。
t1 | t2 | t3 | t4 |
---|---|---|---|
1000 | 1003 | 1004 | 1004 |
進(jìn)程1查詢點(diǎn) | |||
進(jìn)程2查詢點(diǎn) |
就像上面一樣,可能出現(xiàn),兩個(gè)進(jìn)程同時(shí)獲取到t1任務(wù)
有兩種處理方式:
第一種 增加分布式鎖,可以基于Redis的set類型數(shù)據(jù)
代碼如下
# 偽代碼 while true { nowTimestamp = time.now() # 獲取當(dāng)前時(shí)間戳 taskIds = redis.ZRANGEBYSCORE("tasks", "-inf", nowTimestamp) for taskId in taskIds { addSuccess = redis.SADD("tasks:lock", taskId) if addSuccess == 0 { # 寫入不成功 說明有其他進(jìn)程在處理該任務(wù) continue } # TODO 執(zhí)行任務(wù)處理邏輯 redis.ZREM("tasks", taskId) # 正常處理了調(diào)用ZREM 刪除任務(wù) redis.SREM("tasks:lock", taskId) # 釋放鎖, 當(dāng)然如果是任務(wù)處理失敗 也可以釋放鎖 以便可以重試任務(wù) } }
如果某一段時(shí)間沒有任務(wù),會(huì)存在 ZRANGEBYSCORE 空轉(zhuǎn)的情況,增加了不必要的網(wǎng)絡(luò)調(diào)用
可以在獲取不到任務(wù)的時(shí)候,等待一個(gè)任務(wù)時(shí)間最小間隔 通常是1s
# 偽代碼 while true { nowTimestamp = time.now() # 獲取當(dāng)前時(shí)間戳 taskIds = redis.ZRANGEBYSCORE("tasks", "-inf", nowTimestamp) if len(taskIds) == 0 { sleep(1) continue } for taskId in taskIds { addSuccess = redis.SADD("tasks:lock", taskId) if addSuccess == 0 { # 寫入不成功 說明有其他進(jìn)程在處理該任務(wù) continue } # TODO 執(zhí)行任務(wù)處理邏輯 redis.ZREM("tasks", taskId) # 正常處理了調(diào)用ZREM 刪除任務(wù) redis.SREM("tasks:lock", taskId) # 釋放鎖, 當(dāng)然如果是任務(wù)處理失敗 也可以釋放鎖 以便可以重試任務(wù) } }
第二種 任務(wù)實(shí)際處理邏輯繼續(xù)下沉
當(dāng)前程序不進(jìn)行任務(wù)處理 只取到期的任務(wù),任務(wù)處理交給其他任務(wù)處理進(jìn)程專門處理,只向List里推送任務(wù)
# 偽代碼 # 定時(shí)獲取任務(wù)程序 保留一個(gè) while true { nowTimestamp = time.now() # 獲取當(dāng)前時(shí)間戳 taskIds = redis.ZRANGEBYSCORE("tasks", "-inf", nowTimestamp) if len(taskIds) == 0 { sleep(1) continue } for taskId in taskIds { redis.LPUSH("tasks:list", taskId) # 下推到隊(duì)列任務(wù) redis.ZREM("tasks", taskId) # 刪除指定元素 } } # 下層的任務(wù)處理程序 可以橫向擴(kuò)展運(yùn)行多個(gè)進(jìn)程 while true { taskId = redis.BRPOP("tasks:list", 10) # 還考慮服務(wù)器 閑置連接超時(shí) 斷開的異常 if taskId is null { continue } # TODO 執(zhí)行任務(wù)處理邏輯 # 任務(wù)如果處理失敗需要重試 可以考慮重新ZADD到 tasks 里 }
如果生產(chǎn)消息的速度非???,也是可以考慮把 “定時(shí)獲取任務(wù)程序” 也橫向擴(kuò)展,不過需要增加分布式鎖,避免重復(fù)消費(fèi),可參考 第一種 的處理方式;也可以將任務(wù)按業(yè)務(wù)類型分類,放在不同的sorted set 中分別處理,互不影響。
到此這篇關(guān)于Redis Sorted Set類型使用及應(yīng)用場景的文章就介紹到這了,更多相關(guān)Redis Sorted Set使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis消息隊(duì)列實(shí)現(xiàn)異步秒殺功能
在高并發(fā)場景下,為了提高秒殺業(yè)務(wù)的性能,可將部分工作交給 Redis 處理,并通過異步方式執(zhí)行,Redis 提供了多種數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)消息隊(duì)列,總結(jié)三種,本文詳細(xì)介紹Redis消息隊(duì)列實(shí)現(xiàn)異步秒殺功能,感興趣的朋友一起看看吧2025-04-04關(guān)于Redis?bigkeys命令會(huì)阻塞問題的解決
這篇文章主要介紹了關(guān)于Redis?bigkeys命令會(huì)阻塞問題的解決,今天分享一次Redis引發(fā)的線上事故,避免再次踩雷,實(shí)現(xiàn)快速入門,需要的朋友可以參考下2023-03-03通過docker和docker-compose安裝redis兩種方式詳解
這篇文章主要介紹了通過docker和docker-compose安裝redis的兩種方式,Docker安裝方式包括拉取鏡像、查看本地鏡像、運(yùn)行容器和測試連接,Docker Compose安裝方式包括目錄結(jié)構(gòu)、配置文件、啟動(dòng)和關(guān)閉容器、檢查啟動(dòng)情況以及查看CPU和內(nèi)存使用狀態(tài),需要的朋友可以參考下2024-12-12利用redis lua腳本實(shí)現(xiàn)時(shí)間窗分布式限流
Lua是一種輕量小巧的腳本語言,Redis是高性能的key-value內(nèi)存數(shù)據(jù)庫,在部分場景下,是對(duì)關(guān)系數(shù)據(jù)庫的良好補(bǔ)充,本文給大家介紹了如何利用redis lua腳本實(shí)現(xiàn)時(shí)間窗分布式限流,需要的朋友可以參考下2024-03-03Redis分布式鎖與Redlock算法實(shí)現(xiàn)
在Redis中,可以使用多種方式實(shí)現(xiàn)分布式鎖,如使用SETNX命令或RedLock算法,本文就來介紹一下Redis分布式鎖與Redlock算法實(shí)現(xiàn),感興趣的可以了解一下2023-12-12詳解Centos7下配置Redis并開機(jī)自啟動(dòng)
本篇文章主要介紹了Centos7下配置Redis并開機(jī)自啟動(dòng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2016-11-11Windows安裝Redis并添加本地自啟動(dòng)服務(wù)的實(shí)例詳解
這篇文章主要介紹了Windows安裝Redis并添加本地自啟動(dòng)服務(wù)的實(shí)例詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11