BitMap实现用户签到、UV统计

发布于:2025-03-07 ⋅ 阅读:(14) ⋅ 点赞:(0)

1. Redis 的 BitMap 概述

在 Redis 中,BitMap 并非一种独立的数据结构,而是基于 String 类型数据结构实现的一种存储方式。由于 String 类型的最大上限是 512M,换算成 bit 位就是 2^32 个,这决定了 BitMap 可操作的最大范围。BitMap 非常适合用于处理大量的布尔值,能以极小的空间存储大量的标志位信息,常用于签到统计、活跃用户统计等场景。

2. BitMap 常用操作命令

SETBIT:用于向指定位置(offset)存入一个 0 或 1,例如 SETBIT key offset value

GETBIT:获取指定位置(offset)的 bit 值,即 GETBIT key offset

BITCOUNT:统计 BitMap 中值为 1 的 bit 位的数量,命令为 BITCOUNT key

BITFIELD:可对 BitMap 中 bit 数组的指定位置(offset)的值进行查询、修改、自增等操作。

BITFIELD_RO:获取 BitMap 中 bit 数组,并以十进制形式返回。

BITOP:能将多个 BitMap 的结果做位运算(与、或、异或),如 BITOP AND result_key key1 key2

BITPOS:查找 bit 数组中指定范围内第一个 0 或 1 出现的位置,例如 BITPOS key 1

3. 基于 BitMap 的签到功能实现

签到实现
  • 业务逻辑:获取当前登录用户的 ID 和日期,根据用户 ID 和日期拼接 Redis 的 key,确定今天是本月的第几天,然后使用 SETBIT 命令将该位置的 bit 位设置为 1 表示签到。
@Override
public Result sign() {
    Long userId = UserHolder.getUser().getId();
    LocalDateTime now = LocalDateTime.now();
    String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
    String key = USER_SIGN_KEY + userId + keySuffix;
    int dayOfMonth = now.getDayOfMonth();
    stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
    return Result.ok();
}
签到统计
  • 业务逻辑:同样先获取当前登录用户的 ID 和日期,拼接 Redis 的 key 并确定今天是本月的第几天。使用 BITFIELD 命令获取本月截止今天为止的所有签到记录,返回一个十进制数字。通过循环对该数字进行位运算,从右向左逐位检查,统计连续签到的天数,直到遇到第一个 0 为止。
@Override
public Result signCount() {
    Long userId = UserHolder.getUser().getId();
    LocalDateTime now = LocalDateTime.now();
    String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
    String key = USER_SIGN_KEY + userId + keySuffix;
    int dayOfMonth = now.getDayOfMonth();
    List<Long> result = stringRedisTemplate
           .opsForValue()
           .bitField(key, BitFieldSubCommands.create()
                   .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
                   .valueAt(0));
    if (result == null || result.isEmpty() || result.get(0) == null || result.get(0) == 0) {
        return Result.ok(0);
    }
    Long num = result.get(0);
    int count = 0;
    while (num > 0) {
        if ((num & 1) == 0) {
            break;
        } else {
            count++;
        }
        num >>>= 1;
    }
    return Result.ok(count);
}

四、BitMap总结

Redis 的 BitMap 利用 String 类型的特性,以高效的方式处理大量布尔值信息。在签到功能的实现中,通过合理使用 SETBIT 和 BITFIELD 等命令,能轻松完成签到记录和签到统计的操作,不仅节省了存储空间,还提高了操作效率。

五、UV统计

UV:全称Unique Visitor,也叫独立访客量,是指通过互联网访问、浏览这个网页的自然人。1天内同一个用户多次访问该网站,只记录1次。
PV:全称Page View,也叫页面访问量或点击量,用户每访问网站的一个页面,记录1次PV,用户多次打开页面,则记录多次PV。往往用来衡量网站的流量。

在 UV 统计场景中,由于需要统计大量独立访客,使用传统方式存储所有访客信息会占用大量内存。而 Redis 的 HyperLogLog 以其低内存占用和可接受的误差,成为了 UV 统计的理想选择,能够高效地完成大规模数据的基数统计任务。

HyperLogLog(HLL)是从 Loglog 算法派生的概率算法,用于确定非常大的集合的基数(集合中不同元素的数量),无需存储集合中的所有值。

Redis 中 HyperLogLog 的特点

        数据结构:基于 string 结构实现。

        内存占用:单个 HLL 的内存永远小于 16kb,内存占用极低。

        误差:测量结果是概率性的,存在小于 0.81%的误差,但对于 UV 统计来说,此误差可忽略不计。

对于百万级别的数据,使用 HyperLogLog 进行统计时,内存占用仅十几 kb,充分体现了 HyperLogLog 在处理大规模数据基数统计时内存占用低的优势。