这里说下redis 的位图,在学习 redis 位图的时候,被它的一些应用场景给惊艳到了,特此记录下。
1个字节( b ) = 8位( bit ) = 0.001 千字节( kb )
redis 位图命令
setbit
指定字符串偏移的位的值
setbit key offset value
setbit 官方文档:https://redis.io/commands/setbit ,也可以参考这个中文版的:http://redisdoc.com/bitmap/setbit.html。
对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。位的设置或清除取决于 value 参数,可以是 0 也可以是 1 。当 key 不存在时,自动生成一个新的字符串值。字符串会进行伸展(grown)以确保它可以将 value 保存在指定的偏移量上。当字符串值进行伸展时,空白位置以 0 填充。offset 参数必须大于或等于 0 ,小于 2^32 (bit 映射被限制在 512 MB 之内) 。对于大偏移量的setbit 操作申请内存会花费一定的时间。
getbit
获取指定偏移量上的位 ,当 offset 比字符串值的长度大,或者 key 不存在时,返回 0 。
getbit key offset
使用位图操作来进行字符串大小写转换
A ===== > 65 0100 0001
a ===== > 97 0110 0001
A 与 a 在二进制位表示,在第 3 位上表示为1 ,将第3位(偏移量为2,偏移量是从0开始的)设置为1即可转换成a
127.0.0.1:6379> set character A
OK
127.0.0.1:6379> get character
"A"
127.0.0.1:6379> setbit character 2 1
(integer) 0
127.0.0.1:6379> getbit character 2
(integer) 1
127.0.0.1:6379> get character
"a"
127.0.0.1:6379>
bitcount
统计字符串被设置为1的bit数
bitcount key [start end]
参考地址:https://redis.io/commands/bitcount
一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。start 和 end 参数的设置和 GETRANGE 命令类似,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,以此类推。不存在的 key 被当成是空字符串来处理,因此对一个不存在的 key 进行 BITCOUNT 操作,结果为 0 。
案例:使用 bitmap 实现用户上线次数统计
假设现在我们希望记录自己网站上的用户的上线频率,比如说,计算用户 A 上线了多少天,用户 B 上线了多少天,诸如此类,以此作为数据,从而决定让哪些用户参加 beta 测试等活动 —— 这个案例可以使用 SETBIT 和 BITCOUNT 来实现。比如说,每当用户在某一天上线的时候,我们就使用 SETBIT ,以用户名作为 key ,将那天所代表的网站的上线日作为 offset 参数,并将这个 offset 上的为设置为 1 。举个例子,如果今天是网站上线的第 100 天,而用户 peter 在今天阅览过网站,那么执行命令 SETBIT peter 100 1 ;如果明天 peter 也继续阅览网站,那么执行命令 SETBIT peter 101 1 ,以此类推。
当要计算 peter 总共以来的上线次数时,就使用 BITCOUNT 命令:执行 BITCOUNT peter ,得出的结果就是 peter 上线的总天数。
性能分析
前面的上线次数统计例子,即使运行 10 年,占用的空间也只是每个用户 10*365 比特位(bit),也即是每个用户 456 字节。对于这种大小的数据来说, BITCOUNT 的处理速度就像 GET 和 INCR 这种 O(1) 复杂度的操作一样快。
如果你的 bitmap 数据非常大,那么可以考虑使用以下两种方法:
- 将一个大的 bitmap 分散到不同的 key 中,作为小的 bitmap 来处理。使用 Lua 脚本可以很方便地完成这一工作。
- 使用 BITCOUNT key [start] [end] 的 start 和 end 参数,每次只对所需的部分位进行计算,将位的累积工作(accumulating)放到客户端进行,并且对结果进行缓存 (caching)。
这个例子,可以在redis 官方文档:https://redis.io/commands/bitcount 有讲,当然也有中文版的:http://www.redis.cn/commands/bitcount.html。
bitop
对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
bitop operation destkey key [key ...]
BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作,除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入。
参考地址:https://redis.io/commands/bitop
例子
127.0.0.1:6379> set str1 "foobar"
OK
127.0.0.1:6379> set str2 "abcdef"
OK
127.0.0.1:6379> bitop and str1 str1 str2
(integer) 6
127.0.0.1:6379> get str1
"`bc`ab"
127.0.0.1:6379>
foobar
f === > 102 0110 0110 o === > 111 0110 1111 o === > 111 0110 1111
b === > 98 0110 0010 a === > 97 0110 0001 r === > 114 0111 0010
=============================================================================
abcdef
a === > 97 0110 0001 b === > 98 0110 0010 c === > 99 0110 0011
d === > 100 0110 0100 e === > 101 0110 0101 f === > 102 0110 0110
结果:
0110 0110 0110 1111 0110 1111 0110 0010 0110 0001 0111 0010
0110 0001 0110 0010 0110 0011 0110 0100 0110 0101 0110 0110
and
----------------------------------------------------------------------------------------------------------------------------------
0110 0000 0110 0010 0110 0011 0110 0000 0110 0001 0110 0010
96 98 99 96 97 98
---------------------------------------------------------------------------------------------------------------------------------
` b c ` a b
案例:
上亿个用户,统计一周内连续活跃用户。
可以结合setbit ,bitop 来实现:以周为key ( 比如周一则 key 为mon,周二 key 为tues ),uid 为500 的用户周一上线了,则用 setbit 把mon将偏移量为500的值设置为1,uid 为501的用户周一上线了,用 setbit 把mon将偏移量为501的值设置为1 ,uid 为501的用户周日上线了,用 setbit 把sun( 周日的key )将偏移量为501的值设置为1 ,以此类推。
最后统计一周内连续活跃用户数,可以使用sitop and res mon tues ... sun 。
拿到结果后,只需要判断第几位值位1,则代表该位所代表 uid 用户一周内连续登陆。
以上案例,1亿人每天登陆情况,用1亿bit约10M的空间就能表示,一年也才不到4个G的空间就能存储。
这里写的比较基础,深入可以参考:https://blog.csdn.net/u011957758/article/details/74783347 。