目录
BitMap
使用场景
1. 用户签到系统
2. 用户行为标记
3. 布隆过滤器(Bloom Filter)
BitMap介绍
Redis中的使用
Redis功能示例
添加:
获取:
批量获取:
java中实现
统计本月连续签到次数
UV统计
UV 统计的核心需求
使用 HyperLogLog
UV 统计的常见场景
场景 1:每日 UV 统计
场景 2:月度 UV 统计
BitMap
使用场景
在开发中,Bitmap
经常被用于以下场景:
1. 用户签到系统
场景描述:
用户每天签到一次,系统需要记录用户每月的签到情况,并支持快速查询连续签到天数、总签到天数等。
实现方式:
-
使用一个
Bitmap
,每一位代表一天(1表示签到,0表示未签到)。 -
例如,用户ID为1的用户在2023年10月的签到记录可以用一个31位的
Bitmap
表示。
优点:
-
存储空间极小:一个月的签到记录只需要4字节(32位)。
-
查询效率高:可以通过位运算快速计算连续签到天数、总签到天数等。
2. 用户行为标记
场景描述:
系统需要标记用户是否完成了某些行为(例如是否阅读了某篇文章、是否参与了某个活动等)。
实现方式:
-
使用一个
Bitmap
,每一位代表一个行为(1表示完成,0表示未完成)。 -
例如,用户ID为1的用户完成了行为A、B、D,可以用
0b1101
表示。
优点:
-
节省存储空间:一个用户的所有行为标记可以用一个整数表示。
-
支持快速查询:通过位运算可以快速判断用户是否完成了某个行为。
3. 布隆过滤器(Bloom Filter)
场景描述:
布隆过滤器是一种概率型数据结构,用于快速判断某个元素是否存在于一个集合中(可能存在误判,但不会漏判)。
实现方式:
-
使用一个
Bitmap
作为布隆过滤器的底层存储结构。 -
通过多个哈希函数将元素映射到
Bitmap
的不同位置,并将这些位置标记为1。
优点:
-
空间效率极高:适合海量数据的去重和查询。
-
查询速度快:时间复杂度为 O(1)。
BitMap介绍
如果是使用表来储存,需要耗费大量的内存,数据库压力山大
因此我们换一种方式来存储,一个月最多有31天,因此,如果某一天签到了,那么对应的位为1,没有则为0。这种方式只需要31bit,也就是8字节,大大节省了空间。
Redis中的使用
Redis功能示例
添加:
储存为11100111
获取:
批量获取:
u2中的u表示储存的为无符号,2表示只截取两个比特位,截取结果为11,转化为十进制就是3
java中实现
public Result sign() {// 获取登录用户Long userId = UserHolder.getUser().getId();// 获取日期LocalDateTime now = LocalDateTime.now();// 拼接用户和日期变成keyString keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
// String key = "sign:"+userId+keySuffix;String key = USER_SIGN_KEY+userId+keySuffix;// 获取今天是本月的第几天int dayOfMonth = now.getDayOfMonth();// 写入Redis setbit key offset 1stringRedisTemplate.opsForValue().setBit(key,dayOfMonth-1,true); // 注意这里需要减一因为在储存中字节是从0开始的return Result.ok();}
统计本月连续签到次数
@Overridepublic Result signCount() {// 获取登录用户Long userId = UserHolder.getUser().getId();// 获取日期LocalDateTime now = LocalDateTime.now();// 拼接用户和日期变成keyString keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
// String key = "sign:"+userId+keySuffix;String key = USER_SIGN_KEY+userId+keySuffix;// 获取今天是本月的第几天int dayOfMonth = now.getDayOfMonth();//获取本月为止的所有的签到记录,返回的是一个十进制的数字 BITFIELD key GET udayOfMonth 0List<Long> result = stringRedisTemplate.opsForValue().bitField(key,BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)) // 子命令.valueAt(0));if(result == null || result.isEmpty()){return Result.ok(0);}// 为什么需要 get(0)?get(0) 是从 List<Long> 中获取第一个元素。// stringRedisTemplate.opsForValue().bitField(...) 返回的是一个 List<Long>,// 即使你只请求了一个值,它也会以列表的形式返回。// 因此,result.get(0) 获取的是这个列表中的第一个元素,也就是你请求的签到记录的值。Long num = result.get(0);if(num == null || num == 0){return Result.ok(0);}// 遍历循环int cnt = 0;while(cnt < dayOfMonth){if ((num & 1) == 0) {break;}cnt++;// 把数字右移一位,抛弃最后一个bit位,继续下一个bit位num >>>=1;}return Result.ok(cnt);}
UV统计
在 Redis 中,UV(Unique Visitor)统计 是指统计某个时间段内访问某个资源的独立用户数量。UV 统计是许多应用场景(如网站访问量统计、广告点击统计等)中的核心需求。Redis 提供了多种数据结构和方法来实现高效的 UV 统计。
以下是 Redis 中 UV 统计的相关知识点介绍:
UV 统计的核心需求
-
去重:同一个用户在同一时间段内的多次访问只算作一次。
-
高效存储:需要支持海量用户的统计。
-
快速查询:能够快速获取某个时间段内的 UV 数据。
使用 HyperLogLog
原理:
-
HyperLogLog 是一种概率算法,用于估算大量数据的基数(去重后的数量)。
-
它通过极小的存储空间(每个 HyperLogLog 键只需要 12 KB)来统计 UV。
命令:
-
PFADD key user_id
:将用户 ID 添加到 HyperLogLog 中。 -
PFCOUNT key
:获取 UV 的估算值。
优点:
-
存储空间极小,适合海量用户的 UV 统计。
-
查询速度快。
缺点:
-
结果是估算值,存在一定的误差(标准误差约为 0.81%)
UV 统计的常见场景
场景 1:每日 UV 统计
需求:
-
统计每天的独立访问用户数。
实现:
-
使用
HyperLogLog
,每天创建一个新的键(例如uv:2023-10-01
),将当天的用户 ID 添加到键中。 -
每天结束时,使用
PFCOUNT
获取当天的 UV 值。
场景 2:月度 UV 统计
需求:
-
统计每月的独立访问用户数。
实现:
-
使用
HyperLogLog
,将整个月的用户 ID 添加到同一个键中(例如uv:2023-10
)。 -
每月结束时,使用
PFCOUNT
获取当月的 UV 值。