基于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
2
setbit k1 1 1
setbit k1 7 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
2
3
> BITFIELD mykey INCRBY i8 100 1 GET u4 0
1) (integer) 1
2) (integer) 0

其他应用场景

用户在线状态

只需要一个key,然后用户id为偏移量offset,如果在线就设置为1,不在线就设置为0。如果数据量过多的,可以按用户ID进行分片。

统计活跃用户

使用时间天作为缓存的key,然后用户id为offset,如果当日活跃过就设置为1。之后通过bitOp AND进行二进制计算算出在某段时间内用户的活跃情况

参考资料