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