Redis ---BitMap在redis中的使用
bitmap:
BitMap,即位图,使用每个位表示某种状态,适合处理整型的海量数据。本质上是哈希表的一种应用实现,原理也很简单,给定一个int整型数据,将该int整数映射到对应的位上,并将该位由0改为1。例如:
/**
* 当前用户进行签到
* @return
*/
@PostMapping("/sign")
public Result sign(){
return userService.sign();
}
实现方法:
/**
* 用户签到功能
* @return
*/
@Override
public Result sign() {
//获取当前用户id
Long userid = UserHolder.getUser().getId();
//获取日期
LocalDateTime now = LocalDateTime.now();
// 获取哪一天签到的标识
String key_sufix= now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
//拼接key
String key="sign:"+userid+key_sufix;
//在bitmap 存入bit位 存放第几位 所以需要获取今天是本月的第几天
int day = now.getDayOfMonth();
//写入当前bit位 offset偏移量 因为是bit 从0开始计数 1-31 在bit中是0-30
stringRedisTemplate.opsForValue().setBit(key,day-1,true);//true代表1 签到
return Result.ok();
}
这样我们就用:业务前缀:用户id:签到月份为key,存储了用户的签到情况
知识扩展
签到统计
bitfiled 参数说明,因为这个指令包含多个操作这里只涉及get u代表无符号 ,dayofmonth(今日时本月的第多少天) 0从第几号开始 案列
代码实现:
/**
* 实现统计连续签到天数如今天14号
* u14 无符号 查询14个bit
* @return
*/
@Override
public Result signCount() {
//1. 获取目前的是本月的第几天 以及需要用到的key
Long userid = UserHolder.getUser().getId();
LocalDateTime now = LocalDateTime.now();
String key_sufix= now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
String key="sign:"+userid+key_sufix;
int today = now.getDayOfMonth();
// 2. 获取本月为止所有的签到记录 因为bitfield包含了很多操作所以才返回集合
List<Long> rs = stringRedisTemplate.opsForValue().bitField(key,
BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(today))//对应u today 查询无符号多少位
.valueAt(0)
);
if (rs==null||rs.isEmpty())
{
// 没有任何结果
return Result.ok(0);
}
// 因为我们知识get了一个结果 所有直接get 0
Long num = rs.get(0);
if (num==null||num==0){
return Result.ok(0);
}
// 3.循环便利
int count=0;
while(true){
// 3.1 让获取的结果跟1做于运算 得到数字最后一个bit位
if( (num&1)==0){
// 如果为0 找到第一个未签到的
break;
} else{
// 3.2如果不为0 说明便利的已经签到计数器加1
++count;
}
//把数字无符号右移进行下一次便利
// 8.把数字右移一位抛弃最后一位
num>>>=1;//num=>>>1;
}
return Result.ok(count);
}
HyperLogLog
我们存入5个元素 使用pfcount统计: 如果我们存储同样的element元素呢 结果可知:不会统计重复的元素,所以HyperLogLog简直就是为uv统计量身定做的数据结构
模拟100w用户存储游览量
@Test
void testHyperLogLog() {
String[] values = new String[1000];
int j = 0;
for (int i = 0; i < 1000000; i++) {
j = i % 1000;//角标范围0-999
values[j] = "user_" + i;//模拟用户点击
if(j == 999){
//每隔1000 插入1次
// 发送到Redis
stringRedisTemplate.opsForHyperLogLog().add("hl2", values);//可变参数 也是数组
}
}
// 统计数量
Long count = stringRedisTemplate.opsForHyperLogLog().size("hl2");
System.out.println("count = " + count);
}
结果: 可以看到数据的误差百万级别只有0.25 是相当不错的
