• 【Redis】Bitmap 使用及应用场景


    前言:bitmap 占用空间小,查询效率高,在一些场景中使用 bitmap 是一个很好的选择。

    一、bitmap 相关命令

    SETBIT - 设置指定位置的比特值,可以设为 1 或 0

    例如 SETBIT key 10 1,将在 key 对应的 bitmap 中第10位设置为 1。

    GETBIT - 获取指定位置的比特值

    例如 GETBIT key 10,返回 key 对应 bitmap 的第10位的值。

    BITCOUNT - 统计比特值为 1 的数量

    例如 BITCOUNT key,返回 key 对应 bitmap 中比特值为 1 的数量。

    BITPOS - 查找第一个为指定值的比特位

    例如 BITPOS key 1,返回 key 对应 bitmap 中,第一个值为 1 的比特位的位置。

    BITFIELD - 一次对多个比特位进行操作

    例如 BITFIELD key INCRBY i 5 1,将 key 对应 bitmap 的第 i 个比特位增加 5。

    BITOP - 对两个或多个 bitmap 执行位操作(AND/OR/XOR/NOT)并存储结果到另一个 key

    例如 BITOP AND destkey key1 key2,将 key1 和 key2 做位与运算,结果存储到 destkey。

    注意: bitmap 的第几位值是从 0 开始的,类似于数组下标从 0 开始。

    二、bitmap 应用场景

    2.1、统计用户是否在线

    思路: 用户 id 作为偏移量,通过用户 id 就可以快速查到用户是否在线。

    1. 记录用户 10086 已登录
    SETBIT login_status 10086 1
    
    • 1
    1. 查询用户 10086 是否登录,返回 1 表示用户 10086 已登录。
    GETBIT login_status 10086
    
    • 1
    1. 用户 10086 退出,将用户设置为离线。
    SETBIT login_status 10086 0
    
    • 1

    2.2、用户每个月的签到情况

    思路: 每个用户每天的签到用 1 个 bit 位表示,一年的签到仅需要 365 个 bit 为。一个月最多只有 31 天,只需要 31 个 bit 位即可。

    比如统计用户 10086 在 2023 年 9 月份的打卡记录。

    1. 记录用户 10086 用户在 2023 年 9 月 3 日的签到记录。
    SETBIT uid:sign:10086:202309 2 1
    
    • 1
    1. 获取用户 10086 在 2023 年 9 月 3 日是否打卡。
    GETBIT uid:sign:10086:202309 2
    
    • 1
    1. 统计用户 10086 在 2023 年 9 月的打卡次数。
    BITCOUNT uid:sign:10086:202309
    
    • 1
    1. 获取用户在 2023 年 9 月首次打卡的日期。
    BITPOS uid:sign:10086:202309 1
    
    • 1

    2.3、用户当月连续签到的天数

    1. 用户签到相关代码
      public Result sign() {
          //获取当前登陆用户
          Long id = UserHolder.getUser().getId();
          //获取日期
          LocalDateTime now = LocalDateTime.now();
          //拼接key
          String yyyyMM = now.format(DateTimeFormatter.ofPattern("yyyy:MM:"));
          String key = USER_SIGN_KEY +yyyyMM+ id;
          //获取今天是本月的第几天
          int dayOfMonth = now.getDayOfMonth();
          //写了redis
          stringRedisTemplate.opsForValue().setBit(key,dayOfMonth-1,true);
          return Result.ok();
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    1. 统计用户当月连续签到的天数

    主要逻辑是拿到 bitmap 对应的二进制数据,从后往前遍历连续 1 的个数,连续几个就是当月连续签到几天。

    @Override
    public Result signCount() {
        //获取当前登陆用户
        Long id = UserHolder.getUser().getId();
        //获取日期
        LocalDateTime now = LocalDateTime.now();
        //拼接key
        String yyyyMM = now.format(DateTimeFormatter.ofPattern("yyyy:MM:"));
        String key = USER_SIGN_KEY + yyyyMM + id;
        //获取今天是本月的第几天
        int dayOfMonth = now.getDayOfMonth();
    
        //转二进制字符串
        String binaryString = getBitsBefore(key, dayOfMonth);
    
        //计算连续签到天数
        int count = 0;
        for (int i = binaryString.length() - 1; i >= 0; i--) {
            if (binaryString.charAt(i) == '1') {
                count++;
            } else {
                break;
            }
        }
        //返回
        return Result.ok(count);
    }
    
    /**
     * 获取指定 key 的二进制字符串
     */
    public String getBitsBefore(String key, int index) {
        ValueOperations<String, String> ops = this.stringRedisTemplate.opsForValue();
        String value = ops.get(key);
        if (StringUtils.isBlank(value)) {
            return "";
        }
    
        //字符串转二进制字符串
        String binaryString = new BigInteger(value.getBytes()).toString(2);
    
        if(index > 0 && index < binaryString.length()) {
            return binaryString.substring(0, index);
        } else {
            return binaryString;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    2.4、连续签到用户总数

    思路: 将某一个具体的天作为 key(day:20230907),用户 id 作为偏移量,使用 BITOP 命令合并多天的 bitmap。

    image-20230907201756284

    1. 设置用户A、B、C 在上图中的日期签到(假设用户 id 为 1、2、3)
    setbit sign:20230901 0 1
    setbit sign:20230901 1 1
    setbit sign:20230901 2 1
    setbit sign:20230902 0 1
    setbit sign:20230902 1 1
    setbit sign:20230903 1 1
    setbit sign:20230903 2 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 与操作,获取一个新的 bitmap key
    bitop and result sign:20230901 sign:20230902 sign:20230903
    
    • 1
    1. 计算连续签到的人数
    bitcount result
    
    • 1

    2.5、优惠券每人限领一张

    思路:使用优惠券编号作为 bitmap key,用户 id 作为 offset。发优惠券的时候,先获取 bitmap 中用户是否领过优惠券。因为每人限领一张,领过的人直接返回。

    1. 设置用户 100、用户 101 领取过优惠券 a。
    setbit coupon:a 100 1
    setbit coupon:a 101 1
    
    • 1
    • 2
    1. 查看用户 100 是否领过优惠券 a,返回 1 则代表用户 100 领过优惠券。
    getbit coupon:a 100
    
    • 1
    1. SpringBoot 项目,一人一单实战代码
    /**
     * 通过 redis 的 bitmap 判断是否是一人一单
     */
    private boolean isOnePersonOneOrderByRedis(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
        String userVoucherKey = "user_voucher:" + voucherId;
    
        //获取该用户在Bitmaps中的状态
        Boolean isOrder = stringRedisTemplate.opsForValue().getBit(userVoucherKey, userId);
        if (isOrder == null || !isOrder) {
            //如果该用户没有购买过该商品,那么在Bitmaps中设置该用户的状态为已购买
            stringRedisTemplate.opsForValue().setBit(userVoucherKey, userId, true);
            return true;
        } else {
            //如果该用户已经购买过该商品,那么返回false
            return false;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2.6、统计网站活跃用户

    思路: 使用日期作为 key,然后用户 id 为 offset。
    假如 20230901 活跃用户情况是: [1,0,1,1,0]。20230902 活跃用户情况是 :[ 1,1,0,1,0 ]

    1. 统计连续两天活跃的用户总数
    bitop and dest1 20230901 20230902 
    bitcount dest1
    
    • 1
    • 2
    1. 统计 20230901 ~ 20230902 活跃过的用户
    bitop or dest2 20201009 20201010 
    bitcount dest2
    
    • 1
    • 2

    三、参考文档

    四、最后

    我是 xiucai,一位后端开发工程师。

    如果你对我感兴趣,请移步我的个人博客,进一步了解。

    - 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
    - 本文首发于个人博客,未经许可禁止转载💌

  • 相关阅读:
    java项目技术方案——书写示例
    Linux系统部署若依前后端分离项目(手把手教学)
    2022游戏出海实用发行策略
    element UI表格控制列行合并
    iPhone 14参数曝光,mini版本被砍掉;谷歌组建Web3团队;可在浏览器中运行Python应用的框架发布|极客头条
    ER 图与数据字典 – 哪个更适合记录数据模型
    洞见商业新机,云原生数据库GaussDB让企业决策更科学
    Uniapp零基础开发学习笔记(11)-安装扩展组件uni-ui/uView及微信小程序开发环境
    用Promise发起请求,失败后再次请求,几次后不再执行
    KubeEdge 边缘端架构设计
  • 原文地址:https://blog.csdn.net/qq_40258748/article/details/132745737