# 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);
1
  • 计算日活则使用bitcount即可获得一个key的位值为1的量
Long dayActivityNum = stringRedisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(dayKey.getBytes()));
log.info("用户日活: {}", dayActivityNum);
1
2
  • 计算月活(一个月内登陆的用户去重总数)即可把30天的所有bitmapor计算,然后再计算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);
1
2
3
4
5
6
7
8
9
10
11
12
  • 计算留存率(次日留存=昨天今天连续登录的人数/昨天登录的人数) 即昨天的bitmap与今天的bitmapand计算再做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
Last Updated: 2 years ago