NoSQL数据库(三)02-Redis进阶与实战——EXPIRE命令设置过期时间-实现定期检测删除过期数据 & EXPIRE实现和优化访问服务器频率限制

文章介绍了如何在Redis中使用EXPIRE命令设置键的过期时间,以及如何利用TTL检查键的剩余生存时间。此外,讨论了如何通过EXPIRE实现访问频率限制,防止服务器压力过大,并提出了一种基于列表的改进方案,以更精确地控制访问频率。同时,提到了PERSIST命令用于取消键的过期时间,以及EXPIREAT和PEXPIREAT命令用于设定键的精确过期时间点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

NoSQL数据库(三)02-Redis进阶与实战——EXPIRE命令设置过期时间-实现定期检测删除过期数据 & EXPIRE实现和优化访问服务器频率限制

过期时间

在实际的开发中经常会遇到一些有时效的数据,比如限时优惠活动、缓存或验证码等,过了一定的时间就需要删除这些数据。在关系数据库中一般需要额外的一个字段记录到期时间,然后定期检测删除过期数据。而在Redis中可以使用 EXPIRE命令设置一个键的过期时间,到时间后Redis会自动删除它。

EXPIRE 命令的使用方法为 EXPIRE key seconds,其中 seconds 参数表示键的过期时间,单位是秒。如要想让session:29e3d键在15分钟后被删除:

redis> SET session:29e3d uid1314 
OK 
redis> EXPIRE session:29e3d 900 
(integer) 1

**EXPIRE命令返回1表示设置成功,返回0则表示键不存在或设置失败。**例如:

redis> DEL session:29e3d 
(integer) 1 
redis> EXPIRE session:29e3d 900 
(integer) 0

**如果想知道一个键还有多久的时间会被删除,可以使用TTL命令。**返回值是键的剩余时间(单位是秒):

redis> SET foo bar 
OK 
redis> EXPIRE foo 20 
(integer) 1 
redis> TTL foo 
(integer) 15 
redis> TTL foo 
(integer) 7 
redis> TTL foo 
(integer) –2

可见随着时间的不同,foo键的过期时间逐渐减少,20秒后foo键会被删除。当键不存在时TTL命令会返回−2。 那么没有为键设置过期时间(即永久存在,这是建立一个键后的默认情况)的情况下会返回什么呢?答案是返回−1:

如果想取消键的过期时间设置(即将键恢复成永久的),则可以使用PERSIST命令。如果过期时间被成功清除则返回1;否则返回0(因为键不存在或键本来就是永久的):

redis> SET foo bar 
OK 
redis> EXPIRE foo 20 
(integer) 1 
redis> PERSIST foo 
(integer) 1 
redis> TTL foo 
(integer) –1

除了PERSIST命令之外,使用SET或GETSET命令为键赋值也会同时清除键的过期时间,例如:

redis> EXPIRE foo 20 
(integer) 1 
redis> SET foo bar OK 
redis> TTL foo 
(integer) –1

其他只对键值进行操作的命令(如INCR、LPUSH、HSET、ZREM)均不会影响键的过期时间。

EXPIRE命令的seconds参数必须是整数,所以最小单位是1秒。**如果想要更精确的控制键的过期时间应该使用 PEXPIRE命令,**PEXPIRE命令与 EXPIRE的唯一区别是前者的时间单位是毫秒,即 PEXPIRE key 1000 与 EXPIRE key 1 等价。对应地可以用 PTTL命令以毫秒为单位返回键的剩余时间。

提示 如果使用 WATCH命令监测了一个拥有过期时间的键,该键时间到期自动删除并不会被WATCH命令认为该键被改变。

另外还有两个相对不太常用的命令EXPIREAT 和 PEXPIREAT

EXPIREAT命令与EXPIRE命令的差别在于前者使用Unix时间作为第二个参数表示键的过期时刻。PEXPIREAT命令与EXPIREAT命令的区别是前者的时间单位是毫秒。如:

redis> SET foo bar OK 
redis> EXPIREAT foo 1351858600 
(integer) 1 
redis> TTL foo 
(integer) 142 
redis> PEXPIREAT foo 1351858700000 
(integer) 1
ex: 实现访问频率限制

需求:为了减轻服务器的压力,需要限制每个用户(以IP计)一段时间的最大访问量。

实现: 通过EXPIRE

分析思路:

  1. 根据用户ID新建一个字段 rate.limite:$IP, 存储的结果是访问量
  2. 用户每次访问的时候使用incr进行+1,并且EXPIRE设置一个过期时间
  3. 如果没过期则+1, 过期了则新建字段初始化访问次数0
var redis = require('redis');
var client = new redis({
    // 配置
});

var isKeyExists = client.exists(`rate:limite:$ip`);

if (isKeyExists) {
    var time = client.incr('rate:limite:$ip');
    if (time > 100) {
        client.exit('你已经不能访问了,请休息下')
    }
} else {
    client.incr('rate:limite:$ip');
    client.expire('rate:limite:$ip 60');
}

这段代码存在一个不太明显的问题:假如程序执行完倒数第二行后突然因为某种原因退出了,没能够为该键设置过期时间,那么该键会永久存在,导致使用对应的IP的用户在管理员手动删除该键前最多只能访问100次博客,这是一个很严重的问题。

修改思路:使用事务包裹else

如果一个用户在一分钟的第一秒访问了一次博客,在同一分钟的最后一秒访问了9次,又在下一分钟的第一秒访问了10次,这样的访问是可以通过现在的访问频率限制的,但实际上该用户在2秒内访问了19次博客,这与每个用户每分钟只能访问10次的限制差距较大。尽管这种情况比较极端,但是在一些场合中还是需要粒度更小的控制方案。

修改思路:将散列结构改造为列表

  1. 新建一个 rate:limit:$ip的列表字段
  2. 每次访问向列表中存入当前时间戳
  3. 如果 列表长度小于10则存入
  4. 如果等于10,则讲列表的第一个时间戳和现在的时间对比。
    1. 如果小于60秒则拒绝访问
    2. 反之存入,并删除第一条时间
var redis = require('redis');
var client = new redis({
    // 配置
});

var listLength = client.llen('rate:limit:$ip');

if (listLength < 10) {
    client.lpush(`rate:limit:$ip ${new Date().valueOf()}`);
} else {
    var time = client.lindex('rate:limit:$ip -1');
    if(new Date().valueOf() - time < 60) {
        client.exit('访问超过限制');
    } else {
        client.lpush(`rate:limit:$ip ${new Date().valueOf()}`);
        client.ltrim(`rate:limit:$ip 0 9`);
    }
}
实例1-expire实现访问频率限制

expire.js

var redis = require('redis');
var client = new redis({
    // 配置
});
// 第一次访问,redis有你的字段吗? 
var isKeyExists = client.exists(`rate:limit:$ip`);

if (isKeyExists) {  // 假设1分钟对多访问10次
    var times = client.incr(`rate:limit:$ip`);
    if (times > 10) {
        client.exit('你不能访问');
    }
} else {
    // 没有访问过的情况
    client.multi();
    client.incr('rate:limit:$ip');    // 假如出现意外,则永远不会设置过期时间
    client.expire('rate:limit:$ip 60');
    client.exec();
}
实例2-expire实现访问频率限制优化

list_expire.js

var redis = require('redis');
var client = new redis({
    // 配置
});

// 判断列表的长度, 代表你的访问次数
var listLength = clien.llen('rate:limit:$ip');

if (listLength < 10) {
    clien.lpush(`rate:limit:$ip ${new Date().valueOf()}`); // 存储时间戳
} else{
    var time = clien.lindex(`rate:limit:$ip -1`);
    if (new Date().valueOf() - time < 60 ) { // 当前时间 - 你第一访问的时间(rate:limit:$ip 列表里面最早的时间 ) < 60秒
        clien.exit('不能访问');
    } else {
        clien.lpush(`rate:limit:$ip ${new Date().valueOf()}`);
        clien.ltrim(`rate:limit:$ip 0 9`);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值