前言:
每次你在游戲中看到玩家排行榜啡省,或者在音樂應(yīng)用中瀏覽熱門歌單标沪,有沒有想過這個排行榜是如何做到實時更新的朱沃?當然苞轿,依靠 Redis 即可做到。
在技術(shù)領(lǐng)域逗物,我們經(jīng)常聽到「鍵值存儲」 這個詞搬卒。但在 Redis 的世界里,這只是冰山一角敬察。Redis 的對象秀睛,不僅僅是簡單的數(shù)據(jù),它們是為各種任務(wù)量身定制的超能工具莲祸。
接下來蹂安,讓我們走進 Redis 的對象世界椭迎,Redis 5.0版本就已經(jīng)支持了下面的 9 種類型,分別是 :字符串對象田盈、列表對象畜号、哈希對象、集合對象允瞧、有序集合對象简软、Bitmaps 對象、HyperLogLog 對象、Geospatial 對象、Stream對象攻谁。
Redis 對象:
首先,我們要知道疼蛾,Redis 中保存的數(shù)據(jù)是以鍵值對的形式存在的。
對象的類型與編碼
類型
在 Redis 的大家庭中艺配,每個鍵值對都有兩個重要的“身份證”察郁。那就是鍵的類型和值的類型。就好像我們的名字和職業(yè)转唉,其中名字(鍵)總是一個字符串皮钠,而職業(yè)(值)則可以是各種各樣:可以是字符串、列表赠法、哈希麦轰、集合,甚至是有序集合期虾。這就是我們所說的對象類型原朝,五彩斑斕驯嘱,各有特色镶苞。
編碼
我們都知道超級英雄有著不同的超能力,蜘蛛俠(Spider-Man) 有蜘蛛感應(yīng)鞠评,鋼鐵俠(Iron Man)有高科技裝備茂蚓。同樣,Redis 中的每個對象都有一種稱為“編碼”的隱藏能力剃幌。這是什么呢聋涨?
簡單說,編碼是對象的“內(nèi)部魔法”负乡。它決定了對象在 Redis 內(nèi)部的存儲方式牍白。就好像手機里的照片可以是 JPEG 或 PNG 格式,Redis 對象也可以有不同的編碼格式抖棘。
但為什么這很重要呢茂腥?因為不同的編碼方式意味著不同的存儲效率和性能狸涌。Redis 非常聰明,它會選擇最佳的編碼方式最岗,為我們節(jié)省空間和提高性能帕胆。
我們先來看下 Redis 對象結(jié)構(gòu)體聲明
typedef struct redisObject {
unsigned type:4; # 數(shù)據(jù)類型,使用了4位來表示
unsigned encoding:4; # 編碼方式
void *ptr; # 指向底層數(shù)據(jù)結(jié)構(gòu)的指針
} robj;
Redis 中的每個對象都是由 redisObject 結(jié)構(gòu)表示般渡,其中的 encoding 成員記錄了對象所使用的編碼懒豹,encoding 的取值不同,對象內(nèi)部使用的數(shù)據(jù)結(jié)構(gòu)也會有所不同驯用。關(guān)于 redis 對象的各個數(shù)據(jù)結(jié)構(gòu)的講解脸秽,本篇不涉及,后續(xù)會補上蝴乔。
分類
字符串對象
基本概念:
字符串對象是最簡單的類型豹储,也是二進制安全的,意味著可以存儲任何形式的數(shù)據(jù)淘这,例如 JPEG 圖片剥扣、序列化的對象或者純文本。
簡單圖解:
value 可以存儲任何類型的數(shù)據(jù):包括普通字符串铝穷,數(shù)值類型(int,float) 等
內(nèi)部實現(xiàn)
Redis 的 String 對象使用一種稱為 簡單動態(tài)字符串SDS(Simple Dynamic String) 的結(jié)構(gòu)來存儲數(shù)據(jù)钠怯。
對象結(jié)構(gòu)體聲明:
typedef struct redisObject {
unsigned type:4; # 數(shù)據(jù)類型,使用了4位來表示
unsigned encoding:4; # 編碼方式
void *ptr; # 指向底層數(shù)據(jù)結(jié)構(gòu)的指針
} robj;
SDS 結(jié)構(gòu)體聲明:
struct sdshdr {
size_t len; # 記錄buf數(shù)組中已使用字節(jié)的數(shù)量曙聂。
size_t alloc; # 記錄buf數(shù)組的總?cè)萘俊? char buf[]; # 字節(jié)數(shù)組晦炊,用于保存字符串。
};
String 對象的編碼
String 對象的編碼有三種: int宁脊、embstr断国、raw 。
- int 編碼 :對象 robj 的 ptr 成員指向的是 long 類型的整數(shù)
- embstr 和 raw 編碼 : 對象 robj 的 ptr 成員指向的是 sdshdr 結(jié)構(gòu)體榆苞,值存儲在 buf 中稳衬。
使用限制: 單個 String 對象的值可以存儲的數(shù)據(jù)大小上限為 512MB
常見命令:
基本操作
SET key value : 設(shè)置鍵的值
GET key : 獲取鍵的值
DEL key : 刪除鍵
> SET username "xiaokang"
OK
> GET username
"xiaokang"
> DEL username
(integer) 1
字符串操作
APPEND key value : 向字符串尾部追加
STRLEN key : 獲取字符串的長度
SETRANGE key offset value : 覆蓋部分內(nèi)容
GETRANGE key start end : 獲取子字符串
# 上面命令的參數(shù) offset、start坐漏、end 都指的是下標(從0開始)
# 初始鍵值對 : SET username "xiaokang"
> APPEND username 1998
(integer) 12
> GET username
"xiaokang1998"
> STRLEN username
(integer) 12
> SETRANGE username 0 kang
(integer) 12
> GET username
"kangkang1998"
> GETRANGE username 8 -1 # -1代表的是值的結(jié)尾
"1998"
數(shù)值操作
INCR/DECY key : 自增1或自減1
INCRBY/DECRBY key increment : 自增或自減整數(shù)薄疚,步長為 increment 必須為整數(shù),可正可負
INCRBYFLOAT key increment : 自增赊琳、自減浮點數(shù)街夭,increment 推薦使用浮點數(shù),代表你是在操作浮點數(shù)躏筏,可正可負
# redis 中沒有提供 DECRBYFLOAT板丽,所以要想遞減浮點數(shù),increment 為負即可
> set count 2
OK
> INCR count
(integer) 3
> DECR count
(integer) 2
127.0.0.1:6379> INCRBY count 3
(integer) 5
127.0.0.1:6379> DECRBY count 3
(integer) 2
> SET float_count 5.5
OK
> INCRBYFLOAT float_count 2.5
"8"
> INCRBYFLOAT float_count -3.5
"4.5"
批量操作
MSET key1 value1 key2 value2 : 批量設(shè)置鍵值
MGET key1 key2 : 批量獲取鍵值
> MSET username xiaokang age 25
OK
> GET username
"xiaokang"
> MGET username age
1) "xiaokang"
2) "25"
條件設(shè)置
SETNX key value : 僅當鍵不存在時設(shè)置值,鍵存在則不會執(zhí)行任何操作
MSETNX key value [key value ...] : 批量設(shè)置
> SETNX career programmer
(integer) 1
> MSETNX sex man hobby swim
(integer) 1
帶有過期時間的設(shè)置
SETEX key seconds value : 為鍵值設(shè)置過期時間
# 10s 后 redis 會自動刪除這個 key
> SETEX username 10 xiaokang
OK
應(yīng)用案例
計數(shù)器
描述: 利用 Redis 追蹤某些事物的數(shù)量趁尼。
具體應(yīng)用 :
1. 文章訪問計數(shù):文章的閱讀次數(shù)
#當某篇文章被訪問時埃碱,遞增該文章的閱讀計數(shù)器碴卧。
INCR article:12345:views
#獲取某篇文章的閱讀次數(shù)。
GET article:12345:views
2. 社交媒體互動計數(shù) :點贊數(shù)
#當某個帖子被點贊時乃正,遞增該帖子的點贊計數(shù)器住册。
INCR post:67890:likes
#檢查某個帖子的點贊數(shù)。
GET post:67890:likes
3. 實時統(tǒng)計 :例如瓮具,一個電商網(wǎng)站可以使用 Redis 來跟蹤網(wǎng)站上當前的在線用戶數(shù)量
#當用戶在線時荧飞,遞增在線用戶計數(shù)器。
INCR website:online_users
#檢查當前在線的用戶數(shù)量名党。
GET website:online_users
4. 限流: 例如叹阔,你可能想限制一個 API 在一定時間內(nèi)的調(diào)用次數(shù)。
# 設(shè)置一個 API 的調(diào)用次數(shù)限制传睹。這里以 60 秒內(nèi)最多調(diào)用 10 次為例耳幢。
SETEX api:call_limit:client_ip 60 10
# 當 API 被調(diào)用時,遞減調(diào)用計數(shù)器欧啤。如果值小于或等于 0睛藻,則表示已達到限流。
DECRBY api:call_limit:client_ip 1
# 檢查某個 API 的剩余調(diào)用次數(shù)邢隧。
GET api:call_limit:client_ip
分布式鎖 : 超越傳統(tǒng)的鎖機制
想象一下店印,一個電商網(wǎng)站正在進行一次秒殺活動,該活動只有100個商品庫存倒慧。當活動開始時按摘,數(shù)萬用戶同時嘗試購買這些商品。
如果秒殺系統(tǒng)只部署在一個服務(wù)器上纫谅,那么我們可以使用普通鎖來保證庫存不會被超賣炫贤。但是,現(xiàn)在的大型電商平臺的搶購系統(tǒng)都是部署在多個服務(wù)器上的付秕,所以單個服務(wù)器上的普通鎖并不能保證整個系統(tǒng)的數(shù)據(jù)一致性兰珍。
這時候,我們需要一個更強大的鎖:分布式鎖盹牧, 那什么是分布式鎖呢俩垃? 分布式鎖,顧名思義汰寓,是能在多個系統(tǒng)或多臺機器之間都起到限制訪問的“鎖”。
在秒殺活動這個場景中苹粟,分布式鎖確保了即便是數(shù)萬用戶在多個服務(wù)器上同時嘗試購買有滑,系統(tǒng)也能正確、有序地處理每一個購買請求嵌削,確保不會出現(xiàn)超賣的情況毛好。
基本概念:
分布式鎖是一種能夠在多個計算機望艺、服務(wù)器或節(jié)點之間確保任何時候只有一個進程在執(zhí)行的機制。它是在復(fù)雜的分布式環(huán)境中維持順序和一致性的關(guān)鍵工具肌访。
實現(xiàn)方式:
使用 Redis 實現(xiàn)分布式鎖一般步驟:
- 加鎖:SET lock_key unique_id EX expire_time NX
- lock_key : 分布式鎖名
- unique_id : 唯一標識符
- EX : 設(shè)置過期時間
- NX : 當 lock_key 不存在時命令才會成功
- 操作共享資源
- 釋放鎖:通過 Lua 腳本來釋放鎖找默,先 GET 判斷鎖是否歸屬自己,再 DEL 釋放鎖
Lua 腳本 : 用來保證釋放鎖操作的原子性
判斷鎖是自己的吼驶,才釋放
if redis.call("GET",KEYS[1]) == ARGV[1]
then
return redis.call("DEL",KEYS[1])
else
return 0
end
上面實現(xiàn)方式存在一個問題:分布式鎖的過期時間如何確定惩激?
如果客戶端預(yù)期的操作時間超過鎖的過期時間,這該怎么辦蟹演? 鎖的超時時間設(shè)置過長過短都不好风钻。一個合理的方案就是采用自動續(xù)期。
續(xù)期具體做法:
加鎖時酒请,我們設(shè)定一個到期時間骡技,啟動一個「守護線程」 定時查看鎖的狀態(tài)。如果鎖即將到期且任務(wù)未完成羞反,我們自動「續(xù)期」 這個鎖布朦,重新設(shè)置其到期時間。如果續(xù)期失敗昼窗,為避免并發(fā)問題右遭,客戶端應(yīng)立刻停止操作让腹。
然而幸運的是,一些編程語言已經(jīng)實現(xiàn)了專門的客戶端庫佃牛,如 Java 的 Redisson和 Go 的 redsync遍希,它們提供了簡化的分布式鎖實現(xiàn)漂洋。這些庫已內(nèi)置了自動續(xù)期等關(guān)鍵功能庭猩,避免開發(fā)者手動構(gòu)建這些邏輯于样。
注意:
以上只是實現(xiàn)了一個單機版的分布式鎖贬芥,而 Redis 在實際生產(chǎn)環(huán)境中都會采用主從集群 + 哨兵的模式部署孵构,這樣當主庫異常宕機時叹俏,哨兵可以實現(xiàn)「故障自動切換」,把從庫提升為主庫僻族,繼續(xù)提供服務(wù)粘驰,以此保證可用性。
我們來考慮一個問題:當「主從發(fā)生切換」時述么,這個分布鎖會依舊安全嗎蝌数?
關(guān)于這個問題我這里不做深入探討,感興趣的可以參考這篇文章:「鏈接地址在文章末尾」
緩存
描述:利用 Redis 緩存 MYSQL 等關(guān)系型數(shù)據(jù)庫查詢結(jié)果度秘,從而減少關(guān)系型數(shù)據(jù)庫的壓力顶伞。
具體實現(xiàn):
當用戶請求某個數(shù)據(jù)時饵撑,首先檢查 Redis 是否有這個數(shù)據(jù)。如果有唆貌,直接從 Redis 返回滑潘,這樣可以避免查詢數(shù)據(jù)庫。如果 Redis 中沒有锨咙,那么查詢關(guān)系型數(shù)據(jù)庫语卤,獲取數(shù)據(jù)后,存入 Redis酪刀,并設(shè)置一個適當?shù)倪^期時間粹舵。
簡單示例
func getUserInfo(rdb *redis.Client, userID string) string {
// 嘗試從 Redis 中獲取用戶信息
val, err := rdb.Get(ctx, userID).Result()
if err == redis.nil{
userData := queryDatabase(userID) #在這里模擬一個數(shù)據(jù)庫查詢
rdb.Set(ctx, userID, userData, time.Hour) # 緩存結(jié)果1小時
return userData
}
return val
}
func queryDatabase(userID string) string {
//查詢數(shù)據(jù)庫
// ...
return result
}
Session 存儲
Session 是什么:
每次你在網(wǎng)上購物時,在瀏覽骂倘、選擇商品眼滤、加入購物車,網(wǎng)站都能“記得”你的選擇历涝,這是因為它使用了"Session"诅需。簡單來說,Session 是服務(wù)器給你的一個小“記憶空間”睬关。
在日常的網(wǎng)頁瀏覽中诱担,每當一個用戶的請求到達服務(wù)器,例如頁面訪問电爹、API調(diào)用蔫仙,為了維持用戶狀態(tài)或提供個性化的服務(wù),系統(tǒng)通常需要讀取該用戶的 session 數(shù)據(jù)丐箩。
具體實現(xiàn):
當用戶登錄到一個系統(tǒng)時澎语,后端通常會為該用戶生成一個唯一的 session ID 蛉拙。這個 ID 會被傳回給客戶端,通常存儲在 cookie 中。隨后多搀,每次客戶端發(fā)出請求時领舰,都會攜帶這個 session ID蝌焚,允許服務(wù)器識別出該用戶移剪。
為了應(yīng)對這種頻繁的數(shù)據(jù)讀取需求,我們可以將這個 session ID 和 session 數(shù)據(jù)分別作為鍵值存儲到 redis 中瓤摧。 session 數(shù)據(jù)包括 : 用戶 ID竿裂、用戶名等。使用 Redis 來存儲 session 數(shù)據(jù)不僅提供了高速的讀取效率照弥,還讓用戶體驗更為流暢腻异。
使用示例:
# 在設(shè)置 session 存儲時,一般會設(shè)置過期時間的这揣。
SET session:userId expired_time "user_data_in_json_or_serialized_format"
GET session:userId
列表對象
基本概念
Redis 的列表對象是一個有序的字符串集合悔常,這里的有序指的是添加元素有先后順序的影斑,可以被看作是一個雙向鏈表。在 Redis 中机打,每個列表可以包含超過 4 億個元素矫户。
簡單圖解
內(nèi)部實現(xiàn)
- 壓縮列表 (Ziplist): 當列表對象保存的所有字符串元素的長度都小于 64 字節(jié)并且列表對象保存的元素數(shù)量小于 512 個,列表對象會使用壓縮列表(ziplist)作為其底層實現(xiàn)姐帚。
- 雙向鏈表(Linkedlist):不滿足上述兩個條件之一的, 列表對象會使用雙向鏈表(Linkedlist) 作為其底層實現(xiàn)吏垮。
常見命令:
查找元素
LRANGE key start stop : 獲取列表指定范圍內(nèi)的元素
LINDEX key index : 通過索引獲取列表中的元素
LLEN key : 返回列表的長度
# 查看列表對象的所有元素障涯,-1 代表列表對象的最后一個元素
> LRANGE myList 0 -1
1) "grape"
2) "banana"
3) "apple"
4) "orange"
> LINDEX myList 1
"banana"
> LLEN myList
(integer) 4
插入元素 :
- 普通插入
LPUSH key element [element ...] : 將一個或多個值插入到列表頭部
RPUSH key element [element ...] : 將一個或多個值插入到列表尾部
> LPUSH myList "apple" "banana"
(integer) 2
> LRANGE myList 0 -1
1) "banana"
2) "apple"
> RPUSH myList "orange" "pear"
(integer) 4
> LRANGE myList 0 -1
1) "banana"
2) "apple"
3) "orange"
4) "pear"
- 條件插入
LPUSHX key element [element ...] : 只有當列表存在時罐旗,將值插入到列表的頭部,如果列表不存在唯蝶,則什么也不做
RPUSHX key element [element ...] : 只有當列表存在時九秀,將值插入到列表的尾部,如果列表不存在粘我,則什么也不做
> LPUSHX myList "grape"
(integer) 5
> LRANGE myList 0 -1
1) "grape"
2) "banana"
3) "apple"
4) "orange"
5) "pear"
> LPUSHX noExistList "peach"
(integer) 0
刪除元素:
LPOP key [count] : 移除并返回列表的前 count 個元素
RPOP key [count] : 移除并返回列表的最后 count 個元素
LREM key count value : 移除列表中與參數(shù) value 相等的 count 個元素
# 先查看列表中的元素
> LRANGE myList 0 -1
1) "grape"
2) "banana"
3) "apple"
4) "orange"
5) "pear"
> LPOP myList
"grape"
> LRANGE myList 0 -1
1) "banana"
2) "apple"
3) "orange"
4) "pear"
> RPOP myList
"pear"
> LRANGE myList 0 -1
1) "banana"
2) "apple"
3) "orange"
修改元素:
LSET key index value : 通過索引來設(shè)置元素的值
LTRIM key start stop :裁剪列表
# 先查看列表中的元素
> LRANGE myList 0 -1
1) "grape"
2) "banana"
3) "apple"
4) "orange"
> LSET myList 0 "peach"
OK
> LRANGE myList 0 -1
1) "peach"
2) "banana"
3) "apple"
4) "orange"
> LTRIM myList 0 2
OK
> LRANGE myList 0 -1
1) "peach"
2) "banana"
3) "apple"
元素轉(zhuǎn)移:
RPOPLPUSH source destination: 將 source 列表的最后一個元素彈出鼓蜒,并將該元素添加到 destination 列表的頭部,同時返回該元素征字,如果 destination 列表不存在都弹,redis 會自動創(chuàng)建 destination 列表
> LRANGE myList 0 -1
1) "banana"
2) "apple"
> RPOPLPUSH myList destList
"apple"
> LRANGE destList 0 -1
1) "apple"
要注意的是列表對象并不存在 LPOPRPUSH 命令,可以通過組合 LPOP 和 RPUSH 命令來實現(xiàn)類似效果匙姜。
阻塞操作:
BLPOP key [key ...] timeout : 移除并獲取列表的第一個元素畅厢,或阻塞直到有一個可用
BRPOP key [key ...] timeout : 移除并獲取列表的最后一個元素,或阻塞直到有一個可用
BRPOPLPUSH source destination timeout : 將 source 列表的最后一個元素彈出氮昧,并將該元素添加到 destination 列表的頭部咪辱,同時返回該元素
# 先查看列表中的元素
> LRANGE myList 0 -1
1) "peach"
2) "banana"
3) "apple"
4) "orange"
> LRANGE noExistList 0 -1
(empty array)
> BLPOP myList noExistList 10
1) "myList"
2) "peach"
> LRANGE myList 0 -1
1) "banana"
2) "apple"
3) "orange"
> BRPOP myList noExistList 10
1) "myList"
2) "orange"
# 先查看列表的初識元素
> LRANGE myList 0 -1
1) "banana"
> LRANGE destList 0 -1
1) "apple"
> BRPOPLPUSH myList destList 10
"banana"
> LRANGE myList 0 -1
(empty array)
> LRANGE destList 0 -1
1) "banana"
2) "apple"
應(yīng)用案例:
1.消息隊列 :
當用戶在網(wǎng)上購物下訂單后,為了不讓他們等待各種后續(xù)處理(如檢查庫存寸癌、處理付款专筷、發(fā)貨),我們直接把訂單放入一個消息隊列灵份,然后由后臺進程從隊列中獲取并處理訂單。
實現(xiàn)步驟:
1. 生產(chǎn)者(下訂單的用戶)
LPUSH orders_queue order_id
2. 消費者服務(wù)獲取訂單( 消費者:后臺處理訂單的服務(wù))
BRPOP orders_queue 5
3. 處理訂單 :
一旦消費者從隊列中獲取了一個新訂單弦聂,它可以開始進行必要的處理莺葫,
例如檢查庫存匪凉、處理付款等聂受。
2.棧和隊列 :
棧: 想象一個瀏覽器的返回功能。用戶訪問了幾個頁面,你希望能夠提供一個[返回] 按鈕讓用戶回到他們之前瀏覽的頁面锣咒。
實現(xiàn)步驟:
1. 添加元素
LPUSH browser_history "page1"
LPUSH browser_history "page2"
LPUSH browser_history "page3"
2.當用戶點擊返回按鈕時:
LPOP browser_history # Fetches "page3", the most recently visited
隊列:
當我們在電商網(wǎng)站上瀏覽商品绽左、點擊廣告或執(zhí)行其他操作時悼嫉,這些行為都可以被捕獲為一個"事件"。每個事件通常都包含一些基本信息拼窥,如用戶ID戏蔑、商品ID蹋凝、點擊時間、頁面URL等总棵。
實現(xiàn)步驟:
- 用戶點擊事件捕獲
假設(shè)一個用戶點擊了一個商品鳍寂。此時,我們可以創(chuàng)建一個 JSON 對象來存儲這次點擊的詳細信息情龄。
$json = {
"user_id": "12345",
"product_id": "98765",
"timestamp": "2023-09-01T12:00:00Z",
"page_url": "/products/98765"
}
- 發(fā)送事件到隊列
LPUSH click_events $json
- 消費者進程獲取事件
RPOP click_events
當消費者進程從隊列中獲取事件后迄汛,可以進一步解析這個JSON對象,并進行所需的處理骤视,例如更新商品的點擊率等鞍爱,以便將該商品推薦給更多的用戶。
3.歷史追蹤:
可以使用 Redis 列表來跟蹤最近的歷史記錄尚胞,例如最近訪問的網(wǎng)頁或其他活動硬霍。
以最近訪問的網(wǎng)頁舉例說明: 在一個網(wǎng)站上,你可能希望追蹤用戶最近訪問了哪些頁面笼裳。每當用戶訪問一個新頁面時,你可以使用 LPUSH 將這個頁面 URL 添加到一個列表中粱玲,并使用 LTRIM 確保列表只保存最近 N 次訪問躬柬。
LPUSH user_recent_pages "/home"
LPUSH user_recent_pages "/product/1"
LTRIM user_recent_pages 0 9 # 保留最近10個訪問的頁面
使用 Redis 列表來跟蹤最近的歷史記錄能夠高效地保存和查詢用戶的近期活動,從而為用戶提供個性化的推薦抽减。
哈希對象
基本概念
Redis 哈希對象也是一種用于存儲鍵值對集合的數(shù)據(jù)結(jié)構(gòu)允青,它允許你將多個鍵值對存儲在一個 Redis 鍵中。
我們一般會使用 Redis 的 Hash 對象來存儲對象信息卵沉,比如用戶信息颠锉,用戶的名字、年齡史汗、愛好琼掠、電子郵箱、密碼等停撞。與使用普通的 key-value 存儲方式相比 , 哈希對象的存儲方式更為高效和節(jié)省空間瓷蛙,特別是當我們要存儲大量小對象時。
簡單圖解:
簡單說明:
假設(shè)你有一把大鑰匙戈毒,這把鑰匙可以打開一個特定的箱子艰猬。這箱子里面有很多物品,每個物品都有標簽來描述它埋市。
- 大鑰匙 就是我們的 key
- 箱子 就代表一個 哈希對象
- 箱子里的物品與其標簽 就是 field-value 鍵值對
內(nèi)部實現(xiàn)
- 壓縮列表(ziplist) : 當哈希對象保存的所有鍵值對的鍵和值的字符串長度都小于64字節(jié)并且哈希對象保存的鍵值對的數(shù)量小于 512 個冠桃,哈希對象會采用壓縮列表作為其底層實現(xiàn)。
- 字典(基于哈希表實現(xiàn)) : 當不能滿足上述條件之一時道宅,哈希對象則會采用字典作為其底層實現(xiàn)食听。
常見命令
設(shè)置值
HSET key field value [field value ...] : 設(shè)置哈希的 Field-Value 對,可以設(shè)置多個
HSETNX key field value : 只有在字段 field 不存在時套么,才設(shè)置對應(yīng)的 value 值,否則什么也不做碳蛋。
> HSET userInfo username xiaokang age 25 hobby swim
(integer) 3
獲取值
HGET key field : 獲取哈希指定字段的值
HMGET key field [field ...] : 獲取哈希多個字段的值 # 批量獲取
HGETALL key : 獲取哈希表中的所有字段和值
HKEYS key : 獲取哈希中所有的字段名(field)
HVALS key : 獲取哈希中所有的字段值(value)
> HGET userInfo username
"xiaokang"
> HMGET userInfo "username" "age"
1) "xiaokang"
2) "25"
> HGETALL userInfo
1) "username"
2) "xiaokang"
3) "age"
4) "25"
5) "hobby"
6) "swim"
> HKEYS userInfo
1) "username"
2) "age"
3) "hobby"
> HVALS userInfo
1) "xiaokang"
2) "25"
3) "swim"
自增操作
HINCRBY key field increment : 為哈希字段的整數(shù)值加上增量 # increment 可正可負
HINCRBYFLOAT key field increment : 為哈希字段的浮點數(shù)值加上增量 # increment 可正可負
> HGET userInfo age
"25"
> HINCRBY userInfo age 1
(integer) 26
> HGET userInfo age
"26"
# HINCRBYFLOAT 命令類似胚泌,increment 既可以是整數(shù)也可以是浮點數(shù),可正可負
刪除操作
HDEL key field [field ...] : 刪除一個或多個哈希表字段
# 先查看 hash 字段的所有字段和值
> HGETALL userInfo
1) "username"
2) "xiaokang"
3) "age"
4) "25"
5) "hobby"
6) "swim"
> HDEL userInfo "hobby"
(integer) 1
> HGETALL userInfo
1) "username"
2) "xiaokang"
3) "age"
4) "25"
其他操作
HEXISTS key field : 檢查哈希對象中是否存在給定的字段
HLEN key : 獲取哈希中字段的數(shù)量
HSTRLEN key field : 獲取哈希字段的字符串長度
# 先查看哈希的所有字段和值
> HGETALL userInfo
1) "username"
2) "xiaokang"
3) "age"
4) "25"
> HLEN userInfo
(integer) 2
> HSTRLEN userInfo "username"
(integer) 8
注意事項
- 小哈希優(yōu)化: Redis 對于哈希對象的內(nèi)存布局進行了優(yōu)化肃弟。小哈希(即哈希對象字段數(shù)量很少且字段值大小較戌枋摇)的內(nèi)存使用會更加高效。
- 避免大量刪除: 使用 HDEL 一次刪除大量字段可能會影響性能笤受,建議分批進行穷缤。
- 使用哈希而非多個鍵: 當需要存儲有關(guān)特定對象的多個相關(guān)字段時,使用單個哈希鍵比使用多個獨立的 Redis 鍵更為高效箩兽。
應(yīng)用案例
- 對象存儲 :
主要是指將某種實體或數(shù)據(jù)(如用戶信息)持久性地保存在 Redis 中津肛,如 :用戶的用戶名、郵箱汗贫、密碼等信息身坐。
保存用戶信息:
HSET user:1234 name "John Doe" email "john.doe@example.com" age 30
獲取用戶的郵箱:
HGET user:1234 email
- 對象緩存:
這是指當數(shù)據(jù)原本存儲在其他存儲系統(tǒng)(如關(guān)系型數(shù)據(jù)庫)中,但由于頻繁訪問或讀取落包,我們決定在 Redis 中緩存該數(shù)據(jù)的一份副本部蛇,以減少對原始數(shù)據(jù)源的訪問壓力并提高讀取速度。
場景描述:
假設(shè)你有一個博客網(wǎng)站咐蝇,用戶可以閱讀和評論各種博客文章涯鲁。每當用戶點擊一個博客標題,系統(tǒng)都會從關(guān)系型數(shù)據(jù)庫中獲取該文章的詳細內(nèi)容進行顯示有序。但是抹腿,由于某些熱門文章被大量用戶頻繁訪問,直接從數(shù)據(jù)庫中獲取文章可能會對數(shù)據(jù)庫造成很大的壓力旭寿,從而影響網(wǎng)站的性能警绩。
具體實現(xiàn):
偽代碼展示 :
import (
"github.com/go-redis/redis/v8"
"context"
)
var ctx = context.Background()
options := &redis.Options{
Addr: "localhost:6379", // Redis服務(wù)器地址
}
// 初始化Redis客戶端:
rdb := redis.NewClient(options)
// 檢查緩存:當用戶請求一篇文章時,首先檢查Redis緩存中是否存在該文章的數(shù)據(jù)许师。
exists, err := rdb.HExists(ctx, "article:ID", "title").Result()
if err != nil {
// Handle error
}
//從緩存中獲取數(shù)據(jù):如果文章存在于Redis中房蝉,則直接從Redis的哈希對象中獲取所有相關(guān)字段,并顯示給用戶微渠。
if exists {
articleData, err := rdb.HGetAll(ctx, "article:ID").Result()
if err != nil {
// Handle error
}
display(articleData)
}
//從數(shù)據(jù)庫中獲取數(shù)據(jù):如果文章不在Redis緩存中搭幻,則從關(guān)系型數(shù)據(jù)庫中獲取數(shù)據(jù)保存到 Redis 緩存中,并且設(shè)置超時逞盆。
if !exists {
articleData := Database.fetchArticleByID("article:ID")
_, err := rdb.HMSet(ctx, "article:ID", map[string]interface{}{
"title": articleData.Title,
"author": articleData.Author,
"content": articleData.Content,
}).Result()
if err != nil {
// Handle error
}
rdb.Expire(ctx, "article:ID", time.Hour) // 設(shè)置1小時的過期時間
// 顯示給用戶
display(articleData)
}
//顯示給用戶的函數(shù)
func display(articleData map[string]string) {
// ... 你的顯示邏輯
}
- 實時統(tǒng)計 :
為了跟蹤網(wǎng)站的實時活動檀蹋,我們可以使用哈希來保存當前在線用戶、頁面瀏覽量和API 請求次數(shù)等。
增加在線用戶數(shù):
HINCRBY website:stats online_users 1
減少在線用戶數(shù):
HINCRBY website:stats online_users -1
記錄每個頁面的訪問次數(shù):
HINCRBY pageviews:20230901 "/home" 1
集合對象
基本概念:
集合對象(Set)是一種存儲多個唯一元素的無序集合數(shù)據(jù)結(jié)構(gòu)俯逾,它提供了豐富的操作使得集合對象成為非常強大和靈活的工具贸桶。集合對象特別適用于存儲不允許重復(fù)的數(shù)據(jù)項,例如標簽桌肴、社交網(wǎng)絡(luò)中的好友列表或者任何需要快速判斷某個元素是否存在的場景皇筛。
簡單圖解
內(nèi)部實現(xiàn)
- 整數(shù)集合(intset) : 當集合對象保存的所有元素都是整數(shù)或者集合對象保存的整數(shù)個數(shù)不超過512個時,集合對象采用 intset 作為其底層實現(xiàn)坠七。
- 字典(基于哈希表實現(xiàn)): 不滿足上面的兩個條件之一水醋,則采用字典作為其底層實現(xiàn)。
常見命令
基本操作:
SADD key member [member ...] :向集合添加一個或多個成員
SMEMBERS key :獲取集合中的所有成員
SCARD key :獲取集合的成員數(shù)量
SISMEMBER key member :判斷 member 元素是否是集合 key 的成員
SREM key member [member ...] :移除集合中的一個或多個成員
> SADD mySet "apple" "banana" "peach"
(integer) 3
> SMEMBERS mySet
1) "peach"
2) "banana"
3) "apple"
> SCARD mySet
(integer) 3
> SISMEMBER mySet "peach" # 成員存在返回 1
(integer) 1
> SISMEMBER mySet "pear" # 成員不存在返回 0
(integer) 0
> SREM mySet "peach"
(integer) 1
> SMEMBERS mySet
1) "banana"
2) "apple"
集合運算:
SINTER key [key ...] :返回所有給定集合的交集
SINTERSTORE destination key [key ...] :交集存儲在 destination 集合中
SUNION key [key ...] :返回所有給定集合的并集
SUNIONSTORE destination key [key ...] :并集存儲在 destination 集合中
SDIFF key [key ...] :返回第一個集合與其他集合之間的差集
SDIFFSTORE destination key [key ...] :差集存儲在 destination 集合中
> SMEMBERS mySet
1) "pear"
2) "banana"
3) "apple"
> SMEMBERS yourSet
1) "peach"
2) "banana"
3) "apple"
> SINTER mySet yourSet
1) "banana"
2) "apple"
> SINTERSTORE resultSet mySet yourSet
(integer) 2
> SMEMBERS resultSet
1) "banana"
2) "apple"
# 并集我這里不提了彪置,和交集操作類似
> SDIFF mySet yourSet
1) "pear"
> SDIFFSTORE result mySet yourSet
(integer) 1
> SMEMBERS result
1) "pear"
隨機操作:
SRANDMEMBER key [count] :隨機返回集合中的一個或多個成員
SPOP key [count] :隨機移除并返回集合中的一個或多個成員
> SMEMBERS mySet
1) "pear"
2) "banana"
3) "apple"
# 可以看到每次返回的成員都不一樣
> SRANDMEMBER mySet
"pear"
> SRANDMEMBER mySet
"apple"
> SPOP mySet
"pear"
> SMEMBERS mySet
1) "banana"
2) "apple"
其他操作:
SMOVE source destination member : 將 member 成員從 source 集合移動到 destination 集合
> SMEMBERS mySet
1) "pear"
2) "banana"
3) "apple"
> SMEMBERS yourSet
1) "peach"
2) "banana"
3) "apple"
> SMOVE mySet yourSet "pear"
(integer) 1
> SMEMBERS mySet
1) "banana"
2) "apple"
> SMEMBERS yourSet
1) "peach"
2) "pear"
3) "banana"
4) "apple"
應(yīng)用案例
- 社交網(wǎng)站的好友與關(guān)注系統(tǒng)
利用集合來存儲每個用戶的朋友或關(guān)注者列表拄踪。例如,我們可以為每個用戶維護一個集合拳魁,其中包含他們的所有朋友或關(guān)注者惶桐。
SADD "alice:friends" "bob" # Alice 關(guān)注了 Bob
SADD "bob:friends" "charlie" # Bob 關(guān)注了 Charlie
SISMEMBER "alice:friends", "bob" # 判斷 Bob 是否是 Alice 的朋友
- 標簽系統(tǒng)
可以為內(nèi)容(如文章、圖片等)添加標簽潘懊,并利用集合存儲這些標簽姚糊。
場景描述:假設(shè)你正在運行一個博客平臺,你希望為每篇文章添加一組標簽卦尊,使用戶可以更容易地根據(jù)主題或興趣查找相關(guān)文章叛拷。
1.為文章添加標簽:
# 當你發(fā)布文章"Redis入門指南",其ID為1001岂却,你為它添加了標簽"technology", "tutorial",和"redis"。
SADD tag:technology 1001
SADD tag:tutorial 1001
SADD tag:redis 1001
2.查找具有特定標簽的文章
# 如果用戶想要查看所有與"redis"相關(guān)的文章:
SMEMBERS tag:redis # 輸出 1001 :這表示文章ID為1001的文章帶有"redis"標簽裙椭。
3.獲取文章的所有標簽
# 每當你需要查看某篇文章的標簽躏哩,你可以為其創(chuàng)建一個集合。以文章ID為1001為例:
SADD article:1001:tags "technology"
SADD article:1001:tags "tutorial"
SADD article:1001:tags "redis"
# 要查看文章1001的所有標簽:
SMEMBERS article:1001:tags
# 輸出:
1) "technology"
2) "tutorial"
3) "redis"
- 唯一計數(shù)
例如揉燃,記錄網(wǎng)站的獨立訪客數(shù)扫尺。集合可以幫助我們做到這一點,因為它們只存儲唯一的元素炊汤。
1.存儲當天登錄的所有用戶ID:
SADD visitors:2023-09-20 user12345
SADD visitors:2023-09-20 user67890
2.獲得當天登錄的獨立用戶數(shù)
SCARD visitors:2023-09-20
有序集合對象
基本概念:
Redis 有序集合(Sorted Set)對象是一種存儲非重復(fù)元素集合的數(shù)據(jù)結(jié)構(gòu)正驻,每個元素都關(guān)聯(lián)一個雙精度浮點數(shù)分數(shù)(score),用于維護元素之間的排序順序抢腐。有序集合支持高效的元素插入和刪除操作姑曙,同時保持元素按分數(shù)排序,使其非常適合于需要按照排序順序訪問元素的場景迈倍,如排行榜伤靠、帶權(quán)重的任務(wù)隊列等。
簡單圖解:
內(nèi)部實現(xiàn)
- 壓縮列表(ziplist): 當有序集合保存的元素數(shù)量小于 128 個 并且有序集合保存的所有元素成員的長度小于 64 字節(jié)啼染,有序集合對象會采用 ziplist 作為其底層實現(xiàn)宴合。
- 跳表(skiplist ):不滿足以上2個條件之一的采用 skiplist 作為其底層實現(xiàn)焕梅。
常見命令
添加與更新成員
ZADD key score member [score member ...] : 添加一個或多個成員到有序集合,或者更新已存在成員的分數(shù)
ZINCRBY key increment member : 增加有序集合中指定成員的分數(shù)
> ZADD myZSet 10 member1 20 member2
(integer) 2
# 獲取有序集合中的所有成員及其分數(shù)
> ZRANGE myZSet 0 -1 WITHSCORES
1) "member1"
2) "10"
3) "member2"
4) "20"
> ZINCRBY myZSet 5 "member1"
"15"
> ZRANGE myZSet 0 -1 WITHSCORES
1) "member1"
2) "15"
3) "member2"
4) "20"
查詢操作
ZCARD key : 獲取有序集合的成員數(shù)量
ZSCORE key member : 獲取指定成員的分數(shù)值
ZRANK key member : 返回有序集合中指定成員的排名卦洽,排名依次是 0贞言,1,2 ...
ZREVRANK key member : 返回有序集合中指定成員的排名阀蒂,成員按分數(shù)值遞減排列
> ZCARD myZSet
(integer) 2
> ZSCORE myZSet "member2"
"20"
> ZRANK myZSet "member1"
(integer) 0
> ZRANK myZSet "member2"
(integer) 1
> ZREVRANK myZSet "member2"
(integer) 0
> ZREVRANK myZSet "member1"
(integer) 1
ZRANGE key min max [WITHSCORES] : 返回有序集中指定區(qū)間內(nèi)的成員 # min 和 max 是要獲取的排名范圍(排名可以理解為索引岗宣,從0開始,其中0是分數(shù)最低的成員)惭蹂,可選的 [WITHSCORES] 參數(shù)表示除了返回成員名外谒府,還要返回它們的分數(shù)
ZREVRANGE key start stop [WITHSCORES] : 返回有序集中指定區(qū)間內(nèi)的成員,成員按分數(shù)遞減排列
> ZRANGE myZSet 0 1
1) "member1"
2) "member2"
> ZRANGE myZSet 0 1 WITHSCORES
1) "member1"
2) "15"
3) "member2"
4) "20"
> ZREVRANGE myZSet 0 1
1) "member2"
2) "member1"
> ZREVRANGE myZSet 0 1 WITHSCORES
1) "member2"
2) "20"
3) "member1"
4) "15"
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] : 獲取分數(shù)在指定區(qū)間的所有成員争便,建議:max >= min
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]: 獲取分數(shù)在指定區(qū)間的所有成員级零,成員按分數(shù)遞減排列; 建議:max >= min
#[LIMIT offset count] 這是一個限制返回的成員數(shù)量的可選項。其中滞乙,offset 是開始返回的起始位置(基于0的索引)奏纪,count 是返回的成員總數(shù)。例如斩启,LIMIT 1 3 會返回從下標為1開始的3個成員
> ZRANGEBYSCORE myZSet 16 22
1) "member2"
> ZRANGEBYSCORE myZSet 16 22 WITHSCORES
1) "member2"
2) "20"
> ZRANGEBYSCORE myZSet 15 22 WITHSCORES LIMIT 0 2
1) "member1"
2) "15"
3) "member2"
4) "20"
> ZREVRANGEBYSCORE myZSet 22 15 WITHSCORES
1) "member2"
2) "20"
3) "member1"
4) "15"
ZCOUNT key min max: 計算在有序集合中指定區(qū)間分數(shù)的成員數(shù)
ZLEXCOUNT key min max: 用于計算基于成員名稱的字典序位于指定區(qū)間內(nèi)的成員數(shù)量 # 對于 min和 max序调,要使用方括號 [ 或 ( 來指示范圍的邊界是否包含在計數(shù)中。方括號 [ 表示該值是包含的兔簇,而小括號 ( 表示該值是不包含的发绢。
> ZCOUNT myZSet 15 20
(integer) 2
> ZLEXCOUNT myZSet [a [z
(integer) 2
> ZLEXCOUNT myZSet [a [b
(integer) 0
ZSCAN key cursor [MATCH pattern] [COUNT count]: 迭代Redis的有序集合(ZSET)的元素,包括它的成員和分數(shù)垄琐。是一種漸進地遍歷有序集的方法边酒,而不是一次返回所有結(jié)果,這對于大型數(shù)據(jù)集尤為有用狸窘,因為它不會因為要返回大量的結(jié)果而阻塞服務(wù)器墩朦。
# cursor: 用于迭代的游標。初次調(diào)用時翻擒,通常設(shè)置為"0"氓涣,之后的調(diào)用將使用上次返回的游標值。
MATCH pattern (可選): 一個可選的匹配模式陋气,用于篩選具有特定模式的元素劳吠。
COUNT count (可選): 提示服務(wù)器每次迭代應(yīng)返回多少元素。實際數(shù)目可能會稍多或稍少恩伺。
> ZSCAN myZSet 0
1) "0"
2) 1) "member1"
2) "15"
3) "member2"
4) "20"
> ZSCAN myZSet 0 MATCH mem*
1) "0"
2) 1) "member1"
2) "15"
3) "member2"
4) "20"
> ZSCAN myZSet 0 COUNT 1
1) "0"
2) 1) "member1"
2) "15"
3) "member2"
4) "20"
刪除操作
ZREM key member [member ...] : 移除有序集合中的一個或多個成員
# 先查看有序集合中的成員及分數(shù)
> ZRANGE myZSet 0 -1 WITHSCORES
1) "member1"
2) "15"
3) "member2"
4) "20"
> ZREM myZSet "member1"
(integer) 1
> ZRANGE myZSet 0 -1 WITHSCORES
1) "member2"
2) "20"
ZPOPMAX key [count] : 移除并返回有序集合中的最大的一些成員
> ZADD myZSet 15 "member1" 30 "member3" 55 "member5" 40 "member4"
(integer) 4
> ZRANGE myZSet 0 -1 WITHSCORES
1) "member1"
2) "15"
3) "member2"
4) "20"
5) "member3"
6) "30"
7) "member4"
8) "40"
9) "member5"
10) "55"
> ZPOPMAX myZSet 2
1) "member5"
2) "55"
3) "member4"
4) "40"
> ZRANGE myZSet 0 -1 WITHSCORES
1) "member1"
2) "15"
3) "member2"
4) "20"
5) "member3"
6) "30"
ZPOPMIN key [count] : 移除并返回有序集合中的最小的一些成員
# ZPOPMIN 我這里就不舉例了赴背,和 ZPOPMAX 類似
ZREMRANGEBYRANK key start stop : 移除有序集合中給定的排名區(qū)間的所有成員
ZREMRANGEBYSCORE key min max : 移除有序集合中給定的分數(shù)區(qū)間的所有成員
ZREMRANGEBYLEX key min max : 移除有序集合中給定的成員名字典區(qū)間的所有成員
> ZREMRANGEBYSCORE myZSet 20 30
(integer) 1
> ZRANGE myZSet 0 -1 WITHSCORES
(empty array)
> ZADD myZSet 15 "member1" 30 "member3" 55 "member5" 40 "member4"
(integer) 4
> ZRANGE myZSet 0 -1 WITHSCORES
1) "member1"
2) "15"
3) "member3"
4) "30"
5) "member4"
6) "40"
7) "member5"
8) "55"
> ZREMRANGEBYLEX myZSet ["member1" ["member4"
(integer) 3
> ZRANGE myZSet 0 -1 WITHSCORES
1) "member5"
2) "55"
集合操作
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] : 計算給定的一個或多個有序集的并集,并存儲在新的 destination 中
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] : 計算給定的一個或多個有序集的交集,并存儲在新的 destination 中
說明:
destination - 新的有序集合的名字凰荚,用于存放結(jié)果燃观。
numkeys - 指定將要合并的有序集合的數(shù)量。
key [key ...] - 需要合并的有序集合的名字便瑟。
WEIGHTS - 可選參數(shù)缆毁,用于為每一個輸入的有序集分配一個乘法因子。例如到涂,如果某個集合的 WEIGHT 是2脊框,那么在計算并集時,該集合中的每個元素的分數(shù)都會乘以2践啄。
AGGREGATE - 可選參數(shù)浇雹,它決定了當多個有序集合中存在相同元素時,如何處理這些元素的分數(shù)屿讽。有三個選項:SUM(默認)昭灵、MIN和MAX。SUM將相同元素的分數(shù)加起來伐谈,MIN 使用最小分數(shù)烂完,而 MAX 使用最大分數(shù)。
> ZADD set1 1 "one" 2 "two"
(integer) 2
> ZADD set2 1 "one" 3 "three"
(integer) 2
> ZRANGE set1 0 -1 WITHSCORES
1) "one"
2) "1"
3) "two"
4) "2"
> ZRANGE set2 0 -1 WITHSCORES
1) "one"
2) "1"
3) "three"
4) "3"
> ZUNIONSTORE result 2 set1 set2
(integer) 3
> ZRANGE result 0 -1 WITHSCORES
1) "one"
2) "2"
3) "two"
4) "2"
5) "three"
6) "3"
# 當然诵棵,你可以使用 WEIGHTS 和 AGGREGATE 參數(shù)來調(diào)整結(jié)果的計算方式抠蚣。例如,如果你想要為 set1 的每個成員加倍其分數(shù)履澳,然后和 set2 求并集嘶窄,并取每個成員的最大值作為結(jié)果:
# set1 set2 兩個集合的初始值沒變,還是上面的數(shù)據(jù)
> ZUNIONSTORE result 2 set1 set2 WEIGHTS 2 1 AGGREGATE MAX
(integer) 3
> ZRANGE result 0 -1 WITHSCORES
1) "one"
2) "2"
3) "three"
4) "3"
5) "two"
6) "4"
應(yīng)用案例
實時排行榜
- 游戲排行榜
在線游戲的實時排行榜距贷,玩家或用戶的得分可以即時更新护侮,并按分數(shù)進行排序。
# 添加或更新分數(shù)
ZADD leaderboard 1500 "player1"
ZADD leaderboard 2200 "player2"
ZADD leaderboard 1800 "player3"
#查詢前3名玩家
ZREVRANGE leaderboard 0 2 WITHSCORES
# 獲取某個玩家的排名
ZREVRANK leaderboard "player1"
- 網(wǎng)站文章或視頻的熱門排行
網(wǎng)站可能希望展示其上點擊量最高的文章或視頻储耐。每次有用戶點擊時,相關(guān)內(nèi)容的計數(shù)就會增加滨溉,然后可以使用有序集合實時顯示熱門內(nèi)容什湘。
# 文章被點擊:
ZINCRBY article_views 1 "article123"
#獲取最熱門的 3 篇文章
ZREVRANGE article_views 0 2 WITHSCORES
- 電商平臺的熱門產(chǎn)品排行
電商平臺可能希望展示最受歡迎的產(chǎn)品。每當產(chǎn)品被購買晦攒,其熱度都會增加闽撤。
#產(chǎn)品被購買:
ZINCRBY product_sales 1 "product123"
#獲取最受歡迎的10個產(chǎn)品
ZREVRANGE product_sales 0 9 WITHSCORES
時間線事件記錄
場景描述:假設(shè)我們正在為一個社交網(wǎng)絡(luò)網(wǎng)站設(shè)計功能,用戶每次登錄脯颜、發(fā)帖或評論都會在其時間線上生成一個事件哟旗。我們希望可以跟蹤這些事件并能夠檢索特定時間范圍內(nèi)的事件。
#用戶在某個時間點登錄了網(wǎng)站
ZADD user123:timeline 1631498200 "Logged in"
#該用戶稍后發(fā)表了一篇文章
ZADD user123:timeline 1631498300 "Posted an article about Redis"
#查詢該用戶在指定的時間范圍內(nèi)的所有事件
ZRANGEBYSCORE user123:timeline 1631498200 1631498400
延遲任務(wù)隊列
場景描述:用戶訂閱了一個在線服務(wù),比如說一個音樂服務(wù)闸餐,它提供了一個月的免費試用饱亮。為了提醒用戶及時續(xù)費或者保存他們的歌單數(shù)據(jù),服務(wù)提供商可以在試用期結(jié)束前的幾天舍沙,發(fā)送一個“您的試用即將結(jié)束近上,請及時續(xù)費”的提醒。這個提醒任務(wù)就可以放入延遲隊列拂铡。
具體實現(xiàn)步驟:
# 用戶注冊: 當用戶開始他們的免費試用時壹无,你會將這個用戶和他們試用結(jié)束時間的時間戳(減去3天,這樣在結(jié)束前3天提醒他們)加入到一個有序集合中感帅。
ZADD trial_end_reminders (start_timestamp + trial_period - few_days) user_id # 一般時間戳是以秒為單位的斗锭,對于這個例子,trial_period = 24*86400失球,few_days = 3 * 86400岖是。
#后臺任務(wù):后臺任務(wù)每天定期檢查這個有序集合,查看哪些用戶需要在今天被提醒她倘。
ZRANGEBYSCORE trial_end_reminders (current_timestamp) (current_timestamp + 86400)
#發(fā)送提醒:對于上面命令返回的每一個用戶璧微,你的系統(tǒng)會發(fā)送一個提醒郵件或者應(yīng)用內(nèi)通知,告訴他們試用即將結(jié)束硬梁,并提供一個續(xù)訂鏈接前硫。
#清理:在發(fā)送提醒后,你需要從有序集合中移除這些用戶荧止,確保他們不會被再次提醒屹电。
ZREM trial_end_reminders user_id_1 user_id_2 ...
Bitmaps
基本概念:
Redis 的 Bitmaps(位圖)是一種特殊的數(shù)據(jù)結(jié)構(gòu),用于高效地處理大量的布爾值(true/false或者1/0)跃巡。在 Redis 中危号,Bitmaps 實際上并不是一種獨立的數(shù)據(jù)類型,而是字符串(String)類型的一種特殊操作方式素邪。通過 位操作 命令外莲,Redis 允許用戶在一個很大的字節(jié)數(shù)組中設(shè)置和獲取位(bit)的值。
簡單圖解:
內(nèi)部實現(xiàn)
Bitmaps 實際上就是一個 String兔朦,底層采用字節(jié)數(shù)組來存儲數(shù)據(jù)偷线。
Bitmaps 提供了一系列的命令來操作和查詢二進制位。
基本命令:
設(shè)置和獲取位值:
SETBIT key offset value : 設(shè)置或清除操作只需將對應(yīng)位 置 0 即可指定的位 # 清除操作只需將對應(yīng)位 置 0 即可沽甥, offset只能是非負整數(shù)声邦。
GETBIT key offset : 獲取指定位的值
# 將位于索引7的位設(shè)置為1
> SETBIT mymap 7 1
(integer) 0
# 獲取位于索引7的位的值
> GETBIT mymap 7
(integer) 1
統(tǒng)計和查找:
BITCOUNT key :計算整個字符串中設(shè)置為1的位數(shù)
BITCOUNT key [start end] :計算在指定范圍內(nèi)設(shè)置為1的位數(shù)
BITPOS key bit [start] [end] : 找到第一個設(shè)置為1或0的位
其中,start和end是字符串的字節(jié)索引(不是bit索引)
> BITCOUNT mymap
(integer) 1
> BITCOUNT mymap 0 1 # 計算從字節(jié)0到字節(jié)1之間設(shè)置為1的位數(shù)
(integer) 1
> BITPOS mymap 1
(integer) 7
> BITPOS mymap 0
(integer) 0
其他運算
BITOP operation destkey key [key ...] : 對一個或多個bitmaps進行位運算
operation 可能是 AND, OR, XOR, NOT 其中一種
AND:邏輯與 摆舟、OR:邏輯或亥曹、XOR:邏輯異或邓了、NOT:邏輯非
> SETBIT yourmap 5 1
(integer) 0
> BITOP AND destMap mymap yourmap
(integer) 1 # 返回 1,表示 destMap 的長度是 1 字節(jié)
應(yīng)用案列:
員工打卡簽到:
大家都知道媳瞪,員工打卡簽到系統(tǒng)幾乎是每家公司的標配功能骗炉。每天上班,員工都需要打卡來記錄他們的出勤情況材失。今天我們來探討一下痕鳍,如何利用 Redis 中的 Bitmaps 來高效地實現(xiàn)這個功能。
為什么選擇 Bitmaps?
員工打卡簽到無非就是兩種結(jié)果:簽到和未簽到龙巨。這種二元狀態(tài)正好與Bitmaps的存儲方式相契合笼呆,每位員工的每天簽到狀態(tài)只需用一個位(0或1)來表示。
每天使用一個特定日期格式的 key(如 user:12345:attendance:2023-09-01)存儲Bitmaps旨别。員工的 ID 直接作為位索引诗赌,如 ID 為 42 的員工簽到,則將sign_2023-09-01的第 42 位設(shè)為 1秸弛。
示例:
#員工打卡簽到
SETBIT user:42:attendance:2023-09-01 42 1 # 員工 ID 42 在2023-09-01簽到
SETBIT user:42:attendance:2023-09-01 1024 1 # 員工 ID 1024 在2023-09-01簽到
#檢查特定用戶是否在某天簽到
GETBIT user:42:attendance:2023-09-01 42 # 查看員工 ID 42 在2023-09-01是否簽到
GETBIT user:42:attendance:2023-09-01 500 # 查看員工 ID 500 在2023-09-01是否簽到
#計算某一天的簽到用戶數(shù)
BITCOUNT user:42:attendance:2023-09-01 # 查看2023-09-01的簽到用戶數(shù)
日活跟蹤:
記錄特定日期所有活躍用戶的信息铭若。例如,我們可以使用一個特定日期的 bitmaps 來跟蹤該日期的所有活躍用戶递览。
# 用戶 ID 為 12345 和 67890 在 2023-09-01 都活躍了
SETBIT active_users:2023-09-01 12345 1
SETBIT active_users:2023-09-01 67890 1
# 檢查用戶 ID 為 12345 在 2023-09-01 是否活躍
GETBIT active_users:2023-09-01 12345
#檢查某一天所有的活躍用戶數(shù)
BITCOUNT user:42:attendance:2023-09-01
用戶登錄:
可以使用 bitmaps 來跟蹤用戶的登錄狀態(tài)叼屠,例如,確定用戶是否已登錄绞铃。
# 用戶 ID 為 12345 登錄了
SETBIT user:12345:login_status 1
# 檢查用戶 ID 為 12345 是否登錄
GETBIT user:12345:login_status
這三個應(yīng)用的關(guān)注點區(qū)別:
打卡簽到: 主要關(guān)注個體用戶的連續(xù)行為镜雨,例如連續(xù)簽到多少天。
日活跟蹤: 關(guān)注整體的用戶行為儿捧,例如在某一天有多少用戶活躍了荚坞。
用戶登錄: 主要跟蹤用戶的即時狀態(tài),例如某用戶當前是否在線或已登錄菲盾。
HyperLogLog
在大數(shù)據(jù)應(yīng)用中颓影,我們經(jīng)常需要計算或估算某個集合中不重復(fù)元素的數(shù)量(基數(shù)),例如統(tǒng)計網(wǎng)站的獨立訪客(UV)懒鉴。但當數(shù)據(jù)規(guī)模非常大時诡挂,傳統(tǒng)的統(tǒng)計方法會消耗大量的存儲和計算資源。這時临谱,Redis 的 HyperLogLog(簡稱HLL)提供了一個很好的解決方案咆畏。
基本概念:
從廣義上說,HyperLogLog 是一個概率性的數(shù)據(jù)結(jié)構(gòu)吴裤,用于估算集合的基數(shù)(即不重復(fù)元素的數(shù)量)。它不會提供完美準確的計數(shù)溺健,但它使用的存儲空間非常新笪(最多使用12KB的內(nèi)存)钮蛛。
在 Redis 的語境中,我們可以將 HyperLogLog 視為一種特定的數(shù)據(jù)類型剖膳。
簡單圖解:
步驟說明:
- 輸入值通過哈希函數(shù)生成固定長度的二進制哈希值魏颓。
- 哈希值的前幾位決定了它應(yīng)該進入數(shù)組的位置。
- 存儲值吱晒,將給定值的前導(dǎo)零的個數(shù)存儲在數(shù)組的對應(yīng)位置
- 數(shù)組中的值可能會根據(jù)新的哈希值進行更新(新的哈希值的前導(dǎo)零個數(shù)大于數(shù)組值才會更新)
- 根據(jù)數(shù)組元素進行基數(shù)估計
內(nèi)部實現(xiàn)
Redis 的 HyperLogLog 是一種特殊的數(shù)據(jù)類型甸饱,用來估計一個集合中有多少不同的元素。它不會給出完全準確的答案仑濒,但它的估計接近真實值叹话,并且使用的存儲空間非常小。它是基于一種名為 'HyperLogLog' 的聰明算法來工作的墩瞳,這種算法使用概率學(xué)的魔法來做估計驼壶,而不是真正地數(shù)每一個元素。
基本命令:
PFADD key element [element ...] : 添加元素
PFCOUNT key [key ...] : 查詢基數(shù)估計值
PFMERGE destkey sourcekey [sourcekey ...] : 合并多個 HyperLogLog 數(shù)據(jù)集
> PFADD myhll a b c d e f g
(integer) 1
> PFCOUNT myhll
(integer) 7
> PFADD yourhll a b c d h i j k
(integer) 1
> PFMERGE desthll myhll yourhll
OK
> PFCOUNT desthll
(integer) 11
應(yīng)用案例:
網(wǎng)站的 UV(獨立訪客)統(tǒng)計
當你要統(tǒng)計一個網(wǎng)站的UV(獨立訪客)時喉酌,你的目標是確定有多少獨立的用戶訪問了你的網(wǎng)站热凹,而不是訪問的總次數(shù)。傳統(tǒng)的方法(比如泪电,基于關(guān)系型數(shù)據(jù)庫的計數(shù))可能會很耗資源般妙,特別是當訪客量非常大時。
而 Redis 的 HyperLogLog 提供了一個空間效率非常高的方式來進行這樣的估算相速。每當有用戶訪問網(wǎng)站時碟渺,你可以記錄其 IP 地址或者某個與用戶相關(guān)的唯一標識符(sessionID),然后使用 PFADD 命令將其加入到 HyperLogLog 中和蚪。
具體實現(xiàn):
為每個獨立的訪客生成唯一標識:
現(xiàn)在大部分網(wǎng)站都會采用 cookie 技術(shù)止状。每當用戶訪問網(wǎng)站時,服務(wù)器都會為這位新訪客生成一個唯一的 ID 并將其存儲在 cookie 中攒霹。這個唯一的 ID 用于在后續(xù)的訪問中識別該用戶怯疤,從而跟蹤其在網(wǎng)站上的行為和偏好。
我們使用 user_cookie_12345 催束,user_cookie_12346 等來標識不同的cookie
添加 Cookie 到 HyperLogLog:
為了跟蹤一天的 UV集峦,可以為每天創(chuàng)建一個 HyperLogLog。當用戶訪問你的網(wǎng)站時抠刺,使用PFADD命令將他們的唯一標識添加到 HyperLogLog 中塔淤。
> PFADD uv_2023_09_01 user_cookie_12345
> PFADD uv_2023_09_01 user_cookie_12346
> PFADD uv_2023_09_01 user_cookie_12347
...
估算該日的UV:
使用 PFCOUNT 命令來獲取估算的UV值。
該命令會返回一個估算的獨立訪客數(shù)量速妖。請注意高蜂,這是一個估算值,但其精度在大多數(shù)情況下是足夠的罕容。
存儲歷史數(shù)據(jù):
如果你想跟蹤UV的歷史數(shù)據(jù)备恤,可以為每天保留一個 HyperLogLog稿饰。例如:uv_2023_09_01、uv_2023_09_02等露泊。
合并多日數(shù)據(jù):
如果你想要一個時間范圍內(nèi)的估算 UV(例如一個月)喉镰,你可以使用 PFMERGE 命令合并多個 HyperLogLog。
以上就是使用 Redis 的 HyperLogLog 進行網(wǎng)站UV統(tǒng)計的基本方法惭笑。使用這種方法侣姆,你可以用非常少的空間(每個 HyperLogLog 只需要12KB)來跟蹤大量的獨立訪客。
Geospatial
簡介:
Redis支持一個稱為 Geospatial 的地理空間索引功能沉噩。這一功能在許多需要地理定位數(shù)據(jù)的應(yīng)用中都有著廣泛應(yīng)用捺宗,如找到某個位置附近的餐廳或商店等。
基本概念:
“Geo” 來源于希臘語屁擅,意為“地球”偿凭,而 “spatial” 則表示“與空間有關(guān)”派歌。結(jié)合起來匾嘱,Geospatial 主要關(guān)注地球上的空間位置或地域囚聚。在 Redis 中,這意味著我們可以使用坐標系統(tǒng)(如經(jīng)緯度)來存儲和查詢地理位置的數(shù)據(jù)贾富。
簡單圖解:
說明:圖示的(x,y)代表的是 經(jīng)緯度坐標
內(nèi)部實現(xiàn):
地圖到數(shù)字:
想象一下琼了,我們的地球是一個巨大的地圖虱痕。如果我們要在這張地圖上標記一個位置概而,通常會使用經(jīng)緯度來描述它。但計算機更擅長處理數(shù)字而不是這樣的坐標。因此纵刘,Redis 使用了一種叫做 Geohash 的技巧千绪,它可以把這些坐標(比如經(jīng)緯度)轉(zhuǎn)換成一個數(shù)字池摧。
把位置存進列表:
Redis 有一種特殊的列表叫做 zset(有序集合)荔泳。在 Geospatial 中,位置的名字(比如"北京")作為元素尊流,而轉(zhuǎn)換得到的數(shù)字(Geohash)作為這個元素的“分數(shù)”帅戒。
查找附近的地方:
當我們想知道某個位置附近的其他地方時,Redis 會先找出這個位置的數(shù)字(Geohash)崖技,然后在 zset 中查找分數(shù)接近的其他元素逻住。這樣就可以快速地找出附近的位置。
注意:
使用 Geohash 的方法可能不是百分之百精確的响疚,因為它是一種近似的方法鄙信。但在實際應(yīng)用中,這種小小的誤差通常不會導(dǎo)致太大的問題忿晕,而它讓存儲和查找變得非常迅速装诡。
基本命令
添加操作:
向指定的鍵中添加地理空間位置(經(jīng)度、緯度践盼、名稱)
GEOADD key longitude latitude member [longitude latitude member ...] : 向指定的鍵中添加地理空間位置(經(jīng)度鸦采、緯度、名稱)
> GEOADD china:city 114.085947 22.547 shenzhen 113.280637 23.125178 guangzhou
(integer) 2
> GEOADD china:city 121.472644 31.231706 shanghai 116.405285 39.904989 beijing
(integer) 2
> GEOADD china:city 108.948024 34.263161 xian 106.504962 29.533155 chongqing
(integer) 2
獲取操作:
GEOPOS key member [member ...] : 獲取一個或多個位置元素的經(jīng)緯度
GEODIST key member1 member2 [m|km|ft|mi] : 獲取兩個地點之間的距離 # [m|km|ft|mi]: 分別是:米咕幻,千米渔伯,英尺,英里
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count] : 根據(jù)給定的經(jīng)緯度坐標肄程,返回位于給定距離內(nèi)的位置锣吼。
#參數(shù)詳解
WITHCOORD : 返回查詢結(jié)果中地理位置的經(jīng)緯度坐標
WITHDIST : 返回每個查詢結(jié)果地點到給定坐標的距離
WITHHASH : 返回位置的 52 位整數(shù)表示的 geohash
ASC|DESC : 決定了查詢結(jié)果的排序方式,按距離升序排列蓝厌、按距離降序排列
COUNT count : 限制查詢結(jié)果的數(shù)量玄叠。
> GEOADD china:city 108.948024 34.263161 xian 106.504962 29.533155 chongqing
(integer) 2
> GEOPOS china:city xian chongqing
1) 1) "108.94802302122116089"
2) "34.2631604414749944"
2) 1) "106.50495976209640503"
2) "29.53315530684997015"
> GEODIST china:city shenzhen xian km
"1396.1268"
> GEORADIUS china:city 118.767413 32.041544 500 km
1) "shanghai"
> GEORADIUS china:city 118.767413 32.041544 500 km WITHCOORD
1) 1) "shanghai"
2) 1) "121.47264629602432251"
2) "31.23170490709807012"
> GEORADIUS china:city 118.767413 32.041544 500 km WITHDIST
1) 1) "shanghai"
2) "271.5419"
我們來看一個混合使用參數(shù)的例子:
查詢距離某個位置 1000 公里內(nèi)的所有地方,并返回其名稱拓提、距離读恃、坐標,然后按距離升序排序,只顯示前 3 個結(jié)果寺惫。
> GEORADIUS china:city 118.767413 32.041544 1000 km WITHDIST WITHCOORD ASC COUNT 3
1) 1) "shanghai"
2) "271.5419"
3) 1) "121.47264629602432251"
2) "31.23170490709807012"
2) 1) "beijing"
2) "899.9931"
3) 1) "116.40528291463851929"
2) "39.9049884229125027"
3) 1) "xian"
2) "946.7395"
3) 1) "108.94802302122116089"
2) "34.2631604414749944"
應(yīng)用案例
位置數(shù)據(jù)存儲與查詢:
- 社交應(yīng)用:用戶可以查找附近的朋友或興趣點疹吃。例如,一個社交網(wǎng)絡(luò)應(yīng)用可以允許用戶查看附近的其他用戶或活動西雀。
- 出行與導(dǎo)航:用于存儲和查詢地理位置數(shù)據(jù)萨驶,如共享單車或共享汽車的當前位置,以及用戶附近的可用車輛艇肴。
Stream
基本概念
Redis Stream 是 Redis 5.0 中引入的新數(shù)據(jù)類型篡撵,設(shè)計用來存儲和查詢?nèi)罩緮?shù)據(jù)結(jié)構(gòu)。Stream 是 Redis 對“日志”數(shù)據(jù)結(jié)構(gòu)的實現(xiàn)豆挽,這種結(jié)構(gòu)在各種場景中都很有用,如消息隊列和事件日志券盅。
與簡單的 List 不同帮哈,Stream 能夠更好地支持多用戶并發(fā)操作,同時還提供了復(fù)雜的消息確認和消費機制锰镀。
Stream 基本組件介紹
Stream:一個Stream是一個按時間順序排列的消息列表娘侍。每個消息都有一個唯一的ID和鍵值對組成的數(shù)據(jù)。
Consumer:這是一個從 Stream 讀取消息的客戶端泳炉。每個 Consumer 都有一個唯一的名字憾筏。
Consumer Group:一個 Consumer Group 包含一組 Consumer,它們共同讀取一個 Stream花鹅。這樣做是為了并行處理消息氧腰。
簡單圖解
內(nèi)部實現(xiàn):
ListPack : Redis 中的 Stream 的底層用的是一種名為 ListPack 的數(shù)據(jù)結(jié)構(gòu),它非常緊湊并且效率高刨肃。
唯一ID : 每個消息都有一個由時間戳和序列號組成的唯一ID古拴,確保消息的全局唯一性。
基本命令:
基礎(chǔ)操作
添加操作:
XADD key *|ID field value [field value ...] :
# *|ID: 消息的 ID真友。使用 * 會自動生成一個ID黄痪,或者你可以指定一個
> XADD mystream * name xiaokang age 25
"1695020949856-0"
> XADD mystream * hobby swim job programmer
"1695029970290-0"
查詢操作:
XRANGE key start end [COUNT count] : 查詢指定ID范圍內(nèi)的消息
XREVRANGE key end start [COUNT count] : 反向查詢指定ID范圍內(nèi)的消息
# 參數(shù)說明:
key : 表示你要檢索的 Stream 的名字
[COUNT count] : 可選參數(shù),用于限制返回的消息數(shù)量
對于 XRANGE 命令盔然,參數(shù) start 和 end 的含義:
start : 檢索的起始消息 ID
end : 檢索的結(jié)束消息 ID
對于 XRANGE 命令桅打,參數(shù) start 和 end 的含義:
start : 反向檢索的起始消息 ID
end : 反向檢索的結(jié)束消息 ID
特殊符號解釋:
參數(shù) start 取 '-' , 表示最早的消息愈案,取 + 表示最新的消息
# 檢索所有消息
> XRANGE mystream - +
1) 1) "1695020949856-0"
2) 1) "name"
2) "xiaokang"
3) "age"
4) "25"
2) 1) "1695029970290-0"
2) 1) "hobby"
2) "swim"
3) "job"
4) "programmer"
# 反向檢索所有消息
> XREVRANGE mystream + -
1) 1) "1695029970290-0"
2) 1) "hobby"
2) "swim"
3) "job"
4) "programmer"
2) 1) "1695020949856-0"
2) 1) "name"
2) "xiaokang"
3) "age"
4) "25"
獲取長度:
XLEN key :獲取Stream中的消息數(shù)量
> XLEN mystream
(integer) 2
裁剪操作:
XTRIM key MAXLEN|MINID [=|~] threshold [LIMIT count] :裁減Stream 的長度挺尾,控制其大小
# 參數(shù)說明
MAXLEN|MINID : 裁減策略選擇。
MAXLEN :使 Stream 最多保持指定數(shù)量的消息刻帚。
MINID :刪除所有小于指定ID的消息潦嘶。
[=|~]: 這是一個可選的修飾符,與上面的 MAXLEN 或 MINID 一起使用。
=:這意味著長度或ID應(yīng)該精確匹配掂僵。對于 MAXLEN航厚,它確保Stream的長度恰好等于指定的長度(刪除任何額外的消息);對于 MINID锰蓬,它確保刪除的所有消息的ID值都小于等于給定值幔睬。
~:這意味著長度或ID是一個近似值。這可能會導(dǎo)致更快的裁減操作芹扭,但Stream的實際長度可會略微超過或低于指定的值麻顶。
[LIMIT count] : 這是一個可選的參數(shù),它限制了在一個操作中可以刪除的消息數(shù)
> XTRIM mystream MAXLEN = 1
(integer) 1
> XRANGE mystream - +
1) 1) "1695029970290-0"
2) 1) "hobby"
2) "swim"
3) "job"
4) "programmer"
刪除操作:
XDEL key ID [ID ...] : 從Stream中刪除指定的消息
> XDEL mystream "1695029970290-0"
(integer) 1
> XRANGE mystream - +
讀取消息操作:
XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...] : 用于從一個或多個流中讀取消息
參數(shù)說明:
COUNT count :可選參數(shù)舱卡,指定要從每個流中讀取的最大消息數(shù)辅肾。
BLOCK milliseconds :可選參數(shù),阻塞操作的時間(以毫秒為單位)轮锥。該命令會等待指定的時間矫钓,直到有新的消息可用。 在給定的時間內(nèi)沒有新消息舍杜,則命令會返回一個空響應(yīng)
STREAMS :指示后面要列出要從中讀取的流的名稱新娜。這是一個固定的關(guān)鍵字。
key [key ...] :要從中讀取的流的名稱列表
ID [ID ...] :為每個指定的流提供一個消息 ID既绩,從該 ID 之后(不包括該 ID)開始讀取消息概龄。
特殊 ID $ :表示只讀取新的消息,也就是那些在發(fā)出此 XREAD 命令之后添加到流中的消息饲握。
消費者和消費者組操作
XGROUP:用于創(chuàng)建私杜、修改或刪除消費者組。
# 參數(shù)說明:
CREATE : 創(chuàng)建一個新的消費者組互拾。
XGROUP CREATE key groupname ID|$ [MKSTREAM]
key : Stream 的名稱歪今。
groupname : 消費者組的名稱。
ID : 從哪個消息 ID 開始消費颜矿。如果選擇 $寄猩,則只會消費新添加到 Stream 的消息。
MKSTREAM : 可選參數(shù)骑疆。如果 Stream 不存在田篇,它會創(chuàng)建一個新的空 Stream。
SETID : 設(shè)置消費者組的開始消費消息的 ID箍铭。
XGROUP SETID key groupname ID|$
key : Stream 的名稱泊柬。
groupname : 消費者組的名稱。
ID : 從哪個消息 ID 開始消費诈火。如果選擇 $兽赁,則只會消費新添加到 Stream 的消息。
DESTROY : 刪除一個消費者組。
XGROUP DESTROY key groupname
key : Stream 的名稱刀崖。
groupname : 消費者組的名稱惊科。
CREATECONSUMER : 在給定的消費者組中顯式地創(chuàng)建一個消費者。
XGROUP CREATECONSUMER key groupname consumername
key : Stream 的名稱亮钦。
groupname : 消費者組的名稱馆截。
consumername : 新創(chuàng)建的消費者的名稱。
XREADGROUP:使用消費者組從Stream中讀取消息蜂莉。
XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ...]
# 參數(shù)說明:
group : 指的是你想讀取消息的消費者組的名稱蜡娶。
consumer : 是該消費者組內(nèi)的消費者名稱。每次使用 XREADGROUP映穗,都需要指定消費者名稱窖张。
COUNT count : 可選參數(shù),指定從每個流中讀取的最大消息數(shù)量蚁滋。
NOACK : 消息在被讀取時不會被標記為“未確認”荤堪。因此,不需要(也不能)對它們進行確認枢赔。默認情況下,當消費者讀取消息后拥知,是需要對消息進行確認的踏拜。
ID [ID ...] : 為每個指定的流提供一個消息 ID,從該 ID 之后(不包括該 ID)開始讀取消息低剔。
特殊 ID > : 表示從上次讀取的位置繼續(xù)讀人俟!(只在使用 XREADGROUP 時有效)。
XACK : 用來確認消費者已成功處理的特定消息
XACK key group ID [ID ...]
# 參數(shù)解釋
key : 這是你要確認消息的 Stream 的名字襟齿。
group : 這是消息所屬的消費者組的名稱姻锁。
ID [ID ...] : 一個或多個你想確認的消息的 ID。
XPENDING :用于查詢消費者組中待處理(已發(fā)送但未確認)的消息
XPENDING key group [[IDLE min-idle-time] start end count [consumer]]
key : 你要查詢的 Stream 的名稱猜欺。
group: 你要查詢的消費者組的名稱位隶。
IDLE min-idle-time : 這是一個可選的參數(shù),它允許你只查詢那些已經(jīng)空閑或未確認超過指定毫秒數(shù)的消息开皿。
start : 起始消息 ID涧黄。
end : 結(jié)束消息 ID。
count : 你要返回的消息的最大數(shù)量赋荆。
consumer : 這是一個可選的參數(shù)笋妥,它允許你只查詢特定消費者的待處理消息。
XCLAIM : 允許一個消費者重新認領(lǐng)消費者組中的掛起消息窄潭,通常用于處理由失效消費者未完成的消息春宣。
XCLAIM key group consumer min-idle-time ID [ID ...] [IDLE ms] [TIME ms-unix-time] [RETRYCOUNT count] [force] [justid]
key : 指定的 Stream 名稱。
group : 你想要操作的消費者組名稱。
consumer : 這是嘗試認領(lǐng)消息的消費者的名字月帝。
min-idle-time : 以毫秒為單位的時間躏惋,僅當消息的閑置時間超過此值時,消費者才能認領(lǐng)該消息嫁赏。
ID [ID ...] : 你想要認領(lǐng)的消息的 ID 列表其掂。
IDLE ms : 設(shè)置消息的新的閑置時間(自從最后一次被消費以來的時間)。
TIME ms-unix-time : 修改消息的最后一次被讀取的時間為給定的 Unix 時間
RETRYCOUNT count : 設(shè)置消息的投遞計數(shù)(即這條消息已經(jīng)被送達的次數(shù))潦蝇。
force : 這個選項允許你不考慮 min-idle-time 條件款熬,直接強制認領(lǐng)消息。
justid : 如果設(shè)置這個選項攘乒,命令只返回消息 ID贤牛,不返回消息的內(nèi)容。
接下來则酝,我通過一個示例來演示如何使用Redis Stream中的命令進行消息的添加殉簸、消費和確認。
場景:
以一個在線訂單系統(tǒng)為例沽讹,當一個用戶下單時般卑,訂單詳細信息被添加到一個名為“orders”的stream中。有一個消費者組叫做“order-processors”爽雄,里面有兩個消費者:“processor1”和“processor2”蝠检。它們的任務(wù)是處理這些訂單,例如更新庫存挚瘟、發(fā)送確認郵件等叹谁。
1. 添加訂單
XADD orders * order_id 123 item_id A1 count 2 # 用戶A下了一個訂單,訂單編號為123,買了 2件
"1695103666038-0"
2.創(chuàng)建消費者組
XGROUP CREATE orders order-processors $ MKSTREAM # 創(chuàng)建一個名為“order-processors”的消費者組乘盖,從 stream 的開始處監(jiān)聽新的訂單焰檩。
OK
3.讀取和處理訂單
# processor1讀取了訂單123并開始處理它
XREADGROUP GROUP order-processors processor1 COUNT 1 STREAMS orders >
# 同時,另一個用戶B下了一個訂單订框,編號為124
XADD orders * order_id 124 item_id A2 quantity 1
# 然后析苫,processor2 開始讀取訂單:
XREADGROUP GROUP order-processors processor2 COUNT 1 STREAMS orders >
4.確認訂單已處理
# 當processor1成功處理訂單123時,它會確認處理完成:
XACK orders order-processors messageID1
# 注意:messageID1是processor1從Stream讀取到的訂單123的ID穿扳。
5.查看未處理的訂單
XPENDING orders order-processors
# 假設(shè)processor2由于某種原因暫時不能處理訂單124藤违,這時我們會看到訂單124尚未被處理。
6.重新處理失敗的訂單
# 如果processor2出現(xiàn)故障纵揍,processor1可以認領(lǐng)并處理訂單124:
XCLAIM orders order-processors processor1 3600000 messageID2
這個簡單的在線訂購系統(tǒng)例子展示了如何使用Redis Streams進行實時訂單處理顿乒。這種模式可以確保即使某個消費者失敗,訂單也能被其他消費者接手并順利處理泽谨。
應(yīng)用案例:
消息隊列
消息隊列是一種應(yīng)用程序之間傳遞數(shù)據(jù)的方式璧榄。它允許應(yīng)用程序異步地發(fā)送和接收消息特漩,這意味著發(fā)送消息的應(yīng)用程序和接收消息的應(yīng)用程序無需同時運行。
以在線購物系統(tǒng)的訂單處理為例進行說明:
考慮一個在線購物系統(tǒng)骨杂。當用戶下單時涂身,系統(tǒng)不應(yīng)該讓用戶等待直到所有的后端處理(例如庫存檢查、付款處理搓蚪、通知倉庫等)都完成蛤售。相反,一旦訂單提交妒潭,系統(tǒng)應(yīng)該立即給用戶一個響應(yīng)悴能,而后端的處理可以稍后進行。
#1. 添加訂單到隊列:
#用戶下單后雳灾,我們將訂單數(shù)據(jù)添加到名為orders的Redis Stream中漠酿。
XADD orders * order_id 101 product_id P01 quantity 3
#2.處理訂單:
# 后臺有多個workers(進程),它們不斷地監(jiān)聽新的訂單谎亩,并進行處理炒嘲。這些worke 可以是分布在多臺機器上的多個進程。
首先匈庭,我們創(chuàng)建一個消費者組:
XGROUP CREATE orders order-processors $ MKSTREAM
接著夫凸,一個worker可以開始讀取并處理訂單:
XREADGROUP GROUP order-processors worker1 COUNT 1 STREAMS orders >
# 這里,worker1是處理訂單的消費者名稱阱持。>意味著從最新的消息開始讀取寸痢。
確認訂單處理完成:
一旦worker1處理完訂單,它需要確認該訂單已被處理:
XACK orders order-processors <messageID> # 其中紊选,<messageID>是在步驟1中Redis生成的訂單ID。
通過此例子道逗,我們展示了如何使用 Redis Stream 作為消息隊列兵罢,來異步處理在線購物系統(tǒng)中的訂單。這種結(jié)構(gòu)確保了用戶下單后能夠迅速得到響應(yīng)滓窍,同時訂單處理也能在后臺高效地進行卖词。
事件日志
事件日志就是記錄系統(tǒng)或應(yīng)用中發(fā)生的各種事件,如用戶操作吏夯、系統(tǒng)異常等此蜈。
事件日志的常見應(yīng)用是在電商平臺中記錄用戶的購物行為。
假設(shè)你運營一個電商平臺噪生,每當用戶瀏覽裆赵、搜索、點擊跺嗽、購買或者評論商品時战授,都會產(chǎn)生一個事件页藻。你可以使用Redis Streams來記錄這些事件。基于這些事件植兰,可以做一些實時分析份帐,比如:分析用戶的購物模式和偏好卤妒,為他們提供更相關(guān)的商品推薦碳胳。
1.瀏覽商品:
事件:商品瀏覽
數(shù)據(jù):用戶ID,商品ID贰盗,瀏覽時間筒繁,來源頁面等噩凹。
XADD user-activity * event "商品瀏覽" userID "12345" productID "abcd" timestamp "1632067200" source "主頁"
2.搜索商品:
事件:商品搜索
數(shù)據(jù):用戶ID,搜索關(guān)鍵詞膝晾,搜索時間栓始,搜索結(jié)果數(shù)等。
XADD user-activity * event "商品搜索" userID "12345" keyword "運動鞋" timestamp "1632067250" results "50"
3.點擊商品:
事件:商品點擊
數(shù)據(jù):用戶ID血当,商品ID幻赚,點擊時間。
XADD user-activity * event "商品點擊" userID "12345" productID "abcd" timestamp "1632067300"
4.購買商品:
事件:商品購買
數(shù)據(jù):用戶ID臊旭,商品ID落恼,購買數(shù)量,總價离熏,購買時間佳谦。
XADD user-activity * event "商品購買" userID "12345" productID "abcd" quantity "2" total "200" timestamp "1632067400"
5.商品評價:
事件:商品評價
數(shù)據(jù):用戶ID,商品ID滋戳,評分钻蔑,評論內(nèi)容,評價時間奸鸯。
XADD user-activity * event "商品評價" userID "12345" productID "abcd" rating "5" review "非常滿意" timestamp "1632067500"
總結(jié):
在上文咪笑,我們深入探討了 Redis 的九種對象類型,包括字符串(String)娄涩、列表(List)窗怒、哈希(Hash)、集合(Set)蓄拣、有序集合(Sorted Set)扬虚、Bitmaps、HyperLogLog球恤、Geospatial 和 Stream辜昵。
這里簡單總結(jié)下各種數(shù)據(jù)結(jié)構(gòu)的使用場景:
- 字符串對象是最簡單的數(shù)據(jù)類型,適用于緩存咽斧、臨時存儲等場景路鹰。
- 列表對象提供了隊列的實現(xiàn)贷洲,非常適合消息隊列和棧的應(yīng)用。
- 哈希對象是存儲對象屬性的理想選擇晋柱,適用于存儲和訪問對象优构。
- 集合對象和有序集合對象適用于存儲不重復(fù)元素,其中有序集合還可以進行排名和范圍查詢雁竞。
- Bitmaps和 HyperLogLog 提供了高效的計數(shù)和統(tǒng)計功能钦椭。
- Geospatial 允許進行地理位置的存儲和查詢。
- Stream 為構(gòu)建復(fù)雜的消息傳遞提供了基礎(chǔ)碑诉。
Redis 作為一個高性能的鍵值數(shù)據(jù)庫彪腔,已經(jīng)成為現(xiàn)代應(yīng)用開發(fā)不可或缺的組成部分。通過深入了解 Redis 的各種對象及其編碼方式进栽,我們不僅可以更加高效地利用其提供的功能德挣,還能針對不同的應(yīng)用場景選擇最適合的對象類型,從而優(yōu)化我們的應(yīng)用性能和資源使用快毛。
本篇文章旨在為大家提供一個關(guān)于 Redis 各個對象的全面指南格嗅,從基本概念到內(nèi)部實現(xiàn),再到實際應(yīng)用案例唠帝。不管您是剛開始接觸 Redis 還是已經(jīng)有很多經(jīng)驗屯掖,希望本文都能為您帶來新的啟示。
最后
如果你對 Linux 編程襟衰,Redis 等后端技術(shù)感興趣或者想學(xué)習(xí)計算機基礎(chǔ)相關(guān)的知識贴铜,不妨關(guān)注我的公眾號「跟著小康學(xué)編程」。這里不僅有豐富的學(xué)習(xí)資源瀑晒,還有持續(xù)更新的簡單易懂的技術(shù)文章绍坝。
大家可以關(guān)注我 ,具體可訪問 :關(guān)注小康微信公眾號
另外大家在閱讀這篇文章的時候苔悦,如果覺得有問題的或者有不理解的知識點轩褐,歡迎大家評論區(qū)詢問。我看到就會回復(fù)大家的间坐。大家也可以加我的微信:jkfwdkf , 備注「加群」。有任何不理解的都可以咨詢我邑退。
大家如果覺得我寫的還不錯竹宋,也希望大家能夠幫忙點個贊,感謝大家的關(guān)注地技!
參考資料
[1]Redis分布式鎖到底安全嗎蜈七?: http://kaito-kidd.com/2021/06/08/is-redis-distributed-lock-really-safe/