引言
在各個項目中,我們都可能需要用到簽到和 統(tǒng)計功能。簽到后會給用戶一些禮品以此來吸引用戶持續(xù)在該平臺進行活躍。
簽到功能,我們可以通過Redis中的 BitMap功能來實現(xiàn)
一、Redis BitMap 基本用法
BitMap 基本語法、指令
簽到功能我們可以使用MySQL來完成,比如下表:
用戶一次簽到,就是一條記錄,假如有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 位圖功能來實現(xiàn),每次簽到與未簽到用0 或1 來標識 ,一次存31個數(shù)字,只用了2字節(jié) 這樣我們就用極小的空間實現(xiàn)了簽到功能
BitMap 的操作指令:
SETBIT:向指定位置(offset)存入一個0或1
GETBIT:獲取指定位置(offset)的bit值
BITCOUNT:統(tǒng)計BitMap中值為1的bit位的數(shù)量
BITFIELD:操作(查詢、修改、自增)BitMap中bit數(shù)組中的指定位置(offset)的值
BITFIELD_RO:獲取BitMap中bit數(shù)組,并以十進制形式返回
BITOP:將多個BitMap的結(jié)果做位運算(與 、或、異或)
BITPOS:查找bit數(shù)組中指定范圍內(nèi)第一個0或1出現(xiàn)的位置
使用 BitMap 完成功能實現(xiàn)
服務(wù)器Redis版本采用 6.2
進入redis查詢 SETBIT 命令
新增key 進行存儲
查詢 GETBIT命令
查看指定坐標的簽到狀態(tài)
查詢 BITFIELD
無符號查詢
BITPOS 查詢1 和 0 第一次出現(xiàn)的坐標
二、SpringBoot 整合 Redis 實現(xiàn)簽到 功能
需求介紹
采用BitMap實現(xiàn)簽到功能
實現(xiàn)簽到接口,將當前用戶當天簽到信息保存到Redis中
思路分析:
我們可以把 年和月 作為BitMap的key,然后保存到一個BitMap中,每次簽到就到對應(yīng)的位上把數(shù)字從0 變?yōu)?,只要是1,就代表是這一天簽到了,反之咋沒有簽到。
實現(xiàn)簽到接口,將當前用戶當天簽到信息保存至Redis中
提示:因為BitMap 底層是基于String數(shù)據(jù)結(jié)構(gòu),因此其操作都封裝在字符串操作中了。
核心源碼
UserController
@PostMapping("sign") publicResultsign(){ returnuserService.sign(); }
UserServiceImpl
publicResultsign(){ //1.獲取登錄用戶 LonguserId=UserHolder.getUser().getId(); //2.獲取日期 LocalDateTimenow=LocalDateTime.now(); //3.拼接key StringkeySuffix=now.format(DateTimeFormatter.ofPattern(":yyyyMM")); Stringkey=RedisConstants.USER_SIGN_KEY+userId+keySuffix; //4.獲取今天是本月的第幾天 intdayOfMonth=now.getDayOfMonth(); //5.寫入redissetbitkeyoffset1 stringRedisTemplate.opsForValue().setBit(key,dayOfMonth-1,true); returnResult.ok(); }
接口進行測試
ApiFox進行測試
查看Redis 數(shù)據(jù)
三、SpringBoot 整合Redis 實現(xiàn) 簽到統(tǒng)計功能
問題一:什么叫做連續(xù)簽到天數(shù)?
從最后一次簽到開始向前統(tǒng)計,直到遇到第一次未簽到為止,計算總的簽到次數(shù),就是連續(xù)簽到天數(shù)。
邏輯分析:
獲得當前這個月的最后一次簽到數(shù)據(jù),定義一個計數(shù)器,然后不停的向前統(tǒng)計,直到獲得第一個非0的數(shù)字即可,每得到一個非0的數(shù)字計數(shù)器+1,直到遍歷完所有的數(shù)據(jù),就可以獲得當前月的簽到總天數(shù)了
問題二:如何得到本月到今天為止的所有簽到數(shù)據(jù)?
BITFIELDkeyGETu[dayOfMonth]0
假設(shè)今天是7號,那么我們就可以從當前月的第一天開始,獲得到當前這一天的位數(shù),是7號,那么就是7位,去拿這段時間的數(shù)據(jù),就能拿到所有的數(shù)據(jù)了,那么這7天里邊簽到了多少次呢?統(tǒng)計有多少個1即可。
問題三:如何從后向前遍歷每個Bit位?
注意:bitMap返回的數(shù)據(jù)是10進制,哪假如說返回一個數(shù)字8,那么我哪兒知道到底哪些是0,哪些是1呢?
我們只需要讓得到的10進制數(shù)字和1做與運算就可以了,因為1只有遇見1 才是1,其他數(shù)字都是0 ,我們把簽到結(jié)果和1進行與操作,每與一次,就把簽到結(jié)果向右移動一位,依次內(nèi)推,我們就能完成逐個遍歷的效果了。
需求:
實現(xiàn)以下接口,統(tǒng)計當前截至當前時間在本月的連續(xù)天數(shù)
有用戶有時間我們就可以組織出對應(yīng)的key,此時就能找到這個用戶截止這天的所有簽到記錄,再根據(jù)這套算法,就能統(tǒng)計出來他連續(xù)簽到的次數(shù)了
核心源碼
UserController
@GetMapping("/signCount") publicResultsignCount(){ returnuserService.signCount(); }
UserServiceImpl
publicResultsignCount(){ //1.獲取登錄用戶 LonguserId=UserHolder.getUser().getId(); //2.獲取日期 LocalDateTimenow=LocalDateTime.now(); //3.拼接key StringkeySuffix=now.format(DateTimeFormatter.ofPattern(":yyyyMM")); Stringkey=RedisConstants.USER_SIGN_KEY+userId+keySuffix; //4.獲取今天是本月的第幾天 intdayOfMonth=now.getDayOfMonth(); //5.獲取本月截至今天為止的所有的簽到記錄,返回的是一個十進制的數(shù)字BITFIELDsign202301GETu30 Listresult=stringRedisTemplate.opsForValue().bitField( key, BitFieldSubCommands.create() .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)); //沒有任務(wù)簽到結(jié)果 if(result==null||result.isEmpty()){ returnResult.ok(0); } Longnum=result.get(0); if(num==null||num==0){ returnResult.ok(0); } //6.循環(huán)遍歷 intcount=0; while(true){ //6.1讓這個數(shù)字與1做與運算,得到數(shù)字的最后一個bit位判斷這個數(shù)字是否為0 if((num&1)==0){ //如果為0,簽到結(jié)束 break; }else{ count++; } num>>>=1; } returnResult.ok(count); }
進行測試
查看 Redis 變量
從今天開始,往前查詢 連續(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集合中,當用戶過來訪問的時候,我們直接去判斷l(xiāng)ist中是否包含當前的要查詢的數(shù)據(jù),如果說用戶要查詢的id數(shù)據(jù)并不在list集合中,則直接返回,如果list中包含對應(yīng)查詢的id數(shù)據(jù),則說明不是一次緩存穿透數(shù)據(jù),則直接放行。
現(xiàn)在的問題是這個主鍵其實并沒有那么短,而是很長的一個 主鍵
哪怕你單獨去提取這個主鍵,但是在 11年左右,淘寶的商品總量就已經(jīng)超過10億個
所以如果采用以上方案,這個list也會很大,所以我們可以使用bitmap來減少list的存儲空間
我們可以把list數(shù)據(jù)抽象成一個非常大的bitmap,我們不再使用list,而是將db中的id數(shù)據(jù)利用哈希思想,比如:
id 求余bitmap長度 :id % bitmap.size = 算出當前這個id對應(yīng)應(yīng)該落在bitmap的哪個索引上,然后將這個值從0變成1,然后當用戶來查詢數(shù)據(jù)時,此時已經(jīng)沒有了list,讓用戶用他查詢的id去用相同的哈希算法, 算出來當前這個id應(yīng)當落在bitmap的哪一位,然后判斷這一位是0,還是1,如果是0則表明這一位上的數(shù)據(jù)一定不存在,采用這種方式來處理,需要重點考慮一個事情,就是誤差率,所謂的誤差率就是指當發(fā)生哈希沖突的時候,產(chǎn)生的誤差。
圖片
小結(jié)
以上就是對 微服務(wù) Spring Boot 整合 Redis BitMap 實現(xiàn) 簽到與統(tǒng)計 的簡單介紹,簽到功能是很常用的,在項目中,是一個不錯的亮點,統(tǒng)計功能也是各大系統(tǒng)中比較重要的功能,簽到完成后,去統(tǒng)計本月的連續(xù) 簽到記錄,來給予獎勵,可大大增加用戶對系統(tǒng)的活躍度 技術(shù)改變世界!!!
審核編輯:劉清
-
MySQL
+關(guān)注
關(guān)注
1文章
802瀏覽量
26444 -
邏輯分析
+關(guān)注
關(guān)注
0文章
13瀏覽量
7979 -
Redis
+關(guān)注
關(guān)注
0文章
371瀏覽量
10846
原文標題:SpringBoot+Redis BitMap 實現(xiàn)簽到與統(tǒng)計功能
文章出處:【微信號:DBDevs,微信公眾號:數(shù)據(jù)分析與開發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論