Redis In Action 笔记(三):用户登录、浏览记录的缓存与管理

说明

原书中的例子使用的语言是 python,这里使用 php 对该例子进行改写、注释并测试运行结果。
例子要实现的功能是缓存用户登录的token,登录时间、浏览记录,并对这些数据进行日常的筛选、清理等。

数据结构(示意图)

登录数据

key:login:,类型:hash

 +-+ login: +---------------+ hash +--+
 |                                    |
 |   token +-------------> user_id    |
 |   (key)                 (value)    |
 |                                    |
 +------------------------------------+

最近访问数据

key:recent:,类型:zset

 +-+ recent: +--------------+ zset +--+
 |                                    |
 |   token +-------------> timestamp  |
 |   (member)              (score)    |
 |                                    |
 +------------------------------------+

浏览记录数据

key:viewed:user_id,类型:zset

 +-+ viewed:123(uid) +------+ zset +--+
 |                                    |
 |   token +-------------> timestamp  |
 |   (member)              (score)    |
 |                                    |
 +------------------------------------+

程序实现

设置常量和Redis连接

const QUIT = false;
const LIMIT = 1000; //原书为10000000

$redis = new Redis();
$redis->connect('127.0.0.1', '6379') || exit('连接失败!');

检查用户是否登录

function checkToken($redis, $token)
{
    return $redis->hGet('login:', $token);
}

更新token

function updateToken($redis, $token, $user, $item = null)
{
    $now = time();
    //添加或更新用户的token
    $redis->hSet('login:', $token, $user);
    //添加或更新用户最近访问时间
    $redis->zAdd('recent:', $now, $token);

    if ($item) {
        $redis->zAdd('viewed:' . $token, $now, $item);
        //表示从0开始,到倒数第26个之间的数据删除(闭区间,最后一个为-1)
        //也即是从-1数到-25这个范围内的数据保留-->保留最后25个
        $redis->zRemRangeByRank('viewed:' . $token, 0, -26);
    }
}

清理数据

function cleanSessions($redis)
{
    while (!QUIT) {  //可以改为守护进程或cron job任务来定期执行
        //统计最近访问记录数量
        $size = $redis->zCard('recent:');
        //如果数量没有超过限制,则休眠1s,重新检查
        if ($size <= LIMIT) {
            sleep(1);
            continue;
        }
        $end_index = min($size - LIMIT, 100);
        //$end_index的值为[0-100]区间的整数
        //取出recent: 集合保留0-100之间的记录,也即超过LIMIT的这一部分
        //下面将这一部分进行删除
        $tokens = $redis->zRange('recent:', 0, $end_index - 1);
        var_dump($redis->zRange('recent:', 0, 0, true)['token-0']);
        $session_keys = [];
        foreach ($tokens as $token) {
            $session_keys[] = 'viewed:' . $token;
        }
        //删除相应用户的浏览记录
        $aa = $redis->delete($session_keys);
        //删除相应用户的登录信息
        $redis->hDel('login:', ...$tokens);
        //删除相应用户的最近访问记录
        $redis->zRem('recent:', ...$tokens);
    }
}

程序运行

/假设用户访问了30个商品,商品id从1到30
//最终viewed:123有序集合的基数是25
for ($i = 0; $i < 30; $i++) {
    updateToken($redis, 'user-1-token', 123, $i);
}
$count = $redis->zCard('viewed:user-1-token');
echo $count . PHP_EOL;  //输出25

//根据token查找用户
$is_login = checkToken($redis, 'user-1-token');
echo (bool)$is_login . PHP_EOL;

//模拟大量用户访问
for ($i = 0; $i < 1050; $i++) {
    updateToken($redis, 'token-' . $i, 123 + $i, $i);
}

//清理数据,释放缓存,以免大量用户信息占据缓存使缓存耗光
//view:xxx集合的数量、'recent:','login:'的基数将保持在LIMIT(程序开头设置为1000)
cleanSessions($redis);

附录

本作品采用《CC 协议》,转载必须注明作者和本文链接
Was mich nicht umbringt, macht mich stärker
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!