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 在处理大规模数据基数统计时内存占用低的优势。