前言
集合(set
)型別也是用來儲存多個 字串元素,但和 列表型別 不一樣的是,集合中 不允許有重複元素,並且集合中的元素是 無序的,不能通過 索引下標 獲取元素。
如圖所示,集合 user:1:follow
包含著 "it"
、"music"
、"his"
、"sports"
四個元素,一個 集合 最多可以儲存 2 ^ 32 - 1
個元素。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"
複製程式碼
注意:
Redis
從3.2
版本開始,spop
也支援[count]
引數。
srandmember
和 spop
都是 隨機 從集合選出元素,兩者不同的是 spop
命令執行後,元素 會從集合中 刪除,而 srandmember
不會刪除元素。
1.1.7. 獲取所有元素
smembers key
下面程式碼獲取集合 myset
的 所有元素,並且 返回結果 是 無序的。
127.0.0.1:6379> smembers myset
1) "d"
2) "b"
3) "a"
複製程式碼
smembers
和 lrange
、hgetall
都屬於 比較重 的命令,如果 元素過多 存在 阻塞 Redis
的可能性,這時候可以使用 sscan
命令來完成。
1.2. 集合間的操作命令
現在有 兩個集合,它們分別是 user:1:follow
和 user: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:follow
和 user:2:follow
兩個集合的 交集,返回結果是 sports
、it
。
127.0.0.1:6379> sinter user:1:follow user:2:follow
1) "sports"
2) "it"
複製程式碼
1.2.2. 求多個集合的並集
suinon key [key ...]
下面的程式碼是求 user:1:follow
和 user:2:follow
兩個集合的 並集,返回結果是 sports
、it
、his
、news
、music
、ent
。
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:follow
和 user:2:follow
兩個集合的 差集,返回結果是 music
和 his
。
127.0.0.1:6379> sdiff user:1:follow user:2:follow
1) "music"
2) "his"
複製程式碼
前面三個求 交集、並集 和 差集 的操作得到的結果,如圖所示:
1.2.4. 將交集、並集、差集的結果儲存
集合間 的運算在 元素較多 的情況下會 比較耗時,所以 Redis
提供了以下 三個命令(原命令 + store
)將 集合間交集、並集、差集 的結果儲存在 destination key
中。
sinterstore destination key [key ...] suionstore destination key [key ...] sdiffstore destination key [key ...]
下面的操作會將 user:1:follow
和 user: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"
複製程式碼
有關 集合 的 常用命令 基本上就是這麼多了,下表給出了 集合常用命令 的 時間複雜度,開發人員可以根據自身需求進行選擇。
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 開發與運維》
歡迎關注技術公眾號: 零壹技術棧
本帳號將持續分享後端技術乾貨,包括虛擬機器基礎,多執行緒程式設計,高效能框架,非同步、快取和訊息中介軟體,分散式和微服務,架構學習和進階等學習資料和文章。