一、用户签到
1.1 BitMap用法
我们按月来统计用户签到信息,签到记录为1,未签到记录为0
把每一个bit位对应当月的每一天,形成了映射关系。用0和1表示业务状态,这种思路就称为位。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出现的位置;
1.2 签到功能
实现签到接口,将当前用户当天签到信息保存到Redis中
public Result sign() {
// 1. 获取当前登录用户
Long userId = UserHolder.getUser().getId();
// 2. 获取日期,拼接key
LocalDateTime now = LocalDateTime.now();
String key = USER_SIGN_KEY +
userId +
now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
// 3. 获取今天是本月的第几天
int dayOfMonth = now.getDayOfMonth();
// 4. 写入 redis SETBIT key offset 1
stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
return Result.ok();
}
1.3 签到统计
问题1:什么叫做连续签到天数?
从最后一次签到开始向前统计,知道遇到第一次未签到为止,计算总的签到次数,就是连续签到次数
问题2:如何得到本月到今天为止的所有签到数据?
问题3:如何从后向前遍历每个bit位?
与1做与运算,就能得到最后一个bit位。
随后右移1位,下一个bit位就成为了最后一个bit位。
@Override
public Result signCount() {
// 1. 获取当前登录用户
Long userId = UserHolder.getUser().getId();
// 2. 获取日期,拼接key
LocalDateTime now = LocalDateTime.now();
String key = USER_SIGN_KEY +
userId +
now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
// 3. 获取今天是本月的第几天
int dayOfMonth = now.getDayOfMonth();
// 4. 获取本月截止今天为止的所有的签到记录,返回的是一个十进制的数字 BITFIELD sign:5:202203 GET u14 0
List<Long> results = stringRedisTemplate.opsForValue().bitField(
key,
BitFieldSubCommands.create()
.get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
.valueAt(0)
);
if (Objects.isNull(results) || CollectionUtils.isEmpty(results)) {
return Result.ok(0);
}
Long num = results.get(0);
if (Objects.isNull(num) || num == 0) {
return Result.ok(0);
}
// 5. 循环遍历
int count = 0;
while (true) {
// 6.1. 让这个数字与1做与运算,得到数字的最后一个bit位 // 判断这个bit位是否为0
if ((num & 1) == 0) {
// 如果为0,说明未签到,结束
break;
} else {
// 如果不为0,说明已签到,计数器+1
count++;
}
// 把数字右移一位,抛弃最后一个bit位,继续下一个bit位
num >>>= 1;
}
return Result.ok(count);
}
二、UV统计
2.1 HyperLogLog用法
首先,我们搞懂两个概念:
- UV: 全称Unique Visitor,也叫独立访客量,是指通过互联网访问、浏览这个网页的自然人。1天内同一个用户多次访问该网站,只记录1次。
- PV: 全称Page View,也叫页面访问量或点击量,用户每访问网站的一个页面,记录1此PV,用户多次打开页面,则记录多次PV。往往用来衡量网站的流量。
UV统计在服务端做会比较麻烦,因为要判断该用户是否已经统计过了,需要将统计过的用户信息保存。但是每个访问的用户都保存到Redis中,数据量会非常恐怖
2.2 实现UV统计
我们直接利用单元测试,向HyperLogLog中添加100万条数据,看看内存占用和统计效果如何:
@Test
void testHyperLogLog() {
String[] values = new String[1000];
int j = 0;
for (int i = 0; i < 1000000; i++) {
j = i % 1000;
values[j] = "user_" + i;
if (j == 999) {
// 发送到Redis
stringRedisTemplate.opsForHyperLogLog().add("hl2", values);
}
}
// 统计数量
Long count = stringRedisTemplate.opsForHyperLogLog().size("hl2");
System.out.println("count = " + count);
}
运行结果:
count = 997593
查看内存(命令:info memory)
- 插入数据之前
- 插入数据之后
- 计算
(1396913 - 1366928)/ 1024
2.3 总结
- HyperLogLog的作用:
- 做海量数据的统计工作
- HyperLogLog的优点:
- 内存占用极低;
- 性能非常好;
- HyperLogLog的缺点:
- 有一定的误差