深入剖析Redis系列(八) - Redis資料結構之集合

零壹技術棧發表於2018-11-08

前言

集合set)型別也是用來儲存多個 字串元素,但和 列表型別 不一樣的是,集合中 不允許有重複元素,並且集合中的元素是 無序的,不能通過 索引下標 獲取元素。

深入剖析Redis系列(八) - Redis資料結構之集合

深入剖析Redis系列(八) - Redis資料結構之集合

如圖所示,集合 user:1:follow 包含著 "it""music""his""sports" 四個元素,一個 集合 最多可以儲存 2 ^ 32 - 1 個元素。Redis 除了支援 集合內增刪改查,同時還支援 多個集合交集並集差集。合理地使用好集合型別,能在實際開發中解決很多實際問題。

深入剖析Redis系列(八) - Redis資料結構之集合

其他文章

正文

1. 相關命令

1.1. 集合內的操作命令

1.1.1. 新增元素

sadd key element [element ...]

返回結果為新增成功的 元素個數,例如:

127.0.0.1:6379> exists myset
(integer) 0
127.0.0.1:6379> sadd myset a b c
(integer) 3
127.0.0.1:6379> sadd myset a b
(integer) 0
複製程式碼

1.1.2. 刪除元素

srem key element [element ...]

返回結果為成功刪除 元素個數,例如:

127.0.0.1:6379> srem myset a b
(integer) 2
127.0.0.1:6379> srem myset hello
(integer) 0
複製程式碼

1.1.3. 計算元素個數

scard key

scard時間複雜度O(1),它 不會遍歷 集合所有元素,而是直接用 Redis內部 的變數,例如:

127.0.0.1:6379> scard myset
(integer) 1
複製程式碼

1.1.4. 判斷元素是否在集合中

sismember key element

如果給定元素 element 在集合內返回 1,反之返回 0

127.0.0.1:6379> sismember myset c
(integer) 1
複製程式碼

1.1.5. 隨機從集合返回指定個數元素

srandmember key [count]

[count]可選引數,如果不寫預設為 1

127.0.0.1:6379> srandmember myset 2
1) "a"
2) "c"
127.0.0.1:6379> srandmember myset
"d"
複製程式碼

1.1.6. 從集合隨機彈出元素

spop key

spop 操作可以從 集合隨機彈出 一個元素,例如下面程式碼是一次 spop 後,集合元素 變為 "d b a"

127.0.0.1:6379> spop myset
"c"
127.0.0.1:6379> smembers myset
1) "d"
2) "b"
3) "a"
複製程式碼

注意Redis3.2 版本開始, spop 也支援 [count] 引數。

srandmemberspop 都是 隨機 從集合選出元素,兩者不同的是 spop 命令執行後,元素 會從集合中 刪除,而 srandmember 不會刪除元素。

1.1.7. 獲取所有元素

smembers key

下面程式碼獲取集合 myset所有元素,並且 返回結果無序的

127.0.0.1:6379> smembers myset
1) "d"
2) "b"
3) "a"
複製程式碼

smemberslrangehgetall 都屬於 比較重 的命令,如果 元素過多 存在 阻塞 Redis 的可能性,這時候可以使用 sscan 命令來完成。

1.2. 集合間的操作命令

現在有 兩個集合,它們分別是 user:1:followuser:2:follow

127.0.0.1:6379> sadd user:1:follow it music his sports
(integer) 4
127.0.0.1:6379> sadd user:2:follow it news ent sports
(integer) 4
複製程式碼

1.2.1. 求多個集合的交集

sinter key [key ...]

下面的程式碼是求 user:1:followuser:2:follow 兩個集合的 交集,返回結果是 sportsit

127.0.0.1:6379> sinter user:1:follow user:2:follow
1) "sports"
2) "it"
複製程式碼

1.2.2. 求多個集合的並集

suinon key [key ...]

下面的程式碼是求 user:1:followuser:2:follow 兩個集合的 並集,返回結果是 sportsithisnewsmusicent

127.0.0.1:6379> sunion user:1:follow user:2:follow
1) "sports"
2) "it"
3) "his"
4) "news"
5) "music"
6) "ent"
複製程式碼

1.2.3. 求多個集合的差集

sdiff key [key ...]

下面的程式碼是求 user:1:followuser:2:follow 兩個集合的 差集,返回結果是 musichis

127.0.0.1:6379> sdiff user:1:follow user:2:follow
1) "music"
2) "his"
複製程式碼

前面三個求 交集並集差集 的操作得到的結果,如圖所示:

深入剖析Redis系列(八) - Redis資料結構之集合

1.2.4. 將交集、並集、差集的結果儲存

集合間 的運算在 元素較多 的情況下會 比較耗時,所以 Redis 提供了以下 三個命令原命令 + store)將 集合間交集並集差集 的結果儲存在 destination key 中。

sinterstore destination key [key ...] suionstore destination key [key ...] sdiffstore destination key [key ...]

下面的操作會將 user:1:followuser:2:follow 兩個集合交集結果 儲存在 user:1_2:inter 中,user:1_2:inter 本身也是 集合型別

127.0.0.1:6379> sinterstore user:1_2:inter user:1:follow user:2:follow
(integer) 2
127.0.0.1:6379> type user:1_2:inter
set
127.0.0.1:6379> smembers user:1_2:inter
1) "it"
2) "sports"
複製程式碼

有關 集合常用命令 基本上就是這麼多了,下表給出了 集合常用命令時間複雜度,開發人員可以根據自身需求進行選擇。

深入剖析Redis系列(八) - Redis資料結構之集合

2. 內部編碼

集合型別內部編碼 有兩種:

2.1. intset(整數集合)

當集合中的元素都是 整數元素個數 小於 set-max-intset-entries 配置(預設 512 個)時,Redis 會選用 intset 來作為 集合內部實現,從而 減少記憶體 的使用。

2.2. hashtable(雜湊表)

當集合型別 無法滿足 intset 的條件時,Redis 會使用 hashtable 作為集合的 內部實現

2.3. 具體示例

2.3.1. 內部編碼為整數集合

當元素個數 較少 且都為 整數 時,內部編碼intset

127.0.0.1:6379> sadd setkey 1 2 3 4
(integer) 4
127.0.0.1:6379> object encoding setkey
"intset"
複製程式碼

2.3.2. 內部編碼為雜湊

  • 元素個數 超過 512 個,內部編碼 變為 hashtable
127.0.0.1:6379> sadd setkey 1 2 3 4 5 6 ... 512 513
(integer) 513
127.0.0.1:6379> scard setkey
(integer) 513
127.0.0.1:6379> object encoding listkey
"hashtable"
複製程式碼
  • 當某個元素 不為整數 時,內部編碼 也會變為 hashtable
127.0.0.1:6379> sadd setkey a
(integer) 1
127.0.0.1:6379> object encoding setkey
"hashtable"
複製程式碼

有關集合型別的 記憶體優化 技巧,後面的文章會有專門的講解。

3. 應用場景

3.1. 使用者標籤

集合型別 比較典型的使用場景是 標籤tag)。簡單的舉兩個例子:

3.1.3. 娛樂新聞推薦

一個使用者可能對娛樂、體育比較感興趣,另一個使用者可能對歷史、新聞比較感興趣,這些興趣點就是標籤。有了這些資料就可以得到喜歡同一個標籤的人,以及使用者的共同喜好的標籤,這些資料對於使用者體驗以及增強使用者黏度比較重要。

3.1.2. 電商人群分類

一個電子商務的網站會對不同標籤的使用者做不同型別的推薦,比如對數碼產品比較感興趣的人,在各個頁面或者通過郵件的形式給他們推薦最新的數碼產品,通常會為網站帶來更多的利益。

下面使用 集合型別 實現 標籤功能

  • 給使用者新增標籤
sadd user:1:tags tag1 tag2 tag5
sadd user:2:tags tag2 tag3 tag5
...
sadd user:k:tags tag1 tag2 tag4
...
複製程式碼
  • 給標籤新增使用者
sadd tag1:users user:1 user:3
sadd tag2:users user:1 user:2 user:3
...
sadd tagk:users user:1 user:2
...
複製程式碼

使用者標籤關係維護 應該在 一個事務內 執行,防止 部分命令失敗 造成的 資料不一致,有關如何將 兩個命令 放在 一個事務 中,後面會介紹 Lua 的使用。

  • 刪除使用者下的標籤
srem user:1:tags tag1 tag5
...
複製程式碼
  • 刪除標籤下的使用者
srem tag1:users user:1
srem tag5:users user:1
...
複製程式碼
  • 計算使用者共同感興趣的標籤

可以使用 sinter 命令,來計算 使用者共同感興趣標籤

sinter user:1:tags user:2:tags
複製程式碼

上面只是給出了使用 Redis 集合型別 實現 標籤 的基本思路,實際上一個 標籤系統 遠比這個要 複雜 得多,不過 集合型別應用場景 通常為以下幾種:

命令組合 應用場景
sadd Tagging(標籤)
spop/srandmember Random item(生成隨機數,比如抽獎)
sadd + sinter Social Graph(社交需求)

小結

本文介紹了 Redis 中的 集合 的 一些 基本命令,包括 集合內部的操作命令集合之間的操作命令,其次還介紹了 集合內部編碼 轉換和以 使用者行為標籤 為主的 應用場景

參考

《Redis 開發與運維》


歡迎關注技術公眾號: 零壹技術棧

零壹技術棧

本帳號將持續分享後端技術乾貨,包括虛擬機器基礎,多執行緒程式設計,高效能框架,非同步、快取和訊息中介軟體,分散式和微服務,架構學習和進階等學習資料和文章。

相關文章