天天看點

Redis開發與運維. 2.5 集合

<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&gt; exists myset

(integer) 0

127.0.0.1:6379&gt; sadd myset a b c

(integer) 3

127.0.0.1:6379&gt; sadd myset a b

(2)删除元素

srem key element [element ...]

傳回結果為成功删除元素個數,例如:

127.0.0.1:6379&gt; srem myset a b

(integer) 2

127.0.0.1:6379&gt; srem myset hello

(3)計算元素個數

scard key

scard的時間複雜度為o(1),它不會周遊集合所有元素,而是直接用redis内部的變量,例如:

127.0.0.1:6379&gt; scard myset

(integer) 1

(4)判斷元素是否在集合中

sismember key element

如果給定元素element在集合内傳回1,反之傳回0,例如:

127.0.0.1:6379&gt; sismember myset c

(5)随機從集合傳回指定個數元素

srandmember key [count]

[count]是可選參數,如果不寫預設為1,例如:

127.0.0.1:6379&gt; srandmember myset 2

1) "a"

2) "c"

127.0.0.1:6379&gt; srandmember myset

"d"

(6)從集合随機彈出元素

spop key

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

127.0.0.1:6379&gt; spop myset

"c"

127.0.0.1:6379&gt; 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&gt; sadd user:1:follow it

music his sports

(integer) 4

127.0.0.1:6379&gt; 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&gt; 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&gt; 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&gt; 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&gt; sinterstore

user:1_2:inter user:1:follow user:2:follow

127.0.0.1:6379&gt; type user:1_2:inter

set

127.0.0.1:6379&gt; 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&gt; sadd setkey 1 2 3 4

127.0.0.1:6379&gt; object encoding setkey

"intset"

2.1)當元素個數超過512個,内部編碼變為hashtable:

127.0.0.1:6379&gt; sadd setkey 1 2 3 4 5 6

... 512 513

(integer) 509

127.0.0.1:6379&gt; scard setkey

(integer) 513

127.0.0.1:6379&gt; object encoding listkey

"hashtable"

2.2)當某個元素不為整數時,内部編碼也會變為hashtable:

127.0.0.1:6379&gt; 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(社交需求)?

繼續閱讀