redis灵魂拷问:聊一聊bitmap使用

发布于 2022-5-5 14:56
浏览
0收藏

bitmap是redis的一种扩展数据类型,主要用于二值状态统计,比如公司记录员工打卡记录,电商网站记录用户登录行为,积分商城记录用户签到情况。

bigmap底层使用的是String的数据结构,而String保存在计算机中的格式是二进制的字节数组,这样bitmap就充分利用了每个字节的bit位,大大节省了内存开销。

下面我们看一下bitmap的使用。

员工打卡

假如一个公司有100个员工,公司要对员工11月份的打卡行为进行统计,我们可以为11月份每一天分配一个bitmap,这个bitmap保存100个bit位,来记录员工的打卡行为。

注意:bitmap偏移量从0开始,所以100个bit位是从0~99,依次记录1-100号员工。

我们定义bitmap的key格式为:signed:20201101,记录2020年11月1日的打卡情况。下面代码是员工打卡和查询员工打卡情况:

/**
 * SETBIT命令
 * 员工打卡
 * 时间复杂度:O(1)
 */
public void sign(String key, int employeeNumber){
    redisTemplate.opsForValue().setBit(key, employeeNumber - 1, true);
}
/**
 * GETBIT命令
 * 查看员工打卡情况
 * 时间复杂度:O(1)
 */
public boolean isSigned(String key,int employeeNumber){
    return redisTemplate.opsForValue().getBit(key, employeeNumber - 1);
}

我们可以查看某一天的打卡总人数,代码如下,入参:"signed:20201101":

/**
 * BITCOUNT命令
 * 查看某一天的打卡人数
 * 时间复杂度:O(N)
 */
public Long signedCount(String key){
    return (Long) redisTemplate.execute((RedisCallback<Long>)  conn -> conn.bitCount(key.getBytes()));
}

这样我们就能根据打卡人数来判断当天的迟到人数比例。

注意:上面的sign方法必须设置key的序列化采用StringRedisSerializer,否则查询打卡情况是查不到的。如果不设置StringRedisSerializer,上面的sign和isSigned改为使用conn来执行,代码如下:

public void sign(String key, int employeeNumber){
    redisTemplate.execute((RedisCallback<Boolean>)  conn -> conn.setBit(key.getBytes(), employeeNumber - 1, true));
}
public boolean isSigned(String key, int employeeNumber){
    return redisTemplate.execute((RedisCallback<Boolean>)  conn -> conn.getBit(key.getBytes(), employeeNumber - 1));
}

或者使用下面代码来设置RedisTemplate的setKeySerializer:

redisTemplate.setKeySerializer(new StringRedisSerializer());

那如果想看当月没有迟到过的员工呢?这个时候就要用到交集了,对当月每天的bitmap做交集,值为1的员工就是没有迟到过的。

这时就要用到bitmap的聚合运算了,命令BITOP, 支持AND(与)、OR(或), XOR(异或) and NOT(非)运算,除了NOT后面跟一个bitmap外,其他3种聚合运算后面都可以跟多个bitmap,命令如下:

BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN
BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN
BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN
BITOP NOT destkey srckey

为了让demo简单一些,我这里给出一个查看2天内没有迟到的员工,代码如下:

/** * 命令:BITOP * 复杂度:O(N) * 整个月全勤的员工数量,这里用2天代表整个月 * @param key1 第一天 * @param key2 第二天 */public Long

/**
 * 命令:BITOP
 * 复杂度:O(N)
 * 整个月全勤的员工数量,这里用2天代表整个月
 * @param key1 第一天
 * @param key2 第二天
 */
public Long signedAllMonth(String key1, String key2){
    String andMap = "signedAllMonth11";
    redisTemplate.execute((RedisCallback)  conn -> conn.bitOp(RedisStringCommands.BitOperation.AND, andMap.getBytes(), key1.getBytes(), key2.getBytes()));
    return (Long) redisTemplate.execute((RedisCallback<Long>)  conn -> conn.bitCount(andMap.getBytes()));
}

下面我再给出一段测试代码,这段代码模拟有50个员工全勤,bitMapService是我上面的代码所在类:

@Test
public void testSignedAllMonth(){
    for (int i = 1; i <= 100; i++){
        bitMapService.sign("signed:20201101", i);
    }
    for (int i = 1; i <= 100; i += 2){
        bitMapService.sign("signed:20201102", i);
    }
    Long count = bitMapService.signedAllMonth("signed:20201101", "signed:20201102");
    System.out.println("=========="+count);
}

输出如下:

==========50

判断日活跃用户数量

比如网站有10万个用户,我们要判断当天的日活用户。这样我们创建一个长度为10万的bitmap,每个用户id占一个位,我们定义key为:user:login,number为用户编号。当有用户登录时,调用下面的方法:

redisTemplate.opsForValue().setBit(key, number - 1, true);

日终的时候,我们用下面的方法就可以判断出日活用户:

redisTemplate.execute((RedisCallback<Long>)  conn -> conn.bitCount(key.getBytes()));

总结

bitmap广泛地运用在二值计算的场景,对于一个二值状态只用一个bit位就可以,非常节约内存。比如我们对一个10亿的用户进行日活计算,占用的空间只有120M:

10亿/8/1024/1024=120M

官网链接:

https://redis.io/commands/bitop.

本文转载自微信公众号「君哥聊技术」

原文链接:https://mp.weixin.qq.com/s/mYbRV4dHnbhHZWLMBhmG8Q.

 

分类
已于2022-5-5 14:56:51修改
收藏
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐