1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        使用Redis BitMap實(shí)現(xiàn)簽到與統(tǒng)計(jì)功能

        共 6871字,需瀏覽 14分鐘

         ·

        2024-03-29 12:00

        文章轉(zhuǎn)載自

        大家好,今天分享一篇關(guān)于如何實(shí)現(xiàn)網(wǎng)站簽到的文章。

        1bdeadb943ad55f847a2f593c5c56a18.webp

        各個項(xiàng)目中,我們都可能需要用到簽到和 統(tǒng)計(jì)功能。簽到后會給用戶一些禮品以此來吸引用戶持續(xù)在該平臺進(jìn)行活躍。

        簽到功能,我們可以通過Redis中的 BitMap功能來實(shí)現(xiàn)

        一、Redis BitMap 基本用法

        BitMap 基本語法、指令

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

        969860eee236e29ddeb398085763e37c.webp

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

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

        這樣的壞處,占用內(nèi)存太大了,極大的消耗內(nèi)存空間!

        我們可以根據(jù) Redis中 提供的 BitMap 位圖功能來實(shí)現(xiàn),每次簽到與未簽到用0 或1 來標(biāo)識 ,一次存31個數(shù)字,只用了2字節(jié) 這樣我們就用極小的空間實(shí)現(xiàn)了簽到功能。

        BitMap 的操作指令:

          • SETBIT:向指定位置(offset)存入一個0或1

          • GETBIT:獲取指定位置(offset)的bit值

          • BITCOUNT:統(tǒng)計(jì)BitMap中值為1的bit位的數(shù)量

          • BITFIELD:操作(查詢、修改、自增)BitMap中bit數(shù)組中的指定位置(offset)的值

          • BITFIELD_RO:獲取BitMap中bit數(shù)組,并以十進(jìn)制形式返回

          • BITOP:將多個BitMap的結(jié)果做位運(yùn)算(與 、或、異或)

          • BITPOS:查找bit數(shù)組中指定范圍內(nèi)第一個0或1出現(xiàn)的位置

        使用 BitMap 完成功能實(shí)現(xiàn)

        服務(wù)器 Redis 版本采用 6.2 版本

        進(jìn)入redis 查詢 SETBIT 命令

        94bb8f85de7f36947c06f0ebc753f048.webp

        新增 key 進(jìn)行存儲

        1c6f284409cb09d1bee7c1a3df93c898.webp

        查詢 GETBIT 命令

        2a24894fb28f2db62d753aae4632a1b1.webp

        查看指定坐標(biāo)的簽到狀態(tài)

        7f70e74511a43b25e9c89677581de06b.webp

        查詢 BITFIELD 命令

        16627308687f168e526681be89fee683.webp

        無符號查詢

        dac3ee66609ae1f7b0642c007506d7d4.webp

        BITPOS 查詢 10 第一次出現(xiàn)的坐標(biāo)

        08bc63f2e1307939fa5958bd9202ccf2.webp

        二、SpringBoot 整合 Redis 實(shí)現(xiàn)簽到 功能

        需求介紹

        采用BitMap實(shí)現(xiàn)簽到功能


        • 實(shí)現(xiàn)簽到接口,將當(dāng)前用戶當(dāng)天簽到信息保存到Redis中


        思路分析:

        我們可以把 年和月 作為BitMap的key,然后保存到一個BitMap中,每次簽到就到對應(yīng)的位上把數(shù)字從0 變?yōu)?,只要是1,就代表是這一天簽到了,反之咋沒有簽到。

        實(shí)現(xiàn)簽到接口,將當(dāng)前用戶當(dāng)天簽到信息保存至Redis中

        6807e397a2f1eaa2d7cbfe71f3a50927.webp

        提示:因?yàn)锽itMap 底層是基于String數(shù)據(jù)結(jié)構(gòu),因此其操作都封裝在字符串操作中了。

        fdde7ab5e2df47e30d2753a01ea372c3.webp

        核心源碼

        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 -1true);
            return Result.ok();
        }

        接口進(jìn)行測試

        ApiFox進(jìn)行測試

        7859412e8874ee49e6a4d81341493575.webp

        查看Redis 數(shù)據(jù)

        e43a8e202cc2115312efd07cf6cdc5e5.webp

        三、SpringBoot 整合Redis 實(shí)現(xiàn) 簽到統(tǒng)計(jì)功能

        問題一:什么叫做連續(xù)簽到天數(shù)?

        從最后一次簽到開始向前統(tǒng)計(jì),直到遇到第一次未簽到為止,計(jì)算總的簽到次數(shù),就是連續(xù)簽到天數(shù)。

        906582ac54bfcc7f187d497290948ae4.webp

        邏輯分析:

        ?

        獲得當(dāng)前這個月的最后一次簽到數(shù)據(jù),定義一個計(jì)數(shù)器,然后不停的向前統(tǒng)計(jì),直到獲得第一個非0的數(shù)字即可,每得到一個非0的數(shù)字計(jì)數(shù)器+1,直到遍歷完所有的數(shù)據(jù),就可以獲得當(dāng)前月的簽到總天數(shù)了

        ?

        問題二:如何得到本月到今天為止的所有簽到數(shù)據(jù)?

                BITFIELD key GET u[dayOfMonth] 0
              

        假設(shè)今天是7號,那么我們就可以從當(dāng)前月的第一天開始,獲得到當(dāng)前這一天的位數(shù),是7號,那么就是7位,去拿這段時間的數(shù)據(jù),就能拿到所有的數(shù)據(jù)了,那么這7天里邊簽到了多少次呢?統(tǒng)計(jì)有多少個1即可。

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

        ?

        注意:bitMap返回的數(shù)據(jù)是10進(jìn)制,哪假如說返回一個數(shù)字8,那么我哪兒知道到底哪些是0,哪些是1呢?

        ?

        我們只需要讓得到的10進(jìn)制數(shù)字和1做與運(yùn)算就可以了,因?yàn)?只有遇見1 才是1,其他數(shù)字都是0 ,我們把簽到結(jié)果和1進(jìn)行與操作,每與一次,就把簽到結(jié)果向右移動一位,依次內(nèi)推,我們就能完成逐個遍歷的效果了。

        需求:

        實(shí)現(xiàn)以下接口,統(tǒng)計(jì)當(dāng)前截至當(dāng)前時間在本月的連續(xù)天數(shù)

        711993a88f94c79ba7ff947ecceb17fc.webp

        有用戶有時間我們就可以組織出對應(yīng)的key,此時就能找到這個用戶截止這天的所有簽到記錄,再根據(jù)這套算法,就能統(tǒng)計(jì)出來他連續(xù)簽到的次數(shù)了

        核心源碼

        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. 獲取本月截至今天為止的所有的簽到記錄,返回的是一個十進(jìn)制的數(shù)字 BITFIELD sign:5:202301 GET u3 0
            List<Long> result = stringRedisTemplate.opsForValue().bitField(
                key,
                BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
            //沒有任務(wù)簽到結(jié)果
            if (result == null || result.isEmpty()) {
                return Result.ok(0);
            }
            Long num = result.get(0);
            if (num == null || num == 0) {
                return Result.ok(0);
            }
            //6. 循環(huán)遍歷
            int count = 0;
            while (true) {
                //6.1 讓這個數(shù)字與1 做與運(yùn)算,得到數(shù)字的最后一個bit位 判斷這個數(shù)字是否為0
                if ((num & 1) == 0) {
                    //如果為0,簽到結(jié)束
                    break;
                } else {
                    count ++;
                }
                num >>>= 1;
            }
            return Result.ok(count);
        }

        進(jìn)行測試

        f09360b175b92c19373a9a65e79cc82d.webp

        查看 Redis 變量

        13de20535df5b6c15fe92b68b6a6f144.webp

        從今天開始,往前查詢 連續(xù)簽到的天數(shù),結(jié)果為2 測試無誤!

        四、關(guān)于使用bitmap來解決緩存穿透的方案

        回顧緩存穿透:

        發(fā)起了一個數(shù)據(jù)庫不存在的,redis里邊也不存在的數(shù)據(jù),通常你可以把他看成一個攻擊

        解決方案:


        • 判斷id<0



        • 數(shù)據(jù)庫為空的話,向redis里邊把這個空數(shù)據(jù)緩存起來


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

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

        所以我們?nèi)绾谓鉀Q呢?

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

        3470fb84af0048d529bfd46d784fc28e.webp

        現(xiàn)在的問題是這個主鍵其實(shí)并沒有那么短,而是很長的一個 主鍵

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

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

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

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

        3d73bcacef6e2b5025a45ead4cb3bed0.webp

        小結(jié)

        以上就是對 微服務(wù) Spring Boot 整合 Redis BitMap 實(shí)現(xiàn) 簽到與統(tǒng)計(jì) 的簡單介紹,簽到功能是很常用的,在項(xiàng)目中,是一個不錯的亮點(diǎn),統(tǒng)計(jì)功能也是各大系統(tǒng)中比較重要的功能,簽到完成后,去統(tǒng)計(jì)本月的連續(xù) 簽到記錄,來給予獎勵,可大大增加用戶對系統(tǒng)的活躍度。

        點(diǎn)個 在看 你最好看



        瀏覽 50
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            日韩中文字幕免费 | 欧美成人性生活 | 美女乱伦视频 | 古代高辣禁交换伦h | 欧美老年人性生活视频 | 亚洲伦理视频 | 性爱视频播放器 | 黑人借宿与人妻羽月希 | 坐公交忘穿内裤被挺进老外文案 | 韩国三级做爰高潮 |