如何利用 Redis 快速實現簽到統計功能

finecho發表於2019-03-14

@這是小豪的第十一篇文章

上篇文章 已經對 Redis 基礎命令進行了一個大致的學習,接下來我們就需要解決 Issue “增加使用者活躍度統計” 啦!

其實當我看到這個 Issue 的時候,我的第一反應是利用 MySql 來實現,建立一個簽到表,記錄使用者 ID 和 簽到時間,然後統計的時候從資料庫中取出來然後聚合計算,完美,哈哈。

但是當看到要求說要用 Redis 位運算的時候,我就在想,為啥呢,仔細想了一哈,發現如果用 MySql 來實現的話雖然簡單粗暴,但是也有弊端,比如我們想要做一些複雜的功能就不是太方便了,或者說不是太高效能了,比如,今天是連續簽到的第幾天,在一定時間內連續簽到了多少天。另外一方面,如果按100萬使用者量級來計算,一個使用者每年可以產生 365條記錄,100萬使用者的所有簽到記錄那就有點恐怖了,查詢計算速度也會越來越慢。

所以毅然選擇 Redis ,下面給大家介紹一下究竟為啥選擇它。

準備

大家知道 Redis 的字串資料都是以二進位制的形式存放的,所以說 RedisBit 操作非常適合處理這個場景,因為 Bit 的值為 0 或 1,使用者是否打卡也可以用 0 或 1 來表示,我們把簽到的天數對應到每個位元組上,打卡了就是1,沒打卡就是0,那麼一個使用者一年下來的記錄就是 365 位的長度,100萬使用者一年只需要耗費大約 43 M 左右的儲存空間就可以了,而且速度賊快,大夥可能會問,這個究竟是怎麼計算來的,我們來看一下官方的解釋:

在一臺 2010MacBook Pro 上,offset 為2^32-1(分配512MB)需要~300ms,offset 為2^30-1(分配128MB)需要~80ms,offset 為2^28-1(分配32 MB)需要~30ms,offset 為2^26-1(分配8MB)需要8ms。

大概的空間佔用計算公式是:( offset / 8 / 1024 / 1024 )MB

這裡的 offset ,大家姑且當做使用者 ID 來看,哈哈。

那麼究竟如何去打卡呢,我們可以利用 setbit 命令來實現,setbit 的作用說的直白點就是:在你想要的位置操作位元組值,比如說使用者 3 在 3月13號 簽到了,那麼 setbit(20190313, 3 ,1) 就可以實現簽到功能了,這裡的 offset 就是3,同理,不同的使用者不同的日期,改變對應的值就好了。

那麼下面我們來實戰一下:

例項

1. 例項化一個 Redis 連線

 $redis = app('redis.connection');

2. 如何去設計 key 呢?

 $dayKey = 'login:'.\now()->format('Ymd'); // 輸出類似:login:20190310

 // 普通寫法
 $dayKey = 'login:'.\date('Ymd',\time());

簡單粗暴,清晰明瞭,哈哈。

所以我們大致的格式應該是這樣子的:

file

3. 簽到

  • setbit - SETBIT KEY_NAME OFFSET (Time complexity: O(1))

    對 key 所儲存的字串值,設定或清除指定偏移量上的位 bit

    $redis->setbit($dayKey, $this->user->id, 1);

可以看到在儲存方面不僅耗費記憶體少,快,而且操作還方便,就這麼一句話就搞定了,我當初也以為會是很複雜的操作,哈哈。並且它還有非常低的靈活高效的統計計算成本。

4. 統計一週內的簽到資料

  • bitop - BITOP operation destkey key [key ...]

    對一個或多個儲存二進位制位的字串 key 進行位元操作,並將結果儲存到 destkey 上

    AND : 對一個或多個 key 求邏輯並
    OR : 對一個或多個 key 求邏輯或
    XOR : 對一個或多個 key 求邏輯異或
    NOT : 對給定 key 求邏輯非

    $redis->bitop('AND', 'threeAnd', 'login:20190311', 'login:20190312', 'login:20190313');
    echo "連續三天都簽到的使用者數量:" . $redis->bitCount('threeAnd');
    
    $redis->bitop('OR', 'threeOr', 'login:20190311', 'login:20190312', 'login:20190313');
    echo "三天中籤到使用者數量(有一天籤也算簽了):" . $redis->bitCount('threeOr');
    
    $redis->bitop('AND', 'monthActivities'', $redis->keys('login:201903*'));
    echo "連續一個月簽到使用者數量:" . $redis->bitCount('monthActivities');
    
    echo "當前使用者指定天數是否簽到:" . $redis->getbit('login:20190311', $this->user->id);
    .....

是不是特別方便快捷的統計查詢,哈哈,

結束語

從上面的例子中大家可以看到不管在儲存上面還是在統計計算上面,位運算都比 mysql 的方式好太多。

至此,一個簡單的簽到統計功能就已經實現了,大家可以根據自己的需求擴充套件,不當的地方歡迎大家指正,哈哈。

finecho # Lhao

相關文章