給你一個億的keys,Redis如何統計?

前言

不知你大規模的用過Redis嗎?仍是僅僅做爲緩存的工具了?在Redis中使用最多的就是集合了,舉個例子,以下場景:算法

  1. 簽到系統中,一天對應一系列的用戶簽到記錄。
  2. 電商系統中,一個商品對應一系列的評論。
  3. 交友系統中,某個用戶的一系列的好友。

Redis中集合的特色無非是一個Key對應一系列的數據, 可是數據的做用每每是爲了統計的,好比:數組

  1. 交友系統中,須要統計天天的新增好友,以及雙方的共同好友。
  2. 電商系統中,須要統計評論列表中的最新評論。
  3. 簽到系統中,須要統計連續一個月的簽到的用戶數量。

大型互聯網應用中,數據量是巨大的,少說百萬,千萬,甚至是一個億,好比電商巨頭淘寶,交友巨頭微信、微博;辦公巨頭釘釘等,哪個的用戶不是上億?緩存

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

聚合統計

聚合統計指的是多個元素聚合的結果,好比統計多個集合的交集並集差集工具

在你須要對多個集合作聚合統計的時候,Set集合是個不錯的選擇,除了其中無重複的數據外,Redis還提供了對應的API大數據

交集

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

Redis中能夠userid做爲key,好友的userid做爲value,以下圖:spa

統計兩個用戶的共同好友只須要兩個Set集合的交集,命令以下;設計

`SINTERSTORE userid:new userid:20002 userid:20003
`3d

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

差集

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

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

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

差集很簡單,只須要執行SDIFFSTORE命令,以下:

SDIFFSTORE  user:new  userid:20201102 userid:20201101

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

這裏還有一個更貼切的例子,微博上有個可能認識的人功能,可使用差集,便是你朋友的好友減去大家共同的好友便是可能認識的人。

並集

仍是差集的那個例子,假設須要統計2020/11/012020/11/2總共新增的好友,此時只須要對這兩日新增好友的集合作一個並集。命令以下:

`SUNIONSTORE  userid:new userid:20201102 userid:20201101
`

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

總結

Set集合的交差並的計算複雜度很高,若是數據量很大的狀況下,可能會形成Redis的阻塞。

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

  1. Redis集羣中選一個從庫專門負責聚合統計,這樣就不會阻塞主庫和其餘的從庫了
  2. 將數據交給客戶端,由客戶端進行聚合統計。

排序統計

在一些電商網站中能夠看到商品的評論老是最新的在上面,這個是怎麼作的呢?

最新評論列表包含了全部的評論,這就要集合對元素進行保序存儲了。也就是說集合中的元素必須按序存儲,稱之爲有序集合。

Redis中的四種集合中ListSorted Set屬於有序集合。

可是ListSorted Set有何區別呢?到底使用哪種呢?

List是按照元素進入順序進行排序,而Sorted Set能夠根據元素權重來排序。 好比能夠根據元素插入集合的時間肯定權值,先插入的元素權重小,後插入的元素權重大。

針對這一例子中,顯然這兩種都是可以知足要求的,List中分頁查詢命令LRANGESorted Set分頁查詢命令ZRANGEBYSCORE

可是就靈活性來講,List確定不適合,List只能根據前後插入的順序排序,可是大多數的場景中可能並不僅是按照時間前後排序,可能還會按照一些特定的條件,此時Sorted Set就很合適了,只須要根據獨有的算法生成相應的權重便可。

二值狀態統計

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

二值狀態的統計可使用Redis的擴展數據類型Bitmap,底層使用String類型實現,能夠把它當作是一個bit數組。關於詳細內容後續介紹.........

在簽到統計中,01只佔了一個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/012020/11/20天中連續打卡的人數,如何統計呢?

Bitmap中還支持同時對多個BitMap按位作異或操做,命令以下圖:

思路來了,咱們能夠將天天的日期做爲一個key,對應的BitMap存儲一億個用戶當天的打卡狀況。以下圖:

此時咱們只須要對2020/11/12020/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,能夠有效的節省內存空間。

基數統計

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

舉個栗子:電商網站中一般須要統計每一個網頁的UV來肯定權重,網頁的UV確定是須要去重的,在Redis類型中Set支持去重,第一時間確定想到的是Set。

可是這裏有一個問題,Set底層使用的是哈希表和整數數組,若是一個網頁的UV達到千萬級別的話(一個電商網站中何止一個頁面),那麼對於內存的消耗極大。

Redis提供了一個擴展類型HyperLogLog用於基數統計,計算2^64個元素大概只須要12KB的內存空間

是否是很心動?可是HyperLogLog存在偏差的,大概是在0.81%,若是須要精準的統計,仍是須要使用Set。對於這種網頁的UV來講,足夠了。

在統計網頁UV的時候,只須要將用戶的惟一id存入HyperLogLog中,以下:

`PFADD p1:uv 10001 10002 10003 10004
`

若是存在重複的元素,將會自動去重。

統計也很簡單,使用PFCOUNT命令,以下:

`PFCOUNT p1:uv
`

總結

本文介紹了統計的幾種類型以及應該用什麼集合存儲,爲了方便理解,做者將支持狀況和優缺點彙總了一張表格,以下圖:

SetSorted Set支持交集、並集的聚合運算,可是Sorted Set不支差集運算。

Bitmap也能對多個Bitmap作與、異或、或的聚合運算。

ListSortedSet都支持排序統計,可是List是根據元素前後插入順序排序,Sorted Set支持權重,相對於List排序來講更加靈活。

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

對於基數統計,在大數據量、不要求精準的狀況建議使用HyperLogLog,節省內存空間;對於精準的基數統計,最好仍是使用Set集合。

相關文章
相關標籤/搜索