<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          使用Redis BitMap實現簽到與統(tǒng)計功能

          共 6871字,需瀏覽 14分鐘

           ·

          2024-03-29 12:00

          文章轉載自

          大家好,今天分享一篇關于如何實現網站簽到的文章。

          1bdeadb943ad55f847a2f593c5c56a18.webp

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

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

          一、Redis BitMap 基本用法

          BitMap 基本語法、指令

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

          969860eee236e29ddeb398085763e37c.webp

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

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

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

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

          BitMap 的操作指令:

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

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

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

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

            • BITFIELD_RO:獲取BitMap中bit數組,并以十進制形式返回

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

            • BITPOS:查找bit數組中指定范圍內第一個0或1出現的位置

          使用 BitMap 完成功能實現

          服務器 Redis 版本采用 6.2 版本

          進入redis 查詢 SETBIT 命令

          94bb8f85de7f36947c06f0ebc753f048.webp

          新增 key 進行存儲

          1c6f284409cb09d1bee7c1a3df93c898.webp

          查詢 GETBIT 命令

          2a24894fb28f2db62d753aae4632a1b1.webp

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

          7f70e74511a43b25e9c89677581de06b.webp

          查詢 BITFIELD 命令

          16627308687f168e526681be89fee683.webp

          無符號查詢

          dac3ee66609ae1f7b0642c007506d7d4.webp

          BITPOS 查詢 10 第一次出現的坐標

          08bc63f2e1307939fa5958bd9202ccf2.webp

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

          需求介紹

          采用BitMap實現簽到功能


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


          思路分析:

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

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

          6807e397a2f1eaa2d7cbfe71f3a50927.webp

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

          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();
          }

          接口進行測試

          ApiFox進行測試

          7859412e8874ee49e6a4d81341493575.webp

          查看Redis 數據

          e43a8e202cc2115312efd07cf6cdc5e5.webp

          三、SpringBoot 整合Redis 實現 簽到統(tǒng)計功能

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

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

          906582ac54bfcc7f187d497290948ae4.webp

          邏輯分析:

          ?

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

          ?

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

                  BITFIELD key GET u[dayOfMonth] 0
                

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

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

          ?

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

          ?

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

          需求:

          實現以下接口,統(tǒng)計當前截至當前時間在本月的連續(xù)天數

          711993a88f94c79ba7ff947ecceb17fc.webp

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

          核心源碼

          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. 循環(huán)遍歷
              int count = 0;
              while (true) {
                  //6.1 讓這個數字與1 做與運算,得到數字的最后一個bit位 判斷這個數字是否為0
                  if ((num & 1) == 0) {
                      //如果為0,簽到結束
                      break;
                  } else {
                      count ++;
                  }
                  num >>>= 1;
              }
              return Result.ok(count);
          }

          進行測試

          f09360b175b92c19373a9a65e79cc82d.webp

          查看 Redis 變量

          13de20535df5b6c15fe92b68b6a6f144.webp

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

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

          回顧緩存穿透:

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

          解決方案:


          • 判斷id<0



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


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

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

          所以我們如何解決呢?

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

          3470fb84af0048d529bfd46d784fc28e.webp

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

          哪怕你單獨去提取這個主鍵,但是在 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則表明這一位上的數據一定不存在,采用這種方式來處理,需要重點考慮一個事情,就是誤差率,所謂的誤差率就是指當發(fā)生哈希沖突的時候,產生的誤差。

          3d73bcacef6e2b5025a45ead4cb3bed0.webp

          小結

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

          點個 在看 你最好看



          瀏覽 50
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美操逼一级 | 丰满少妇X一88AV | 影音先锋成人片 | 亚洲中文幕在线观看 | 亚洲成人网站在线播放 |