Redis 常見數(shù)據(jù)類型(對象類型)和應(yīng)用案列

前言:

每次你在游戲中看到玩家排行榜啡省,或者在音樂應(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對象.png

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 圖片剥扣、序列化的對象或者純文本。

簡單圖解:

string-字符串對象

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)分布式鎖一般步驟:

  1. 加鎖:SET lock_key unique_id EX expire_time NX
  • lock_key : 分布式鎖名
  • unique_id : 唯一標識符
  • EX : 設(shè)置過期時間
  • NX : 當 lock_key 不存在時命令才會成功
  1. 操作共享資源
  2. 釋放鎖:通過 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 億個元素矫户。

簡單圖解

List-列表對象

內(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é)省空間瓷蛙,特別是當我們要存儲大量小對象時。

簡單圖解:

Redis-哈希對象

簡單說明:

假設(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ò)中的好友列表或者任何需要快速判斷某個元素是否存在的場景皇筛。

簡單圖解

set - 對象

內(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ù)隊列等。

簡單圖解:

SortSet - 有序集合對象

內(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)的值。

簡單圖解:

Bitmaps 對象

內(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ù)類型剖膳。

簡單圖解:

HyperLogLog

步驟說明:

  • 輸入值通過哈希函數(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ù)贾富。

簡單圖解:

Geospatial 對象

說明:圖示的(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花鹅。這樣做是為了并行處理消息氧腰。

簡單圖解

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/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市莫矗,隨后出現(xiàn)的幾起案子飒硅,更是在濱河造成了極大的恐慌砂缩,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件三娩,死亡現(xiàn)場離奇詭異庵芭,居然都是意外死亡,警方通過查閱死者的電腦和手機雀监,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門双吆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人会前,你說我怎么就攤上這事好乐。” “怎么了瓦宜?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵蔚万,是天一觀的道長。 經(jīng)常有香客問我临庇,道長反璃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任苔巨,我火速辦了婚禮版扩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘侄泽。我一直安慰自己礁芦,他們只是感情好,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布悼尾。 她就那樣靜靜地躺著柿扣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪闺魏。 梳的紋絲不亂的頭發(fā)上未状,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天,我揣著相機與錄音析桥,去河邊找鬼司草。 笑死,一個胖子當著我的面吹牛泡仗,可吹牛的內(nèi)容都是我干的埋虹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼娩怎,長吁一口氣:“原來是場噩夢啊……” “哼搔课!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起截亦,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤爬泥,失蹤者是張志新(化名)和其女友劉穎柬讨,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體袍啡,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡踩官,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了葬馋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卖鲤。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖畴嘶,靈堂內(nèi)的尸體忽然破棺而出蛋逾,到底是詐尸還是另有隱情,我是刑警寧澤窗悯,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布区匣,位于F島的核電站,受9級特大地震影響蒋院,放射性物質(zhì)發(fā)生泄漏亏钩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一欺旧、第九天 我趴在偏房一處隱蔽的房頂上張望姑丑。 院中可真熱鬧,春花似錦辞友、人聲如沸栅哀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽留拾。三九已至,卻和暖如春鲫尊,著一層夾襖步出監(jiān)牢的瞬間痴柔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工疫向, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留咳蔚,地道東北人。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓搔驼,卻偏偏與公主長得像谈火,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子匙奴,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內(nèi)容