本文主要是介绍Redis进阶——BitMap用户签到HyperLogLog实现UV统计,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
- 用户签到
- 实现签到功能
- 签到统计
- HyperLogLog实现UV统计
- UV和PV的概述
- 测试百万数据的统计
用户签到
BitMap功能演示
我们针对签到功能完全可以通过MySQL来完成,例如下面这张表
用户签到一次,就是一条记录,假如有1000W用户,平均每人每年签到10次,那这张表一年的数据量就有1亿条
那有没有方法能简化一点呢?我们可以使用二进制位来记录每个月的签到情况,签到记录为1,未签到记录为0
把每一个bit位对应当月的每一天,形成映射关系,用0和1标识业务状态,这种思路就成为位图(BitMap)。这样我们就能用极小的空间,来实现大量数据的表示
Redis中是利用String类型数据结构实现BitMap,因此最大上限是512M,转换为bit则是2^32个bit位
BitMap的操作命令有:
SETBIT:向指定位置(offset)存入一个0或1
GETBIT:获取指定位置(offset)的bit值
BITCOUNT:统计BitMap中值为1的bit位的数量
BITFIELD:操作(查询、修改、自增)BitMap中bit数组中的指定位置(offset)的值
BITFIELD_RO:获取BitMap中bit数组,并以十进制形式返回
BITOP:将多个BitMap的结果做位运算(与、或、异或)
BITPOS:查找bit数组中指定范围内第一个0或1出现的位置
实现签到功能
需求:实现签到接口,将当前用户当天签到信息保存到Redis中
请求方式 Post
请求路径 /user/sign
请求参数 无
返回值 无
思路:我们可以把年和月作为BitMap的key,然后保存到一个BitMap中,每次签到就把对应位上的0变成1,只要是1就说明这一天已经签到了,反之则没有签到
由于BitMap底层是基于String数据结构,因此其操作也都封装在字符串相关操作中了
在UserController中编写对应的方法
@PostMapping("/sign")
public Result sign(){return userService.sign();
}
具体实现
@Override
public Result sign() {//1. 获取当前用户Long userId = UserHolder.getUser().getId();//2. 获取日期LocalDateTime now = LocalDateTime.now();//3. 拼接keyString keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));String key = USER_SIGN_KEY + userId + keySuffix;//4. 获取今天是当月第几天(1~31)int dayOfMonth = now.getDayOfMonth();//5. 写入Redis BITSET key offset 1stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);return Result.ok();
}
通过直接使用登录用户信息,借助Redis的BitMap进行简单的签到记录。
签到统计
如何获取本月到今天为止的所有签到数据?
BITFIELD key GET u[dayOfMonth] 0
如何从后往前遍历每个bit位,获取连续签到天数
连续签到天数,就是从末尾往前数,看有多少个1
简单的位运算算法
int count = 0;
while(true) {if((num & 1) == 0)break;elsecount++;num >>>= 1;
}
return count;
需求:实现下面接口,统计当前用户截止当前时间在本月的连续签到天数
请求方式 GET
请求路径 /user/sign/count
请求参数 无
返回值 连续签到天数
代码实现:
在UserController中创建对应的方法
@GetMapping("/sign/count")
public Result signCount(){return userService.signCount();
}
在UserServiceImpl中实现方法
@Override
public Result signCount() {//1. 获取当前用户Long userId = UserHolder.getUser().getId();//2. 获取日期LocalDateTime now = LocalDateTime.now();//3. 拼接keyString keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));String key = USER_SIGN_KEY + userId + keySuffix;//4. 获取今天是当月第几天(1~31)int dayOfMonth = now.getDayOfMonth();//5. 获取截止至今日的签到记录 BITFIELD key GET uDay 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);}//6. 循环遍历int count = 0;Long num = result.get(0);while (true) {if ((num & 1) == 0) {break;} elsecount++;//数字右移,抛弃最后一位num >>>= 1;}return Result.ok(count);
}
上述通过Redis的BitMap数据结构,加上简单的与运算,计算出来到今天为止连续签到的次数,计算的逻辑是使用计数器统计从末尾往前数,看有多少个1,判断1的依据是使用与运算。
HyperLogLog实现UV统计
UV和PV的概述
UV:全称Unique Visitor,也叫独立访客量,是指通过互联网访问、浏览这个网页的自然人。1天内同一个用户多次访问该网站,只记录1次。
PV:全称Page View,也叫页面访问量或点击量,用户每访问网站的一个页面,记录1次PV,用户多次打开页面,则记录多次PV。往往用来衡量网站的流量。
通常来说PV会比UV大很多,所以衡量同一个网站的访问量,我们需要综合考虑很多因素。
UV统计在服务端做会很麻烦,因为要判断该用户是否已经统计过了,需要将统计过的信息保存,但是如果每个访问的用户都保存到Redis中,那么数据库会非常恐怖,那么该如何处理呢?
HyperLogLog(HLL)是从Loglog算法派生的概率算法,用户确定非常大的集合基数,而不需要存储其所有值,算法相关原理可以参考下面这篇文章:https://juejin.cn/post/6844903785744056333#heading-0
Redis中的HLL是基于string结构实现的,单个HLL的内存永远小于16kb,内存占用低的令人发指!作为代价,其测量结果是概率性的,有小于0.81%的误差。不过对于UV统计来说,这完全可以忽略。
常用的三个方法
PFADD key element [element...]
summary: Adds the specified elements to the specified HyperLogLogPFCOUNT key [key ...]
Return the approximated cardinality of the set(s) observed by the HyperLogLog at key(s).PFMERGE destkey sourcekey [sourcekey ...]
lnternal commands for debugging HyperLogLog values
测试百万数据的统计
使用单元测试,向HyperLogLog中添加100万条数据,看看内存占用是否真的那么低,以及统计误差如何
@Test
public void testHyperLogLog() {String[] users = new String[1000];int j = 0;for (int i = 0; i < 1000000; i++) {j = i % 1000;users[j] = "user_" + i;if (j == 999) {stringRedisTemplate.opsForHyperLogLog().add("HLL", users);}}Long count = stringRedisTemplate.opsForHyperLogLog().size("HLL");System.out.println("count = " + count);
}
执行结果:
插入100W条数据,得到的count为997593,误差率为0.002407%
去Redis图形化界面中查看占用情况为:12.3K字节
这篇关于Redis进阶——BitMap用户签到HyperLogLog实现UV统计的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!