# SpringBoot2.x 中使用 Redis 的 bitmap
位图主要用于快速检索关键字状态,通常要求关键字是一个连续的序列(或者关键字是一个连续序列中的大部分), 最基本的情况,使用1bit标示一个关键字的状态(可标示两种状态), 但根据需要也可以使用2bit(标示4种状态),3bit(标示8种状态)。 位图的主要应用场合:标示连续(或接近连续,即大部分会出现)的关键字序列的状态(状态数/关键字个数 越小越好)。
Redis
其实只支持 5
种数据类型,并没有 BitMap
这种类型,BitMap
底层是基于 Redis
的字符串类型实现的。
# 应用场景
# 用户签到
很多网站都提供了签到功能,并且需要展示最近一个月的签到情况,这种情况可以使用 BitMap 来实现。 根据日期 offset = (今天是一年中的第几天) % (今年的天数),key = 年份:用户id。
# 用户在线状态
使用日期作为 key,然后用户 id 为 offset,如果当日活跃过就设置为1。
# 统计活跃用户
如果需要提供一个查询当前用户是否在线的接口,也可以考虑使用BitMap 。即节约空间效率又高,只需要一个key,然后用户id为offset,如果在线就设置为1,不在线就设置为0。 具体步骤如下:
- 使用每个用户的唯一标识映射一个偏移量,比如使用
id
,这里可以把id
换算成一个数字或直接使用id的二进制值作为该用户在当天是否活跃偏移量 - 用户登录则把该用户偏移量上的位值设置为
1
- 每天按日期生成一个位图(
bitmap
)
stringRedisTemplate.opsForValue().setBit(dayKey, user.getId(), Boolean.TRUE);
- 计算日活则使用
bitcount
即可获得一个key
的位值为1
的量
Long dayActivityNum = stringRedisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(dayKey.getBytes()));
log.info("用户日活: {}", dayActivityNum);
2
- 计算月活(一个月内登陆的用户去重总数)即可把
30
天的所有bitmap
做or
计算,然后再计算bitcount
int maxDayNum = 30;
byte[][] bytes = new byte[maxDayNum][];
for (int offset = 0; offset < maxDayNum; offset++) {
bytes[offset] = StrUtil.format("{}{}", userActivityKeyPrefix,
DateUtil.format(DateUtil.offsetDay(new Date(), -offset), PURE_DATE_FORMAT)).getBytes();
}
String monthKey = StrUtil.format("{}{}", userActivityKeyPrefix, 30);
stringRedisTemplate.execute((RedisCallback<Long>) con -> con.bitOp(RedisStringCommands.BitOperation.OR,
monthKey.getBytes(), bytes));
Long monthActivityNum = stringRedisTemplate
.execute((RedisCallback<Long>) con -> con.bitCount(monthKey.getBytes()));
log.info("用户月活:{}", monthActivityNum);
2
3
4
5
6
7
8
9
10
11
12
- 计算留存率(次日留存=昨天今天连续登录的人数/昨天登录的人数) 即昨天的
bitmap
与今天的bitmap
做and
计算再做bitcount
就得到连续登录人数,bitcount
得到昨天登录人数,就可以通过公式计算出次日留存。
# 实现布隆过滤器
# Redis 的 bitmap 命令
# setbit
设置或修改key上的偏移量(offset)的位(value)的值。
- 语法:setbit key offset value
- 返回值:指定偏移量(offset)原来存储的值。
# getbit
查询key所存储的字符串值,获取偏移量上的位。
- 语法:getbit key offset
- 返回值:返回指定key上的偏移量,若key不存在,那么返回0。
# bitcount
计算给定key的字符串值中,被设置为1的位bit的数量
- 语法:bitcount key [start] [end]
- 返回值:1比特位的数量
# bitop
对一个或多个保存二进制的字符串key进行元操作,并将结果保存到destkey上。
- 语法:operation可以是and、or、not、xor的一种。
- bitop and destkey key [key...],对一个或多个key逻辑并,结果保存到destkey。
- bitop or destkey key [key...],对一个或多个key逻辑或,结果保存到destkey。
- bitop xor destkey key [key...],对一个或多个key逻辑异或,结果保存到destkey。
- bitop xor destkey key,对一个或多个key逻辑非,结果保存到destkey。
- 除了
NOT
之外,其他操作多可以接受一个或多个key作为输入。
BITOP
的时间复杂度是O(N)
,当处理大型矩阵或者大量数据统计时,最好将任务指派到附属节点(slave)进行,避免阻塞主节点。
# bitmap 导致大 KEY
bitmap推荐的偏移量是从1一直累加的,但是计算出的hash值为10位(10亿级别),那么占用的内存大小为239MB,若是计算出的hash值为7位(百万级别)占用的内存大小为124KB。
所以计算偏移量的时候不能无脑的进行hash得到,而是要根据系统情况(百万级别的日活、十万级别日活),进行取余计算,得到合适的偏移量。
解决方案是对数据进行分组在分片:
- 不仅可以避免
bitmap
导致大key
。 - 避免出现范围用户太多导致查询时出现
热key
。