基于Redis Bitmap实现用户签到功能
背景
在之前做过的需求里,有个养鱼活动,用户每天来签到,就可以领一定的饲料,拿来喂养鱼。连续签到的天数越多,每天能领取的饲料克数就越多。每7天为一个周期,一个周期内开始第一天签到,领取的饲料克数重新从100g开始算起,签到中断也是重新开始计算。
技术实现原理
在高并发的场景下,用户签到的数据,如果直接使用数据库存储,数据库的压力会很大。另外,签到的数据比较简单,只需要记录用户ID、是否签到即可。在这里就考虑到使用Redis Bitmap来存储实现,key为sign:{userId}:{yyyyMM}
。Redis提供的数据类型BitMap(位图),每个bit位对应0和1两个状态,设置为1时,代表这一天已经签到了。虽然内部还是采用String类型存储,但Redis提供了一些指令用于直接操作BitMap,可以把它看作一个bit数组,数组的下标就是偏移量。
位图不是真正的数据类型,它是定义在字符串类型中,一个字符串类型的值最多能存储512M字节的内容,最大位数为:2^32 = (2^9) * (2^10) * (2^10) * (2^3)
。
它的优点是内存开销小,效率高且操作简单,很适合用于签到这类场景。缺点在于位计算和位表示数值的局限。如果要用位来做业务数据记录,就不要在意value的值。如果位图很大,建议拆分键。另外,bitmap设置时候时间复杂度O(1)
、读取时候时间复杂度O(n)
,操作是非常快的。因为读取时候时间复杂度O(n)
,越大的串读的时间花销越多。
API介绍
setbit
作用:设置某一位上的值
语法:SETBIT key offset value
(offset位偏移量,从0开始)
1 | setbit k1 1 1 |
getbit
作用:获取某一位上的值
语法:GETBIT key offset
1 | getbit k1 7 |
bitpos
作用:返回指定值0或者1在指定区间上首次出现的下标
语法:BITPOS key bit [start] [end]
(字节索引,0表示第一个字节)
不指定查找范围,表示从全部内容中查找:BITPOS key bit
1 | bitpos k1 1 |
指定查找范围:
- BITPOS key bit start:从start+1个字节开始查找,直到尾部
- BITPOS key bit start end:从start+1字节开始到end+1字节之间查找
bitop
作用:位操作
语法:BITOP operation destkey key [key ...]
对一个或多个保存二进制位的字符串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 NOT destkey key : 对给定 key 求逻辑非,并将结果保存到 destkey
除了NOT
操作之外,其他操作都可以接受一个或多个key
作为输入,当BITOP
处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作 0,空的key
也被看作是包含 0 的字符串序列。如果要使用BITOP
,建议读取到客户端再进行位计算。
BITFIELD
作用:可以在一次调用中同时对多个位范围进行操作。
以下是BITFIELD
命令支持的子命令语法:
- GET
: 返回指定的二进制位范围。 - SET
: 对指定的二进制位范围进行设置,并返回它的旧值。 - INCRBY
: 对指定的二进制位范围执行加法操作,并返回它的旧值。用户可以通过向 increment 参数传入负值来实现相应的减法操作。
除了以上三个子命令之外, 还有一个子命令, 它可以改变之后执行的INCRBY
子命令在发生溢出情况时的行为:
- OVERFLOW [WRAP|SAT|FAIL]
当被设置的二进制位范围值为整数时, 用户可以在类型参数的前面添加i
来表示有符号整数, 或者使用u
来表示无符号整数。 比如说,我们可以使用u8
来表示8位长的无符号整数, 也可以使用i16
来表示16位长的有符号整数。BITFIELD
命令最大支持64位长的有符号整数以及63位长的无符号整数, 其中无符号整数的63位长度限制是由于Redis协议目前还无法返回64位长的无符号整数而导致的。
下面对位于偏移量 100 的 8 位长有符号整数执行加法操作, 并获取位于偏移量 0 上的 4 位长无符号整数:
1 | > BITFIELD mykey INCRBY i8 100 1 GET u4 0 |
其他应用场景
用户在线状态
只需要一个key,然后用户id为偏移量offset,如果在线就设置为1,不在线就设置为0。如果数据量过多的,可以按用户ID进行分片。
统计活跃用户
使用时间天作为缓存的key,然后用户id为offset,如果当日活跃过就设置为1。之后通过bitOp AND
进行二进制计算算出在某段时间内用户的活跃情况