BitMap
什么是 BitMap
BitMap,即位图,其实也就是 byte 数组,用二进制表示,只有 0 和 1 两个数字。
如图所示:
重要 API
命令 | 含义 |
---|
getbit key offset | 对key所存储的字符串值,获取指定偏移量上的位(bit) |
setbit key offset value | 对key所存储的字符串值,设置或清除指定偏移量上的位(bit) 1. 返回值为该位在setbit之前的值 2. value只能取0或1 3. offset从0开始,即使原位图只能10位,offset可以取1000 |
bitcount key [start end] | 获取位图指定范围中位值为1的个数 如果不指定start与end,则取所有 |
bitop op destKey key1 [key2...] | 做多个BitMap的and(交集)、or(并集)、not(非)、xor(异或)操作并将结果保存在destKey中 |
bitpos key tartgetBit [start end] | 计算位图指定范围第一个偏移量对应的的值等于targetBit的位置 1. 找不到返回-1 2. start与end没有设置,则取全部 3. targetBit只能取0或者1 |
演示
应用场景
统计每日用户的登录数。每一位标识一个用户ID,当某个用户访问我们的网页或执行了某个操作,就在bitmap中把标识此用户的位设置为1。
这里做了一个 使用 set 和 BitMap 存储的对比。
场景1:1 亿用户,5千万独立
数据类型 | 每个 userid 占用空间 | 需要存储的用户量 | 全部内存量 |
---|
set | 32位(假设userid用的是整型,实际很多网站用的是长整型) | 50,000,000 | 32位 * 50,000,000 = 200 MB |
BitMap | 1 位 | 100,000,000 | 1 位 * 100,000,000 = 12.5 MB |
| 一天 | 一个月 | 一年 |
---|
set | 200M | 6G | 72G |
BitMap | 12.5M | 375M | 4.5G |
场景2:只有 10 万独立用户
数据类型 | 每个 userid 占用空间 | 需要存储的用户量 | 全部内存量 |
---|
set | 32位(假设userid用的是整型,实际很多网站用的是长整型) | 1,000,000 | 32位 * 1,000,000 = 4 MB |
BitMap | 1 位 | 100,000,000 | 1 位 * 100,000,000 = 12.5 MB |
通过上面的对比,我们可以看到,如果独立用户数量很多,使用 BitMap 明显更有优势,能节省大量的内存。但如果独立用户数量较少,还是建议使用 set 存储,BitMap 会产生多余的存储开销。
使用经验
- type = string,BitMap 是 sting 类型,最大 512 MB。
- 注意 setbit 时的偏移量,可能有较大耗时
- 位图不是绝对好。
统计指定日期范围内的DAU 和UV (IP排重统计数据)
public long calculateUV(Date start, Date end) {
if (start == null || end == null) {
throw new IllegalArgumentException("参数不能为空!");
}
List<String> keyList = new ArrayList<>();
Calendar calendar = Calendar.getInstance();
calendar.setTime(start);
while (!calendar.getTime().after(end)) {
String key = RedisKeyUtil.getUVKey(df.format(calendar.getTime()));
keyList.add(key);
calendar.add(Calendar.DATE, 1);
}
String redisKey = RedisKeyUtil.getUVKey(df.format(start), df.format(end));
redisTemplate.opsForHyperLogLog().union(redisKey, keyList.toArray());
return redisTemplate.opsForHyperLogLog().size(redisKey);
}
public long calculateDAU(Date start, Date end) {
if (start == null || end == null) {
throw new IllegalArgumentException("参数不能为空!");
}
List<byte[]> keyList = new ArrayList<>();
Calendar calendar = Calendar.getInstance();
calendar.setTime(start);
while (!calendar.getTime().after(end)) {
String key = RedisKeyUtil.getDAUKey(df.format(calendar.getTime()));
keyList.add(key.getBytes());
calendar.add(Calendar.DATE, 1);
}
return (long) redisTemplate.execute(new RedisCallback() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
String redisKey = RedisKeyUtil.getDAUKey(df.format(start), df.format(end));
connection.bitOp(RedisStringCommands.BitOperation.OR,
redisKey.getBytes(), keyList.toArray(new byte[0][0]));
return connection.bitCount(redisKey.getBytes());
}
});
}
所有评论(0)