<b>2.5 集合</b>
集合(set)類型也是用來儲存多個的字元串元素,但和清單類型不一樣的是,集合中不允許有重複元素,并且集合中的元素是無序的,不能通過索引下标擷取元素。如圖2-22所示,集合user:1:follow包含着"it"、"music"、
"his"、"sports"四個元素,一個集合最多可以存儲232-1個元素。redis除了支援集合内的增删改查,同時還支援多個集合取交集、并集、差集,合理地使用好集合類型,能在實際開發中解決很多實際問題。
<b>2.5.1 指令</b>
下面将按照集合内和集合間兩個次元對集合的常用指令進行介紹。
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
(2)删除元素
srem key element [element ...]
傳回結果為成功删除元素個數,例如:
127.0.0.1:6379> srem myset a b
(integer) 2
127.0.0.1:6379> srem myset hello
(3)計算元素個數
scard key
scard的時間複雜度為o(1),它不會周遊集合所有元素,而是直接用redis内部的變量,例如:
127.0.0.1:6379> scard myset
(integer) 1
(4)判斷元素是否在集合中
sismember key element
如果給定元素element在集合内傳回1,反之傳回0,例如:
127.0.0.1:6379> sismember myset c
(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"
(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不會。
(7)擷取所有元素
smembers key
下面代碼擷取集合myset所有元素,并且傳回結果是無序的:
smembers和lrange、hgetall都屬于比較重的指令,如果元素過多存在阻塞redis的可能性,這時候可以使用sscan來完成,有關sscan指令2.7節會介紹。
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
(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"
(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
3) "his"
4) "news"
5) "music"
6) "ent"
(3)求多個集合的差集
sdiff key [key ...]
例如下面代碼是求user:1:follow和user:2:follow兩個集合的差集,傳回結果是music和his:
127.0.0.1:6379> sdiff user:1:follow
1) "music"
2) "his"
前面三個指令如圖2-23所示。
圖2-23 集合求交集、并集、差集
(4)将交集、并集、差集的結果儲存
sinterstore destination key [key ...]
suionstore
destination key [key ...]
sdiffstore
集合間的運算在元素較多的情況下會比較耗時,是以redis提供了上面三個指令(原指令 + store)将集合間交集、并集、差集的結果儲存在destination 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
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-6給出集合常用指令的時間複雜度,開發人員可以根據自身需求進行選擇。
表2-6 集合常用指令時間複雜度
命 令 時間複雜度
sadd key element [element ...] o(k),k是元素個數
srem key element [element ...] o(k),k是元素個數
scard key o(1)
sismember key element o(1)
srandmember key [count] o(count)
spop key o(1)
smembers key o(n),n是元素總數
sinter key [key ...] 或者
sinterstore o(m*k),k是多個集合中元素最少的個數,m是鍵個數
suinon key [key ...] 或者 suionstore o(k),k是多個集合元素個數和
sdiff key [key ...] 或者 sdiffstore o(k),k是多個集合元素個數和
<b>2.5.2 内部編碼</b>
集合類型的内部編碼有兩種:
intset(整數集合):當集合中的元素都是整數且元素個數小于set-max-intset-entries配置(預設512個)時,redis會選用intset來作為集合的内部實作,進而減少記憶體的使用。
hashtable(哈希表):當集合類型無法滿足intset的條件時,redis會使用hashtable作為集合的内部實作。
下面用示例來說明:
1)當元素個數較少且都為整數時,内部編碼為intset:
127.0.0.1:6379> sadd setkey 1 2 3 4
127.0.0.1:6379> object encoding setkey
"intset"
2.1)當元素個數超過512個,内部編碼變為hashtable:
127.0.0.1:6379> sadd setkey 1 2 3 4 5 6
... 512 513
(integer) 509
127.0.0.1:6379> scard setkey
(integer) 513
127.0.0.1:6379> object encoding listkey
"hashtable"
2.2)當某個元素不為整數時,内部編碼也會變為hashtable:
127.0.0.1:6379> sadd setkey a
有關集合類型的記憶體優化技巧将在8.3節中詳細介紹。
<b>2.5.3 使用場景</b>
集合類型比較典型的使用場景是标簽(tag)。例如一個使用者可能對娛樂、體育比較感興趣,另一個使用者可能對曆史、新聞比較感興趣,這些興趣點就是标簽。有了這些資料就可以得到喜歡同一個标簽的人,以及使用者的共同喜好的标簽,這些資料對于使用者體驗以及增強使用者黏度比較重要。例如一個電子商務的網站會對不同标簽的使用者做不同類型的推薦,比如對數位産品比較感興趣的人,在各個頁面或者通過郵件的形式給他們推薦最新的數位産品,通常會為網站帶來更多的利益。
下面使用集合類型實作标簽功能的若幹功能。
(1)給使用者添加标簽
sadd user:1:tags tag1 tag2 tag5
sadd user:2:tags tag2 tag3 tag5
...
sadd user:k:tags tag1 tag2 tag4
...
(2)給标簽添加使用者
sadd tag1:users user:1 user:3
sadd tag2:users user:1 user:2 user:3
sadd tagk:users user:1 user:2
使用者和标簽的關系維護應該在一個事務内執行,防止部分指令失敗造成的資料不一緻,有關如何将兩個指令放在一個事務,第3章會介紹事務以及lua的使用方法。
(3)删除使用者下的标簽
srem user:1:tags tag1 tag5
(4)删除标簽下的使用者
srem tag1:users user:1
srem tag5:users user:1
(3)和(4)也是盡量放在一個事務執行。
(5)計算使用者共同感興趣的标簽
可以使用sinter指令,來計算使用者共同感興趣的标簽,如下代碼所示:
sinter user:1:tags user:2:tags
前面隻是給出了使用redis集合類型實作标簽的基本思路,實際上一個标簽系統遠比這個要複雜得多,不過集合類型的應用場景通常為以下幾種:
sadd = tagging(标簽)
spop/srandmember = random item(生成随機數,比如抽獎)
sadd + sinter = social graph(社交需求)?