SpringBoot整合Redis(bitMap)实现签到功能

签到功能我们可以使用MySQL来完成,比如记录用户一年内签到的次数,签了是 1,没签是 0。

用户一次签到,就是一条记录,假如有1000万用户,平均每人每年签到次数为10次,则这张表一年的数据量为 1亿条。

每签到一次需要使用(8 + 8 + 1 + 1 + 3 + 1)共22 字节的内存,一个月则最多需要600多字节。

如果使用 key-value 来存储,那么每个用户都要记录 365 次,当用户成百上亿时,需要的存储空间将非常巨大。为了解决这个问题,Redis 提供了位图结构。

位图(bitmap)同样属于 string 数据类型。Redis 中一个字符串类型的值最多能存储 512 MB 的内容,每个字符串由多个字节组成,每个字节又由 8 个 Bit 位组成。位图结构正是使用“位”来实现存储的,它通过将比特位设置为 0 或 1来达到数据存取的目的,这大大增加了 value 存储数量,它存储上限为2^32 

位图本质上就是一个普通的字节串,也就是 bytes 数组。您可以使用getbit/setbit命令来处理这个位数组,位图的结构如下所示:
 

位图适用于一些特定的应用场景,比如用户签到次数、或者登录次数等。上图是表示一位用户 10 天内来网站的签到次数,1 代表签到,0 代表未签到,这样可以很轻松地统计出用户的活跃程度。相比于直接使用签到而言,位图中的每一条记录仅占用一个 bit 位,从而大大降低了内存空间使用率。

Redis 官方也做了一个实验,他们模拟了一个拥有 1 亿 2 千 8 百万用户的系统,然后使用 Redis 的位图来统计“日均用户数量”,最终所用时间的约为 50ms,且仅仅占用  16 MB内存。

某网站要统计一个用户一年的签到记录,若用 sring 类型存储,则需要 365 个键值对。若使用位图存储,用户签到就存 1,否则存 0。最后会生成 11010101... 这样的存储结果,其中每天的记录只占一位,一年就是 365 位,约为 46 个字节。如果只想统计用户签到的天数,那么统计 1 的个数即可。

位图操作的优势,相比于字符串而言,它不仅效率高,而且还非常的节省空间。

Redis  的位数组是自动扩展的,如果设置了某个偏移位置超出了现有的内容范围,位数组就会自动扩充


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出现的位置

核心源码:

package com.xyhsoft.zhyyk.web.test.controller;

/**
 * @Author stx
 * @Date 2023/3/13/0013 15:53
 */
public class RedisConstants {
    static final String USER_SIGN_KEY= "sign:";
}
package com.xyhsoft.zhyyk.web.test.controller;

import com.xyhsoft.framework.sso.SSOThreadHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

/**
 * @Author stx
 * @Date 2023/3/13/0013 15:48
 */
@Slf4j
@RestController
@RequestMapping("/stx")
public class stxController {

    @Autowired
    private RedisTemplate redisTemplate;

    @PostMapping("sign")
    public Boolean sign() {
        //1. 获取登录用户
        Long userId = SSOThreadHolder.getUserAndCheck().getUserId();
        //2. 获取日期
        LocalDateTime now = LocalDateTime.now();
        //3. 拼接key
        String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = RedisConstants.USER_SIGN_KEY + userId + keySuffix;
        log.info("redis key========" + key);
        //4. 获取今天是本月的第几天
        int dayOfMonth = now.getDayOfMonth();
        //5. 写入redis setbit key offset 1
        redisTemplate.opsForValue().setBit(key, dayOfMonth -1, true);
        return true;
    }

    @GetMapping("/signCount")
    public String signCount() {
        //1. 获取登录用户
        Long userId = SSOThreadHolder.getUserAndCheck().getUserId();
        //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:20:202303 GET u14 0
        List result = redisTemplate.opsForValue().bitField(
                key,
                BitFieldSubCommands.create()
                        .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        //没有任务签到结果
        if (result == null || result.isEmpty()) {
            return "没有任务签到结果";
        }
        Long num = result.get(0);
        if (num == null || num == 0) {
            return "没有任务签到结果";
        }
        //6. 循环遍历
        int count = 0;
        while (true) {
            //6.1 让这个数字与1 做与运算,得到数字的最后一个bit位 判断这个数字是否为0
            if ((num & 1) == 0) {
                //如果为0,签到结束
                break;
            } else {
                count++;
            }
            num >>>= 1;
        }
        return String.valueOf(count);
    }

}

你可能感兴趣的:(java,redis,spring)