Redis In Action 筆記(三):使用者登入、瀏覽記錄的快取與管理

tsin發表於2019-06-12

說明

原書中的例子使用的語言是 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);

附錄

Was mich nicht umbringt, macht mich stärker

相關文章