当前位置: 首页 > article >正文

Redis-BitMap实现签到功能

文章目录

      • 为什么需要 bitmap
      • BitMap 的操作
    • * 1. 用户签到 * 2. 检查用户是否签到 * 3. 获取当月签到次数 * 4. 获取当月连续签到次数
    • 功能实现
      • 1. 用户签到实现
      • 2. 检查用户是否签到
      • 3. 获取当月签到次数
      • 4. 获取当月连续签到次数

为什么需要 bitmap

痛点:在 项目开发中经常会用到签到的功能,如果用户有量很少,签到信息直接存储在数据库中还是很合理的,但是随着用户量的增加,100 万用户,就算一个用户一年签到 20 次就已经 2000 万数据了,增长的非常快,并且查询的时候效率也低。

为了解决这个问题可以用 Bitmap 的数据结构。

用 1 来表示签到,用 0 来表示没有签到。

对于一个月来说,从第一个开始签到。一个月最多 31 天,用 31bit 来表示用户签到。一个月只需要两个字节。

一个用户一个月签到的信息也就只有一条,这样大大减少了数据库的压力。

布隆过滤器底层也是 bitmap。 在 redis 中使用 string 来实现 bitmap。最大存储上线 512,最大时 2 的 32 次方比特位。

BitMap 的操作

功能分析:

对于用户签到数据,如果直接采用数据库存储,当出现高并发访问时,对数据库压力会很大,例如双十一签到活动。这时候应该采用缓存,以减轻数据库的压力,Redis是高性能的内存数据库,适用于这样的场景。

另外如果系统的用户量很多,每次签到都插入一条记录,那么数据库表增长就很快。如果系统两百万用户,一个月平均签到十次,那么就是两千万数据量,mysql 一张表大概也就是两千万。

签到出来的数据

setbit  bm1  0 1  在第0个位置赋值为1

这个图中可以看出来是第 1、2、7 天签到的。

判断第二天是否签到,getbit 如果等于 1 说明用户在这一天完成了签到功能;

get bm1 1  //判断是否等于1

bitpos 判断开始签到的位置

实现签到的代码 存储在 redis 中的格式

sign:userID:202401,这样每一个用户都是一个 key.

@Override
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 = USER_SIGN_KEY + userId + keySuffix;
    // 4.获取今天是本月的第几天
    int dayOfMonth = now.getDayOfMonth(); //当前天数
    // 5.写入Redis SETBIT key offset 1
    stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
    return Result.ok();
}

连续签到次数


* 1. 用户签到 * 2. 检查用户是否签到 * 3. 获取当月签到次数 * 4. 获取当月连续签到次数

创建用户的签到数据库表:

功能实现

1. 用户签到实现

@Override
public void signIn() {
    // 获取当前登录的用户Id
    Long userId = SecurityUtils.getUserId();
    //获取日期
    LocalDateTime now = LocalDateTime.now();
    // 获取当前的月份
    String keySuffix=now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
    //拼接
    String   key= SignRedisConstant.SIGN_KEY + userId+keySuffix;
    int dayOfMonth = now.getDayOfMonth();
    redisTemplate.opsForValue().setBit(key,dayOfMonth-1,true);
}

还需要做异步任务,将数据同步到数据库中去。

待做:用定时任务或者异步操作更新数据库中的签到信息。

2. 检查用户是否签到

将获取 key 的方法封装起来

public class RedisUtil {

    public static String getSign() {
        Long userId = SecurityUtils.getUserId();
        LocalDateTime now = LocalDateTime.now();
        String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        return SignRedisConstant.SIGN_KEY + userId + keySuffix;
    }
    public static LocalDateTime getNow() {
        return  LocalDateTime.now();
    }
}
@Override
public boolean isSignIn() {
    String keySuffix = RedisUtil.getSign();
    int dayOfMonth = RedisUtil.getNow().getDayOfMonth();
    return redisTemplate.opsForValue().getBit(keySuffix, dayOfMonth - 1);
}

3. 获取当月签到次数

@Override
public int getCurrentMonth() {
    String keySuffix = RedisUtil.getSign();
    String str = (String) redisTemplate.opsForValue().get(keySuffix);
    int count = 0;
    for (int i = 0; i < str.length(); i++) {
        if (str.charAt(i) == '1') {
            count++;
        }
    }
    return count;
}

4. 获取当月连续签到次数

从最后一个开始一直找到第一个为 0 的地方,和 1 进行与操作就是拿到最后一个数字的。

@Override
public int getContinuousSignInCount() {
    String keySuffix = RedisUtil.getSign();
    int dayOfMonth = RedisUtil.getNow().getDayOfMonth();
    List<Long> result = redisTemplate.opsForValue().bitField(keySuffix, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
    if (result == null || result.size() == 0) {
        return 0;
    }
    Long num = result.get(0);
    if (num == null || num == 0) {
        return 0;
    }
    int count = 0;
    //主要是这一段的逻辑
    while (true) {
        if ((num & 1) == 0) {
            break;
        } else {
            count++;
        }
        num = num >> 1;
    }
    return count;
}


http://www.kler.cn/a/532900.html

相关文章:

  • 系统学习算法:专题九 穷举vs暴搜vs深搜vs回溯vs剪枝
  • 算法与数据结构(括号匹配问题)
  • 【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.25 视觉风暴:NumPy驱动数据可视化
  • 自定义数据集 使用scikit-learn中SVM的包实现SVM分类
  • pytorch实现长短期记忆网络 (LSTM)
  • Qt网络相关
  • 2024美团春招硬件开发笔试真题及答案解析
  • JVM 四虚拟机栈
  • 暴力破解与验证码安全
  • LabVIEW涡轮诊断系统
  • 【深度学习】多层感知机的简洁实现
  • 渗透测试之文件包含漏洞 超详细的文件包含漏洞文章
  • 3、参数化测试
  • 【Redis实战】Chapter01-投票后端
  • 『 C++ 』中理解回调类型在 C++ 中的使用方式。
  • Android学习20 -- 手搓App2(Gradle)
  • leetcode 1482. 制作 m 束花所需的最少天数
  • git error: invalid path
  • Redis - String相关命令
  • UE编辑器工具
  • 【自学笔记】Git的重点知识点-持续更新
  • LeetCode:392.判断子序列
  • 接口游标分页
  • 本系统旨在为用户提供一个灵活且可扩展的信息安全管理解决方案,通过插件化的开发模式,使得信息安全的维护更加高效、便捷。
  • 云原生详解:构建未来应用的架构革命
  • 996引擎-怪物:Lua 刷怪+清怪+自动拾取