set
- 一. set 類型介紹
- 二. set 命令
- sadd、smembers、sismember
- scard、spop、srandmember
- smove、srem
- 集合間操作
- 交集:sinter、sinterstore
- 并集:sunion、sunionstore
- 差集:sdiff、sdiffstore
- 三. set 命令小結
- 四. set 內部編碼方式
- 五. set 使用場景
- 用戶標簽
- 共同好友
- 統計 UV
一. set 類型介紹
- 集合就是把一些有關聯的數據放到一起,保存多個字符串類型的元素的 (可以使用 JSON 這樣的格式,讓 string 存儲結構化數據),但和列表類型不同的是:
- 元素之間是無序的。
- 此處說的無序和之前 list 說的有序是對應的。
- 有序:順序很重要,變換一下順序,就是不同的列表。
- 無序:順序不重要,變換一下順序,還是那個集合。
- list:[1, 2, 3] 和 [2, 1, 3] 是不同的 list
- set:[1, 2, 3] 和 [2, 1, 3] 是相同的 set
- 元素不允許重復。
- 一個集合中最多可以存儲 2^32 - 1 個元素。
- 元素之間是無序的。
- Redis 除了支持集合內的增刪查改操作,同時還支持多個集合取交集、并集、差集,合理地使用好集合類型,能在實際開發中解決很多問題。
集合類型:
二. set 命令
在 set 中的元素叫做 member,就像在 hash 中的元素叫做 field、value 類似。
sadd、smembers、sismember
- sadd:添加?/多個元素到 set 中。注意:重復的元素無法添加到集合中。
- 語法:
sadd key member [member ...]
- 時間復雜度:添加一個元素是 O(1),添加 N 個元素是 O(N)
- 返回值:本次添加成功的元素個數。
- smembers:獲取一個 set 中的所有元素。注意:元素間的順序是無序的。
- 語法:
smembers key
- 時間復雜度:O(N),N 是集合中元素的個數。
- 返回值:所有元素的列表。
- sismember:判斷一個元素在不在 set 中。
- 語法:
sismember key member
- 時間復雜度:O(1)
- 返回值:元素在 set 中,返回 1;元素不在 set 中或者 key 不存在,返回 0
scard、spop、srandmember
- scard:獲取一個 set 的基數 (cardinality),即 set 中的元素個數。
- 語法:
scard key
- 時間復雜度:O(1)
- 返回值:set 內的元素個數。
- spop:從 set 中刪除并返回一個或者多個元素。注意:由于 set 內的元素是無序的,所以取出哪個元素實際是未定義行為,即可以看作隨機的。
- 語法:
spop key [count]
- 時間復雜度:O(N),N 是 count 的個數。
- 返回值:取出的元素。
- srandmember:從 set 中返回一個或者多個隨機的元素。
- 語法:
srandmember key [count]
- 時間復雜度:O(N),N 是 count 的個數。
- 返回值:取出的元素。
在 Redis 源碼中,針對 spop 實現的時候,就采取了 “生成隨機數” srandmember 的方式。
smove、srem
- smove:將一個元素從源 set 取出并放入目標 set 中。
- 語法:
smove source destination member
- 時間復雜度:O(1)
- 返回值:移動成功時,返回 1;移動失敗時,返回 0
如果我給 key1 里再添加一個 1,再次把這個 1 移動給 key2,此時 smove 不會視為出錯,也會按照 刪除-插入 進行執行。
- srem:將指定的元素從 set 中刪除
- 語法:
srem key member [member ...]
- 時間復雜度:O(N),N 是要刪除的元素個數。
- 返回值:刪除成功的元素個數。
不同操作的返回值,含義差別還是挺大的,需要用的時候,多翻文檔即可。
集合間操作
- 交集 (inter):最終結果同時出現在兩個集合中。
- 并集 (union):把多個集合中的數據都集中放在一起,如果元素有重復,也最終只保留一份。
- 差集 (diff):A 和 B 做差集,就是找出 A 中存在,但是 B 中不存在的元素。
交集:sinter、sinterstore
- sinter:獲取給定 set 的交集中的元素。
- 語法:
sinter key [key ...]
- 時間復雜度:O(N * M),N 是最小的集合元素個數,M 是最大的集合元素個數。
- 返回值:交集的元素。
- sinterstore:獲取給定 set 的交集中的元素并保存到目標 set 中。
- 語法:
sinterstore destination key [key ...]
- 時間復雜度:O(N * M),N 是最小的集合元素個數,M 是最大的集合元素個數。
- 返回值:交集的元素個數。
并集:sunion、sunionstore
- sunion:獲取給定 set 的并集中的元素。
- 語法:
sunion key [key ...]
- 時間復雜度:O(N),N 是給定的所有集合的總的元素個數。
- 返回值:并集的元素。
- sunionstore:獲取給定 set 的并集中的元素并保存到目標 set 中。
- 語法:
sunionstore destination key [key ...]
- 時間復雜度:O(N),N 是給定的所有集合的總的元素個數。
- 返回值:并集的元素個數。
差集:sdiff、sdiffstore
- sdiff:獲取給定 set 的差集中的元素。
- 語法:
sdiff key [key ...]
- 時間復雜度:O(N),N 是給定的所有集合的總的元素個數。
- 返回值:差集的元素。
- sdiffstore:獲取給定 set 的差集中的元素并保存到目標 set 中。
- 語法:
sdiffstore destination key [key ...]
- 時間復雜度:O(N),N 是給定的所有集合的總的元素個數。
- 返回值:差集的元素個數。
三. set 命令小結
命令 | 執行效果 | 時間復雜度 |
---|---|---|
sadd key member [member …] | 向集合中添加元素 | O(K),K 是元素的個數 |
smembers key | 求集合中的元素 | O(K),K 是元素的個數 |
sismember key member | 判斷元素是否在集合中 | O(1) |
scard key | 獲取集合中元素的個數 | O(1) |
spop key [count] | 隨機刪除 count 個元素 | O(N),N 是 count |
srandmember key [count] | 隨機刪除 count 個元素 | O(N),N 是 count |
smove source destination member | 移動源集合中的一個元素到目標集合 | O(1) |
srem key member [member …] | 刪除集合中的元素 | O(K),K 是元素的個數 |
sinter key [key …] | 獲取集合的交集 | O(N * M),N 是最小的集合元素個數,M 是最大的集合元素個數 |
sinterstore destination key [key …] | 存儲集合的交集到目標集合中 | O(N * M),N 是最小的集合元素個數,M 是最大的集合元素個數 |
sunion key [key …] | 獲取集合的并集 | O(N),N 是所有集合的元素個數 |
sunionstore destination key [key …] | 存儲集合的并集到目標集合中 | O(N),N 是所有集合的元素個數 |
sdiff key [key …] | 獲取集合的差集 | O(N),N 是所有集合的元素個數 |
sdiffstore destination key [key …] | 存儲集合的差集到目標集合中 | O(N),N 是所有集合的元素個數 |
四. set 內部編碼方式
集合類型的內部編碼有兩種:
- intset (整數集合):當集合中的元素都是整數并且元素的個數小于 set-max-intset-entries 配置 (默認 512 個) 時,Redis 會選用 intset 來作為集合的內部實現,從而減少內存的使用。
- Redis 是內存數據庫,當元素均為整數,并且元素的個數不是很多的時候,為了節省空間,做出的特點優化。
- hashtable (哈希表):當集合類型無法滿足 intset 的條件時,Redis 會使用 hashtable 作為集合的內部實現。
使用 object encoding key
可以查看集合內部的編碼方式,如下:
五. set 使用場景
用戶標簽
集合類型比較典型的使用場景是標簽 (tag),使用 set 來保存用戶的 “標簽”
- 例如 A 用戶對娛樂、體育板塊比較感興趣,B 用戶對歷史、新聞比較感興趣,這些興趣點可以被抽象為標簽。例如一個電子商務網站會對不同標簽的用戶做不同的產品推薦。
- 有了這些數據就可以得到喜歡同一個標簽的人,以及用戶的共同喜好的標簽,這些數據對于增強用戶體驗和用戶黏度都非常有幫助。分析出你這個人的一些特征,分析清楚之后,再投其所好。
- 上述用戶數據,很多公司都在共享。
- 兩個程序,兩個賬號,如何知道這兩個賬號是一個人?
- 現在的程序登入,主要就是兩個入口,手機號、微信。
- 通過上述過程,搜集到的用戶特征,就會轉換成 “標簽” (簡短的字符串),此時就可以把標簽保存到 Redis 的 set 中。
- 用戶畫像,這種事情其實是挺復雜的事情,一般一個大廠都會有專門的團隊做這樣工作。
- 上述玩法,抖音玩的是最好的,其它互聯網大廠一看這么搞真好,于是紛紛效仿。
- 但是存在 “信息繭房” 問題:你看到的內容始終就是一個小圈子,看不到其它圈子中的事物。
- 當你很認真的看了一個視頻之后,人家的服務器就判定你,非常愛看這種類型的視頻,接下來就給你瘋狂推送。
下面的演示通過集合類型來實現標簽的若干功能。
- 給用戶添加標簽
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:4 user:9 user:28
- 刪除用戶下的標簽
srem user:1:tags tag1 tag5
...
- 刪除標簽下的用戶
srem tag1:users user:1
srem tag5:users user:1
...
- 計算用戶的共同興趣標簽
sinter user:1:tags user:2:tags
共同好友
使用 set 來計算用戶之間的公共好友,基于 “集合求交集”
- 例如:QQ,我這邊加了很多好友,你那邊也加了很多好友。
- 基于上述還可以做一些好友推薦:A 和 B 是好友,A 和 C 是好友,B 和 C 和 D 都是好友,此時系統就會把 D 推薦給 A
統計 UV
一個互聯網產品,如何衡量用戶量,用戶規模?主要的指標,是兩方面:
- PV (page view):用戶每次訪問該服務器,都會產生一個 PV
- UV (user view):每個用戶訪問該服務器,都會產生一個 UV,但是同一個用戶多次訪問,不會增加 UV 的個數。
- UV 需要按照用戶進行去重,上述的去重功能,就可以使用 set 來實現。