redis中的位图理解

1.简介

日常开发工作中会有很多bool类型的值需要存取,比如记录某个玩家一年的活跃情况,每天对应的状态只有两种,活跃/不活跃。如果使用一个set来记录当天活跃的用户,当用户量非常大时会浪费非常多的空间。因此redis提供了位图(bitmap),让用户可以对每一位进行单独操作,设定某一位的值,位图并不是一个新的数据类型,它其实是使用了字符串类型。

127.0.0.1:6900> setbit bitmap|test 1 1
(integer) 1
127.0.0.1:6900> type bitmap|test
string

2.操作

位图的操作主要有以下几个:

1). setbit key offset value 

设定某一位的值,如果key不存在就创建一个新的key并设定值,offset是偏移量,可以看做是下标

 

可以看到只能对某一位设定为0或者1,其他值会报错

2). getbit key offset

获取某一位的值

在上边的操作之后继续执行getbit命令可以看到,没有setbit的位会默认设定为0

3). bitcount key start end

统计start到end之间的1的数量

4). bitpos key bit start end

返回0/1在start到end之间第一次出现的位置

3. 使用场景注意,位图的目的是节省空间,在适当的场景下使用可以很大程度上节省空间,但是使用不当的话反而适得其反。因为bitmap其实就是一个string,因此位图的空间法则和string的空间分配法则一致:当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容时一次只会多扩 1M 的空间。需要注意的是字符串最大长度为 512M。

虽然没办法查看到bitmap的容量,但是我们可以通过使用string的strlen来查看实际填充的大小:

可以看到在设置了365位的值为1的情况下,bitmap的大小仅为46字节。

46 * 8 = 368 刚好可以表示365位的值,再根据上边的操作,可以确定bitmap的填充法则为:按字节用多少填充多少

再验证一下:

154321 * 8 = 1234568 

bitmap适合的场景:对连续增长的整形id进行相应的数据统计,且活跃量较高。

为什么要连续增长呢,这取决于bitmap的位偏移量机制,如果id不能保证连续增长,那么相邻的两个id之间会有很多位始终都为0,反而浪费了空间。

此外,在使用过程中一定要避免存放一些较大的整形,笔者曾经做过一个需求:统计某用户在当天是否登录过游戏,本来使用set就可以完成的需求,但是想到了刚学习的bitmap,于是使用了bitmap来处理,

以时间为key:YYYYMMDD|login_user

用户id为9位的整形(例:123456789),将用户id作为filed,由于用户id不重复,所以每个用户id都有一个位可以表示某天是否登录过游戏,在当时并没有意识到什么不妥,直到有一天看到了redis的底层是string突然想到了空间问题。。。

赶紧去redis下验证了一下才发现以前的做法不但没有节省空间,反而浪费了大量空间:

可以看到,表示9位整形的string长度为15432099字节

再结合string的扩充法则可以推测该key占用的空间为15M,如果使用set来存放用户id的话也能存放上百万用户

造成如此之大的浪费的主要原因就是在100000000之前的所有位都没用利用上,反而浪费了大量空间

因此在使用bitmap时一定要谨慎分析场景,依据bitmap的容量规则,确定能够节省空间的情况下再选择使用bitmap。