Redis 集合儲存詳解

2021-10-14 11:00:16 字數 4441 閱讀 9785

以下文章**於碼猿技術專欄 ,作者不才陳某

不知你大規模的用過redis嗎?還是僅僅作為快取的工具了?在redis中使用最多的就是集合了,舉個例子,如下場景:

簽到系統中,一天對應一系列的使用者簽到記錄。

交友系統中,某個使用者的一系列的好友。

redis中集合的特點無非是乙個key對應一系列的資料, 但是資料的作用往往是為了統計的,比如:

交友系統中,需要統計每天的新增好友,以及雙方的共同好友。

簽到系統中,需要統計連續乙個月的簽到的使用者數量。

只有針對不同場景,選擇合適的集合,統計才能更方便。

聚合統計指的是多個元素聚合的結果,比如統計多個集合的交集、並集、差集

在你需要對多個集合做聚合統計的時候,set集合是個不錯的選擇,除了其中無重複的資料外,redis還提供了對應的api

在上述的例子中交友系統中統計雙方的共同好友正是聚合統計中的交集。

在redis中可以userid作為key,好友的userid作為value,如下圖:

統計兩個使用者的共同好友只需要兩個set集合的交集,命令如下;

sinterstore userid:new userid:20002 userid:20003
上述命令執行完成後,userid:new這個key中儲存的將是userid:20002、userid:20003兩個集合的交集。

舉個例子:假設交友系統中需要統計每日新增的好友,此時就需要對臨近兩天的好友集合取差集了,比如2020/11/1日的好友是set1,2020/11/2日的好友是set2,此時只需要對set1和set2做差集。

此時的結構應該如何設計呢?如下圖:

userid:20201101這個key記錄了userid使用者的2020/11/1日的好友集合。

差集很簡單,只需要執行sdiffstore命令,如下:

sdiffstore  user:new  userid:20201102 userid:20201101
執行完畢,此時的user:new這集合將是2020/11/2日新增的好友。

這裡還有乙個更貼切的例子,微博上有個可能認識的人功能,可以使用差集,即是你朋友的好友減去你們共同的好友即是可能認識的人。

還是差集的那個例子,假設需要統計2020/11/01和2020/11/2總共新增的好友,此時只需要對這兩日新增好友的集合做乙個並集。命令如下:

sunionstore  userid:new userid:20201102 userid:20201101
此時新的集合userid:new則是兩日新增的好友。

set集合的交差並的計算複雜度很高,如果資料量很大的情況下,可能會造成redis的阻塞。

那麼如何規避阻塞呢?建議如下:

在redis集群中選乙個從庫專門負責聚合統計,這樣就不會阻塞主庫和其他的從庫了

將資料交給客戶端,由客戶端進行聚合統計。

redis中的四種集合中list和sorted set屬於有序集合。

但是list和sorted set有何區別呢?到底使用哪一種呢?

list是按照元素進入順序進行排序,而sorted set可以根據元素權重來排序。比如可以根據元素插入集合的時間確定權值,先插入的元素權重小,後插入的元素權重大。

針對這一例子中,顯然這兩種都是能夠滿足要求的,list中分頁查詢命令lrange和sorted set分頁查詢命令zrangebyscore。

但是就靈活性來說,list肯定不適合,list只能根據先後插入的順序排序,但是大多數的場景中可能並不只是按照時間先後排序,可能還會按照一些特定的條件,此時sorted set就很合適了,只需要根據獨有的演算法生成相應的權重即可。

二值狀態指的是取值0或者1兩種;在簽到打卡的場景中,只需要記錄簽到(1)和未簽到(0)兩種狀態,這就是典型的二值狀態統計。

二值狀態的統計可以使用redis的擴充套件資料型別bitmap,底層使用string型別實現,可以把它看成是乙個bit陣列。關於詳細內容後續介紹.........

在簽到統計中,0和1只佔了乙個bit,即使一年的簽到資料才365個bit位。大大減少了儲存空間。

bitmap 提供了getbit/setbit 操作,使用乙個偏移值 offset 對 bit 陣列的某乙個 bit 位進行讀和寫。不過,需要注意的是,bitmap 的偏移量是從 0 開始算的,也就是說 offset 的最小值是 0。當使用 setbit 對乙個 bit 位進行寫操作時,這個 bit 位會被設定為 1。bitmap 還提供了 bitcount 操作,用來統計這個 bit 陣列中所有1的個數。

鍵值如何設計呢?key可以是userid:yyyymm,即是唯一id加上月份。假設員工id為10001,需要統計2020/11月份的簽到打卡記錄。

第一步,執行命令設定值,假設11月2號打卡了,命令如下:

setbit userid:10001:202011 1 1
bitmap是從下標0開始,因此2號則是下標為1,值設定為1則表示成功打卡了。

第二步,檢查該使用者11月2號是否打卡了,命令如下:

getbit userid:10001:202011 1
第三步,統計11月的打卡次數,命令如下:

bitcount userid:10001:202011
那麼問題來了,需要統計你這個簽到系統中連續20天的簽到打卡的使用者的總數,如何處理呢?假設使用者乙個億。

比如需要統計2020/11/01到2020/11/20天中連續打卡的人數,如何統計呢?

bitmap中還支援同時對多個bitmap按位做與、或、異或操作,命令如下圖:

思路來了,我們可以將每天的日期作為乙個key,對應的bitmap儲存一億個使用者當天的打卡情況。如下圖:

此時我們只需要對2020/11/1到2020/11/20號的bitmap做按位與操作,最終得到的乙個bitmap中每個bit位置對應的值則代表連續20天打卡的情況,只有連續20天全部打卡,所在的bit位的值才為1。如下圖:

最終可以使用bitcount命令進行統計。

可以嘗試計算下記憶體開銷,每天使用 1 個 1 億位的 bitmap,大約佔 12mb 的記憶體(10^8/8/1024/1024),20 天的 bitmap 的記憶體開銷約為 240mb,記憶體壓力不算太大。不過,在實際應用時,最好對 bitmap 設定過期時間,讓 redis 自動刪除不再需要的簽到記錄,以節省記憶體開銷。

如果涉及到二值狀態,比如使用者是否存在,簽到打卡,商品是否存在等情況可以使用bitmap,可以有效的節省記憶體空間。

基數統計指統計乙個集合中不重複元素的個數。

但是這裡有乙個問題,set底層使用的是雜湊表和整數陣列,如果乙個網頁的uv達到千萬級別的話(乙個電商**中何止乙個頁面),那麼對於記憶體的消耗極大。

redis提供了乙個擴充套件型別hyperloglog用於基數統計,計算2^64個元素大概只需要12kb的記憶體空間

是不是很心動?但是hyperloglog是存在誤差的,大概是在0.81%,如果需要精準的統計,還是需要使用set。對於這種網頁的uv來說,足夠了。

在統計網頁uv的時候,只需要將使用者的唯一id存入hyperloglog中,如下:

pfadd p1:uv 10001 10002 10003 10004
如果存在重複的元素,將會自動去重。

統計也很簡單,使用pfcount命令,如下:

set和sorted set支援交集、並集的聚合運算,但是sorted set不支差集運算。

bitmap也能對多個bitmap做與、異或、或的聚合運算。

list和sortedset都支援排序統計,但是list是根據元素先後插入順序排序,sorted set支援權重,相對於list排序來說更加靈活。

對於二值狀態統計,判斷某個元素是否存在等場景,建議使用bitmap,節省的記憶體空間。

對於基數統計,在大資料量、不要求精準的情況建議使用hyperloglog,節省記憶體空間;對於精準的基數統計,最好還是使用set集合。

Redis 集合(set)命令詳解

向集合新增乙個或多個成員 sadd 命令將乙個或多個成員元素加入到集合中,已經存在於集合的成員元素將被忽略。假如集合 key 不存在,則建立乙個只包含新增的元素作成員的集合。當集合 key 不是集合型別時,返回乙個錯誤。返回值 被新增到集合中的新元素的數量,不包括被忽略的元素。返回集合中的所有成員 ...

redis無序集合set型別詳解

redis中的set是string型別的無序集合,set元素最大可以包含2的32次方 1個元素。利用set集合型別,我們可以快速取出n個key之間的並集 交集 差集等,從而輕鬆解決mysql等資料庫不容易實現這種運算的缺陷。與上篇中list型別不同的是,set集合不允許出現重複的元素,因此set型別...

redis之set 集合命令詳解

集合的性質 唯一性,無序性,確定性 注 在string和link的命令中,可以通過range 來訪問string中的某幾個字元或某幾個元素 但,因為集合的無序性,無法通過下標或範圍來訪問部分元素.因此想看元素,要麼隨機先乙個,要麼全選 作用 往集合key中增加元素 redis sadd k v1 i...