微服務 Spring Boot 整合 Redis BitMap 實現簽到與統計

作者:Bug 終結者

原文:https://blog.csdn.net/weixin_45526437/article/details/128606929

引言

在各個項目中,我們都可能需要用到簽到和 統計功能。簽到後會給用戶一些禮品以此來吸引用戶持續在該平臺進行活躍。

簽到功能,我們可以通過 Redis 中的 BitMap 功能來實現

一、Redis BitMap 基本用法

BitMap 基本語法、指令

簽到功能我們可以使用 MySQL 來完成,比如下表:

用戶一次簽到,就是一條記錄,假如有 1000 萬用戶,平均每人每年簽到次數爲 10 次,則這張表一年的數據量爲 1 億條

每簽到一次需要使用(8 + 8 + 1 + 1 + 3 + 1)共 22 字節的內存,一個月則最多需要 600 多字節

這樣的壞處,佔用內存太大了,極大的消耗內存空間!

我們可以根據 Redis 中 提供的 BitMap 位圖功能來實現,每次簽到與未簽到用 0 或 1 來標識 ,一次存 31 個數字,只用了 2 字節 這樣我們就用極小的空間實現了簽到功能

BitMap 的操作指令:

使用 BitMap 完成功能實現

服務器 Redis 版本採用 6.2

進入 redis 查詢 SETBIT 命令

新增 key 進行存儲

查詢 GETBIT 命令

查看指定座標的簽到狀態

查詢 BITFIELD

無符號查詢

BITPOS 查詢 1 和 0 第一次出現的座標

二、SpringBoot 整合 Redis 實現簽到 功能

需求介紹

採用 BitMap 實現簽到功能

思路分析:

我們可以把 年和月 作爲 BitMap 的 key,然後保存到一個 BitMap 中,每次簽到就到對應的位上把數字從 0 變爲 1,只要是 1,就代表是這一天簽到了,反之咋沒有簽到。

實現簽到接口,將當前用戶當天簽到信息保存至 Redis 中

提示:因爲 BitMap 底層是基於 String 數據結構,因此其操作都封裝在字符串操作中了。

核心源碼

UserController

@PostMapping("sign")
public Result sign() {
    return userService.sign();
}

UserServiceImpl

public Result sign() {
    //1. 獲取登錄用戶
    Long userId = UserHolder.getUser().getId();
    //2. 獲取日期
    LocalDateTime now = LocalDateTime.now();
    //3. 拼接key
    String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
    String key = RedisConstants.USER_SIGN_KEY + userId + keySuffix;
    //4. 獲取今天是本月的第幾天
    int dayOfMonth = now.getDayOfMonth();
    //5. 寫入redis setbit key offset 1
    stringRedisTemplate.opsForValue().setBit(key, dayOfMonth -1, true);
    return Result.ok();
}

接口進行測試

ApiFox 進行測試

查看 Redis 數據

三、SpringBoot 整合 Redis 實現 簽到統計功能

問題一:什麼叫做連續簽到天數?

從最後一次簽到開始向前統計,直到遇到第一次未簽到爲止,計算總的簽到次數,就是連續簽到天數。

邏輯分析:

獲得當前這個月的最後一次簽到數據,定義一個計數器,然後不停的向前統計,直到獲得第一個非 0 的數字即可,每得到一個非 0 的數字計數器 + 1,直到遍歷完所有的數據,就可以獲得當前月的簽到總天數了

問題二:如何得到本月到今天爲止的所有簽到數據?

BITFIELD key GET u[dayOfMonth] 0

假設今天是 7 號,那麼我們就可以從當前月的第一天開始,獲得到當前這一天的位數,是 7 號,那麼就是 7 位,去拿這段時間的數據,就能拿到所有的數據了,那麼這 7 天裏邊簽到了多少次呢?統計有多少個 1 即可。

問題三:如何從後向前遍歷每個 Bit 位?

注意:bitMap 返回的數據是 10 進制,哪假如說返回一個數字 8,那麼我哪兒知道到底哪些是 0,哪些是 1 呢?

我們只需要讓得到的 10 進制數字和 1 做與運算就可以了,因爲 1 只有遇見 1 纔是 1,其他數字都是 0 ,我們把簽到結果和 1 進行與操作,每與一次,就把簽到結果向右移動一位,依次內推,我們就能完成逐個遍歷的效果了。

需求:

實現以下接口,統計當前截至當前時間在本月的連續天數

有用戶有時間我們就可以組織出對應的 key,此時就能找到這個用戶截止這天的所有簽到記錄,再根據這套算法,就能統計出來他連續簽到的次數了

核心源碼

UserController

@GetMapping("/signCount")
public Result signCount() {
    return userService.signCount();
}

UserServiceImpl

public Result signCount() {
    //1. 獲取登錄用戶
    Long userId = UserHolder.getUser().getId();
    //2. 獲取日期
    LocalDateTime now = LocalDateTime.now();
    //3. 拼接key
    String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
    String key = RedisConstants.USER_SIGN_KEY + userId + keySuffix;
    //4. 獲取今天是本月的第幾天
    int dayOfMonth = now.getDayOfMonth();
    //5. 獲取本月截至今天爲止的所有的簽到記錄,返回的是一個十進制的數字 BITFIELD sign:5:202301 GET u3 0
    List<Long> result = stringRedisTemplate.opsForValue().bitField(
        key,
        BitFieldSubCommands.create()
        .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
    //沒有任務簽到結果
    if (result == null || result.isEmpty()) {
        return Result.ok(0);
    }
    Long num = result.get(0);
    if (num == null || num == 0) {
        return Result.ok(0);
    }
    //6. 循環遍歷
    int count = 0;
    while (true) {
        //6.1 讓這個數字與1 做與運算,得到數字的最後一個bit位 判斷這個數字是否爲0
        if ((num & 1) == 0) {
            //如果爲0,簽到結束
            break;
        } else {
            count ++;
        }
        num >>>= 1;
    }
    return Result.ok(count);
}

進行測試

查看 Redis 變量

從今天開始,往前查詢 連續簽到的天數,結果爲 2 測試無誤!

四、關於使用 bitmap 來解決緩存穿透的方案

回顧緩存穿透:

發起了一個數據庫不存在的,redis 裏邊也不存在的數據,通常你可以把他看成一個攻擊

解決方案:

第一種解決方案:遇到的問題是如果用戶訪問的是 id 不存在的數據,則此時就無法生效

第二種解決方案:遇到的問題是:如果是不同的 id 那就可以防止下次過來直擊數據

所以我們如何解決呢?

我們可以將數據庫的數據,所對應的 id 寫入到一個 list 集合中,當用戶過來訪問的時候,我們直接去判斷 list 中是否包含當前的要查詢的數據,如果說用戶要查詢的 id 數據並不在 list 集合中,則直接返回,如果 list 中包含對應查詢的 id 數據,則說明不是一次緩存穿透數據,則直接放行。

現在的問題是這個主鍵其實並沒有那麼短,而是很長的一個 主鍵

哪怕你單獨去提取這個主鍵,但是在 11 年左右,淘寶的商品總量就已經超過 10 億個

所以如果採用以上方案,這個 list 也會很大,所以我們可以使用 bitmap 來減少 list 的存儲空間

我們可以把 list 數據抽象成一個非常大的 bitmap,我們不再使用 list,而是將 db 中的 id 數據利用哈希思想,比如:

id 求餘 bitmap 長度 :id % bitmap.size = 算出當前這個 id 對應應該落在 bitmap 的哪個索引上,然後將這個值從 0 變成 1,然後當用戶來查詢數據時,此時已經沒有了 list,讓用戶用他查詢的 id 去用相同的哈希算法, 算出來當前這個 id 應當落在 bitmap 的哪一位,然後判斷這一位是 0,還是 1,如果是 0 則表明這一位上的數據一定不存在,採用這種方式來處理,需要重點考慮一個事情,就是誤差率,所謂的誤差率就是指當發生哈希衝突的時候,產生的誤差。

小結

以上就是對 微服務 Spring Boot 整合 Redis BitMap 實現 簽到與統計 的簡單介紹,簽到功能是很常用的,在項目中,是一個不錯的亮點,統計功能也是各大系統中比較重要的功能,簽到完成後,去統計本月的連續 簽到記錄,來給予獎勵,可大大增加用戶對系統的活躍度 技術改變世界!!!

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/TdBYkmfyDdzBRnNpsEJk_w