Redis的BitMap
前言
- 通过一个bit位来表示某个元素对应的值或者状态,其中的key就是对应元素本身
- 版本:redis 2.2.0
- 新增 setbit,getbit,bitcount几个bitmap相关命令
setbit命令说明
指令 setbit key offset value
- 复杂度o(1)
- 设置或者清空key的取决于value(字符串)在offset处的bit值(只能只0或者1)
- 字符串会进行伸展(grown)以确保它可以将value 保存在指定的偏移量上。当字符串值进行伸展时,空白位置以 0 填充。
- offset参数必须大于或等于0,小于2^32
- 返回值 字符串值指定偏移量上原来存储的位(bit)
getbit命令说明
指令getbit key offest
- 对
key
所储存的字符串值,获取指定偏移量上的位(bit)。 - 当
offset
比字符串值的长度大,或者key
不存在时,返回0
- 返回值 字符串指定偏移量上的位
bitcount命令说明
指令bitcount key
、bitcount key start end
- 计算给定字符串中,被设置为1的比特位的数量
- 一般情况下,给定的整个字符串都会被进行计数,通过指定额外的
start
或end
参数,可以让计数只在特定的位上进行 start
和end
参数的设置和GETRANGE
命令类似,都可以使用负数值: 比如-1
表示最后一个字节,-2
表示倒数第二个字节,以此类推- 不存在的
key
被当成是空字符串来处理,因此对一个不存在的key
进行BITCOUNT
操作,结果为 0。 - 返回值 被设置为1的位的数量
使用场景
1. 用户签到
Jedis redis = new Jedis("192.168.31.89",6379,100000);
//用户uid
String uid = "1";
String cacheKey = "sign_"+Integer.valueOf(uid);
//记录有uid的key
// $cacheKey = sprintf("sign_%d", $uid);
//开始有签到功能的日期
String startDate = "2017-01-01";
//今天的日期
String todayDate = "2017-01-21";
//计算offset(时间戳)
long startTime = dateParase(startDate,"yyyy-MM-dd").getTime();
long todayTime = dateParase(todayDate,"yyyy-MM-dd").getTime();
long offset = (long) Math.floor((todayTime - startTime) / 86400);
System.out.println("今天是第"+offset+"天");
//签到
//一年一个用户会占用多少空间呢?大约365/8=45.625个字节,好小,有木有被惊呆?
redis.setbit(cacheKey,offset,"1");
//查询签到情况
boolean bitStatus = redis.getbit(cacheKey, offset);
//判断是否已经签到
//计算总签到次数
long qdCount = redis.bitcount(cacheKey);
2. 统计活跃用户
使用时间作为cacheKey,然后用户ID为offset,如果当日活跃过就设置为1 那么我该如果计算某几天/月/年的活跃用户呢(暂且约定,统计时间内只有有一天在线就称为活跃)
一个新的命令 BITOP operation destkey key [key ...]
说明 :
- 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上
- BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数
Map<String,List<Integer>>dateActiveuser = new HashMap<>();
Jedis redis = new Jedis("192.168.31.89",6379,100000);
Integer[] temp01 = {1,2,3,4,5,6,7,8,9,10};
List<Integer>temp01List = new ArrayList<>();
Collections.addAll(temp01List,temp01);
dateActiveuser.put("2017-01-10",temp01List);
Integer[] temp02 = {1,2,3,4,5,6,7,8};
List<Integer>temp02List = new ArrayList<>();
Collections.addAll(temp02List,temp02);
dateActiveuser.put("2017-01-11",temp02List);
Integer[] temp03 = {1,2,3,4,5,6};
List<Integer>temp03List = new ArrayList<>();
Collections.addAll(temp03List,temp03);
dateActiveuser.put("2017-01-12",temp03List);
Integer[] temp04 = {1,4,5,6};
List<Integer>temp04List = new ArrayList<>();
Collections.addAll(temp04List,temp04);
dateActiveuser.put("2017-01-13",temp04List);
Integer[] temp05 = {1,4,5,6};
List<Integer>temp05List = new ArrayList<>();
Collections.addAll(temp05List,temp05);
dateActiveuser.put("2017-01-14",temp05List);
String date[] = {"2017-01-10","2017-01-11","2017-01-12","2017-01-13","2017-01-14"};
//测试数据放入redis中
for (int i=0;i<date.length;i++){
for (int j=0;j<dateActiveuser.get(date[i]).size();j++){
redis.setbit(date[i], dateActiveuser.get(date[i]).get(j), "1");
}
}
//bitOp
redis.bitop(BitOP.AND, "stat", "stat_2017-01-10", "stat_2017-01-11","stat_2017-01-12");
System.out.println("总活跃用户:"+redis.bitcount("stat"));
redis.bitop(BitOP.AND, "stat1", "stat_2017-01-10", "stat_2017-01-11","stat_2017-01-14");
System.out.println("总活跃用户:"+redis.bitcount("stat1"));
redis.bitop(BitOP.AND, "stat2", "stat_2017-01-10", "stat_2017-01-11");
System.out.println("总活跃用户:"+redis.bitcount("stat2"));
假设当前站点有5000W用户,那么一天的数据大约为50000000/8/1024/1024=6MB
3. 用户在线状态
前段时间开发一个项目,对方给我提供了一个查询当前用户是否在线的接口。不了解对方是怎么做的,自己考虑了一下,使用bitmap是一个节约空间效率又高的一种方法,只需要一个key,然后用户ID为offset,如果在线就设置为1,不在线就设置为0,和上面的场景一样,5000W用户只需要6MB的空间。
//批量设置在线状态
$uids = range(1, 500000);
foreach($uids as $uid) {
$redis->setBit('online', $uid, $uid % 2);
}
//一个一个获取状态
$uids = range(1, 500000);
$startTime = microtime(true);
foreach($uids as $uid) {
echo $redis->getBit('online', $uid) . PHP_EOL;
}
$endTime = microtime(true);
//在我的电脑上,获取50W个用户的状态需要25秒
echo "total:" . ($endTime - $startTime) . "s";
/**
* 对于批量的获取,上面是一种效率低的办法,实际可以通过get获取到value,然后自己计算
* 具体计算方法改天再写吧,之前写的代码找不见了。。。
*/