基于Redis實現-用戶簽到
這個功能將使用到Redis中的BitMap來實現。
我們按照月來統計用戶簽到信息,簽到記錄為1,未簽到則記錄為0
把每一個bit位對應當月的每一天,形成了映射關系。用0和1標示業務狀態,這種思路稱為位圖(BitMap)。
Redis中是利用String類型數據結構實現BitMap,因此最大上限是512M,轉化為bit則是2的32次方個bit位。相比于使用數據庫字段來存儲,內存使用大大減小。
1.BitMap相關命令
- SETBIT:向指定位置(offset)存入一個0或1
- GETBIT:獲取指定位置(offset)的bit值
- BITCOUNT:統計BitMap中值為1的bit位的數量
- BITFIELD:操作(查詢、修改、自增)BitMap中bit數組中的指定位置(offset)的值
- BITFIELD_RO:獲取BitMap中bit數組,并以十進制形式返回
- BITOP:將多個BitMap的結果做位運算(與、或、異或)
- BITPOS:查找bit數組中指定范圍內第一個0或1出現的位置
語法演示:
1. SETBIT - 用戶簽到
# 用戶ID 1001 在第5天簽到(offset從0開始)
SETBIT user:sign:1001 4 1# 用戶ID 1001 在第10天簽到
SETBIT user:sign:1001 9 1
#-------------------------------------------------------------------------------------------
#2. GETBIT - 檢查某天是否簽到
# 檢查用戶ID 1001 第5天是否簽到
GETBIT user:sign:1001 4
# 返回1表示已簽到# 檢查用戶ID 1001 第6天是否簽到
GETBIT user:sign:1001 5
# 返回0表示未簽到
#-------------------------------------------------------------------------------------------
#3. BITCOUNT - 統計簽到總天數
# 統計用戶ID 1001 本月簽到總天數
BITCOUNT user:sign:1001
# 返回簽到的總天數
#-------------------------------------------------------------------------------------------
#4. BITFIELD - 批量操作簽到數據
# 獲取用戶ID 1001 前5天的簽到情況(以無符號5位整數形式返回)
BITFIELD user:sign:1001 GET u5 0# 同時設置多個簽到日
BITFIELD user:sign:1001 SET u1 15 1 SET u1 16 1
#-------------------------------------------------------------------------------------------
#5. BITFIELD_RO - 只讀方式獲取簽到數據
# 安全地獲取用戶ID 1001 前10天的簽到情況
BITFIELD_RO user:sign:1001 GET u10 0
#-------------------------------------------------------------------------------------------
#6. BITOP - 多用戶簽到情況統計
# 創建兩個用戶的簽到數據
SETBIT user:sign:1001 0 1
SETBIT user:sign:1001 1 1
SETBIT user:sign:1002 0 1# 統計哪些天數兩個用戶都簽到了(按位與操作)
BITOP AND both_sign user:sign:1001 user:sign:1002# 查看結果
GETBIT both_sign 0 # 返回1,表示第1天都簽到了
GETBIT both_sign 1 # 返回0,表示第2天不是都簽到了
#-------------------------------------------------------------------------------------------
#7. BITPOS - 查找連續簽到
# 查找用戶ID 1001 第一次簽到的位置(從第0位開始查找值為1的位)
BITPOS user:sign:1001 1# 查找用戶ID 1001 第一次未簽到的位置
BITPOS user:sign:1001 0
#-------------------------------------------------------------------------------------------
#完整簽到系統示例
# 用戶1001連續7天的簽到情況(1已簽,0未簽)
SETBIT user:sign:1001 0 1 # 第1天
SETBIT user:sign:1001 1 1 # 第2天
SETBIT user:sign:1001 2 0 # 第3天未簽
SETBIT user:sign:1001 3 1 # 第4天
SETBIT user:sign:1001 4 1 # 第5天
SETBIT user:sign:1001 5 0 # 第6天未簽
SETBIT user:sign:1001 6 1 # 第7天# 查詢簽到情況
BITCOUNT user:sign:1001 # 返回5(共簽到了5天)
BITPOS user:sign:1001 0 # 返回2(第一個未簽到的位置)
2.使用Java實現簡單的用戶簽到
注意:因為BitMap底層是基于String數據結構,因此其操作都封裝在字符串操作中。
1.實現用戶簽到
//在ServiceImpl@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result sign() {//1.獲取當前用戶Long UserId = UserHolder.getUser().getId();//2.獲取當前日期LocalDateTime now = LocalDateTime.now();//3.拼接keyString KeySuffix = now.format(DateTimeFormatter.ofPattern("yyyyMM"));String key="sign:"+UserId+":"+KeySuffix;//4.獲取今天是本月的第幾天int dayOfMonth = now.getDayOfMonth();//5.寫入redisstringRedisTemplate.opsForValue().setBit(key,dayOfMonth-1,true);return Result.ok();}
2.Java實現統計連續簽到
1.什么是;連續簽到天數?
從最后一次簽到開始向前統計,直到遇到第一次未簽到為止,計算總的簽到次數,就是連續的簽到天數。
2.如何得到本月到今天為止的所有簽到數據?
BITFIELD key GET u[dayOfMonth] 0
3.如何從后向前遍歷每個bit位?
與1做與運算,就能得到最后一個bit位。
隨后右移一位,下一個bit為就成為了最后一個bit位。
//在ServiceImpl@Autowiredprivate StringRedisTemplate stringRedisTemplate;
@Override
public Result signCount() {// 1.獲取當前登錄用戶IDLong UserId = UserHolder.getUser().getId();// 2.獲取當前日期時間(帶時區)LocalDateTime now = LocalDateTime.now();// 3.拼接Redis鍵:sign:用戶ID:年月(例如:sign:1001:202310)String KeySuffix = now.format(DateTimeFormatter.ofPattern("yyyyMM"));String key = "sign:" + UserId + ":" + KeySuffix;// 4.獲取今天是本月的第幾天(1-31)int dayOfMonth = now.getDayOfMonth();// 5.使用BITFIELD命令獲取本月簽到數據的位圖(返回無符號整數)// 格式:BITFIELD key GET u<dayOfMonth> 0// 表示從偏移量0開始,獲取dayOfMonth長度的無符號整數List<Long> longs = stringRedisTemplate.opsForValue().bitField(key,BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));// 5.1 處理空結果情況if (longs == null || longs.isEmpty()) {return Result.ok(0); // 無簽到記錄返回0}// 5.2 獲取位圖轉換后的十進制數值Long num = longs.get(0);if (num == null || num == 0) {return Result.ok(0); // 數值為0表示無簽到}// 6.通過位運算計算連續簽到天數int count = 0;while (true) {// 6.1 檢查最低位是否為1(與1做按位與運算)// 結果為0表示未簽到,1表示已簽到if ((num & 1) == 0) {break; // 遇到未簽到日終止循環} else {count++; // 簽到日計數器+1}// 6.2 無符號右移一位(相當于刪除已檢查的最低位)// 例如:1011(11) >>> 1 = 0101(5)num >>>= 1;}// 7.返回連續簽到天數return Result.ok(count);
}
關于BITFIELD參數使用解釋:
-
key
-
作用:Redis 中存儲 BitMap 的鍵名
-
示例:
"sign:1001:202310"
(用戶1001在2023年10月的簽到數據) -
底層命令:
BITFIELD key [GET type offset]
-
說明:指定要操作的 BitMap 鍵
-
-
BitFieldSubCommands.create()
-
作用:創建
BITFIELD
命令的子命令構建器 -
說明:
- Spring Data Redis 的封裝方法,用于構建復雜的
BITFIELD
操作 - 對應 Redis 原生命令中的
[GET/SET/INCR ...]
部分
- Spring Data Redis 的封裝方法,用于構建復雜的
-
-
get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
-
作用:指定要獲取的位段類型和長度
-
參數分解:
BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)
unsigned
:表示獲取無符號整數(值始終 ≥0)dayOfMonth
:整數位數長度(例如今天是10月25日,則dayOfMonth=25
)- 底層邏輯:Redis 會將從偏移量0開始的25個bit轉換為一個無符號整數
-
示例:
如果簽到數據為1011...
(二進制),unsigned(25)
會將其轉換為十進制整數(如123456
)。
-
valueAt(0)
-
作用:指定要獲取的起始位偏移量(offset)
-
參數:
0
:表示從 BitMap 的第0位開始獲取
-
關鍵點:
- 偏移量從0開始計數(與
SETBIT
/GETBIT
的偏移量規則一致) - 配合
unsigned(dayOfMonth)
表示:從第0位開始,獲取連續dayOfMonth
個bit
- 偏移量從0開始計數(與
-
-
valueAt(0)
-
作用:指定要獲取的起始位偏移量(offset)
-
參數:
0
:表示從 BitMap 的第0位開始獲取
-
關鍵點:
- 偏移量從0開始計數(與
SETBIT
/GETBIT
的偏移量規則一致) - 配合
unsigned(dayOfMonth)
表示:從第0位開始,獲取連續dayOfMonth
個bit
- 偏移量從0開始計數(與
-