Redis 快速实现签到统计功能

触发点

MySql 来实现的话虽然简单粗暴,但是也有弊端,比如我们想要做一些复杂的功能就不是太方便了,或者说不是太高性能了,比如,今天是连续签到的第几天,在一定时间内连续签到了多少天。另外一方面,如果按 100 万用户量级来计算,一个用户每年可以产生 365/366 条记录,100 万用户的所有签到记录那就有点恐怖了,查询计算速度也会越来越慢。

准备

Redis 的字符串数据都是以二进制的形式存放的,所以说 Redis 的 Bit 操作非常适合处理这个场景,因为 Bit 的值为 01用户是否打卡也可以用 0 或 1 来表示,我们把签到的天数对应到每个字节上,打卡了就是 1,没打卡就是 0,那么一个用户一年下来的记录就是 365 位的长度,100 万用户一年只需要耗费大约 43 M 左右的存储空间就可以了,而且速度贼快

大伙可能会问,这个究竟是怎么计算来的,我们来看一下官方的解释:

在一台 2010MacBook Pro 上,offset 为 2^32-1(分配 512MB)需要~300ms,offset 为 2^30-1 (分配 128MB) 需要~80ms,offset 为 2^28-1(分配 32 MB)需要~30ms,offset 为 2^26-1(分配 8MB)需要 8ms。
大概的空间占用计算公式是:(offset / 8 / 1024 / 1024) MB

实例

  • 实例化一个Redis连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

namespace App\Support;

use App\Concerns\Singleton;
use Illuminate\Support\Facades\Log;

class RedisTool extends \Redis
{
use Singleton;

public function __construct()
{
parent::__construct();

$host = config('database.redis.default.host');
$port = config('database.redis.default.port');

if (! $this->connect($host, $port)) {
Log::error("redis fail to connect to $host:$port");
}
}
}
1
$redis = RedisTool::getInstance();
  • 如何设计key?
1
2
3
4
$dayKey = 'login:' . \now()->format('Ymd');

// 普通写法
$dayKey = 'login:' . \date('Ymd', \time());
  • 签到

http://redisdoc.com/bitmap/setbit.html

  • setbit: 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
1
$redis->setBit($dayKey, $user_id, 1);
  • 统计签到数据

http://redisdoc.com/bitmap/bitop.html

  • setOp: 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey
    • AND: 对一个或多个key求逻辑并
    • OR: 对一个或多个key求逻辑或
    • XOR: 对一个或多个key求逻辑异或
    • NOT: 对给定的key求逻辑非
1
2
3
4
5
6
7
8
9
10
11
12
$redis->bitOp('AND', 'threeAnd', 'login:20190804', 'login:20190805', 'login:20190806');
echo "连续三天都签到的用户数量: " . $redis->bitCount('threeAnd') . "<br>";

$redis->bitOp('OR', 'threeOr', 'login:20190804', 'login:20190805', 'login:20190806');
echo "连续三天中签到用户数量 (有一天也算签了) :" . $redis->bitCount('threeOr') . "<br>";

// 这里不建议使用keys,可以使用scan替代
$redis->bitOp('AND', 'monthActivities', $redis->keys("login:201908*"));
echo "连续一个月签到用户数量 :" . $redis->bitCount('monthActivities') . "<br>";

echo "当前用户指定天数是否签到: " . $redis->getBit('login:20190801', 1024) . "<br>";
...
  • 结果
1
2
3
4
连续三天都签到的用户数量: 1
连续三天中签到用户数量 (有一天也算签了) :3
连续一个月签到用户数量 :0
当前用户指定天数是否签到: 0

总结

  • 这里只针对用户签到统计, 如需用户签到具体信息,需要使用一个签到记录表

参考

Powered by Hexo and Hexo-theme-hiker

Copyright © 2017 - 2023 Keep It Simple And Stupid All Rights Reserved.

访客数 : | 访问量 :