Redis用户签到功能

发布于 2023-06-21  188 次阅读


目标:如何使用Redis实现用户签到功能

设计思路

使用Mysql的话1000w用户一个月就是3亿条数据,存储压力太大。

使用Redis的Bitmaps完成

Bitmaps叫位图,它不是Redis的基本数据类型(比如Strings、Lists、Sets、Hashes这类实际的数据类型),而是基于String数据类型的按位操作,高阶数据类型的一种。Bitmaps支持的最大位数是232位。使用512M内存就可以存储多达42.9亿的字节信息(232 = 4,294,967,296)

它是由一组bit位组成的,每个bit位对应0和1两个状态,虽然内部还是采用String类型存储,但Redis提供了一些指令用于直接操作位图,可以把它看作是一个bit数组,数组的下标就是偏移量。它的优点是内存开销小、效率高且操作简单,很适合用于签到这类场景。比如按月进行存储,一个月最多31天,那么我们将该月用户的签到缓存二进制就是00000000000000000000000000000000,当某天签到将0改成1即可,而且Redis提供对bitmap的很多操作比如存储、获取、统计等指令,使用起来非常方便。


BitMaps常用指令

  1. SETBIT key offset value: 将指定键(key)的位图中偏移量(offset)处的位设置为给定的值(value)。其中,offset 表示位图中的位索引,value 可以是 0 或 1。
  2. GETBIT key offset: 返回指定键(key)的位图中偏移量(offset)处的位的值(0 或 1)。
  3. BITCOUNT key [start end]: 统计指定键(key)的位图中指定范围内的位为 1 的个数。可选的 start 和 end 参数用于指定范围,如果不指定,则统计整个位图。
  4. BITOP operation destkey key [key ...]: 对指定键(key)的位图进行位操作,并将结果存储到 destkey 中。operation 可以是 AND、OR、XOR 或 NOT,用于指定位操作的类型。
  5. BITPOS key bit [start] [end]: 在指定键(key)的位图中查找第一个设置为指定 bit(0 或 1)的位的索引。可选的 start 和 end 参数用于指定查找范围。
  6. BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment]: BITFIELD 指令允许您在一个命令中执行多个位级操作,包括获取、设置和增减位值。

功能开发

需求说明

用户签到,默认是当天,但可以通过传入日期补签,返回用户连续签到次数(后续如果有积分规则,就会返回用户此次签到积分)

代码实现

SignService层关注方法

  • 获取登录用户信息
  • 根据日期获取当前是多少号(使用BITSET指令关注时,offset从0开始计算,0就代表1号)
  • 构建用户按月存储key(user:sign:用户id:月份)
  • 判断用户是否签到(GETBIT指令)
  • 用户签到(SETBIT)
  • 返回用户连续签到次数(BITFIELD key GET [u/i] type offset value, 获取从用户从当前日期开始到1号的所有签到状态,然后进行位移操作,获取连续签到天数)

PHP示例

 // 示例参数 假设今天是21号,所以数据只有到21号为止
$redis = new Redis();
$redis->connect('redis', 6379);
$redis->setbit('user:sign:5:202306', 0, 1); // 这是1号的数据
$redis->setbit('user:sign:5:202306', 1, 1);
$redis->setbit('user:sign:5:202306', 2, 1);
$redis->setbit('user:sign:5:202306', 3, 1);
$redis->setbit('user:sign:5:202306', 4, 1);
$redis->setbit('user:sign:5:202306', 5, 1);
$redis->setbit('user:sign:5:202306', 6, 1);
$redis->setbit('user:sign:5:202306', 7, 1);
$redis->setbit('user:sign:5:202306', 8, 1);
$redis->setbit('user:sign:5:202306', 9, 1);
$redis->setbit('user:sign:5:202306', 10, 1);
$redis->setbit('user:sign:5:202306', 11, 1);
$redis->setbit('user:sign:5:202306', 12, 1);
$redis->setbit('user:sign:5:202306', 13, 1);
$redis->setbit('user:sign:5:202306', 14, 1);
$redis->setbit('user:sign:5:202306', 15, 1);
$redis->setbit('user:sign:5:202306', 16, 0); // 没有签到的日子
$redis->setbit('user:sign:5:202306', 17, 1);
$redis->setbit('user:sign:5:202306', 18, 1);
$redis->setbit('user:sign:5:202306', 19, 1);
$redis->setbit('user:sign:5:202306', 20, 1); // 这是21号的数据

// user:sign:5:202306 用户ID为5 2023年06月的签到数据
$sign_days = 0;
$today = date('j'); // 今天的日期 可以手动设置为21
$decimal = $redis->rawCommand('BITFIELD', 'user:sign:5:202306', ...['GET', 'u' . $today, '0']);
$binary = decbin($decimal[0]);
$sign_days = 0;
$length = strlen($binary);
for ($i = $length - 1; $i >= 0; $i--) {
    $bit = $binary[$i];
    if ($bit === '1') {
        $sign_days++;
    } else {
        break;
    }
}

echo "二进制数: " . $binary;
echo "
"; echo "连续签到 $sign_days 天"; echo "
"; echo "一共签到 {$redis->bitCount('user:sign:5:202306')}天"; # 结果 二进制数: 111111111111111101111 连续签到 4 天 一共签到 20天


间歇性凌云壮志,持续性混吃等死