Redis对存储值得过期处理实际上是针对该值得键(key)处理的,即时间的设置也是设置key的有效时间
设置key的过期时间,超过时间后,将会自动删除key;
注:expire和pexpire 是设置一个过期的时间段,比如设置几分钟后过期或者几秒钟后过期;而expireat和pexpireat是指定一个过期时间,比如优惠券在某年某月某日过期
// expire用法
127.0.0.1:6379> set age 12
OK
127.0.0.1:6379> get age
"12"
127.0.0.1:6379> expire age 10
(integer) 1
127.0.0.1:6379> ttl age
(integer) 8
127.0.0.1:6379> ttl age
(integer) 6
127.0.0.1:6379> ttl age
(integer) 1
127.0.0.1:6379> get age
(nil)
//expireat
127.0.0.1:6379> get age
"18"
127.0.0.1:6379> time
1) "1637996753"//2021-11-27 15:05:53
2) "982529"
127.0.0.1:6379> expireat age 1637996825 //吧时间戳设置为2021-11-27 15:07:05为过期时间
(integer) 1
127.0.0.1:6379> ttl age
(integer) 41
127.0.0.1:6379> ttl age
(integer) 39
127.0.0.1:6379> ttl age
(integer) 36
127.0.0.1:6379> ttl age
(integer) 27
127.0.0.1:6379> ttl age
(integer) 9
127.0.0.1:6379> get age
"18"
127.0.0.1:6379> get age
(nil)
//setex
127.0.0.1:6379> get age
(nil)
127.0.0.1:6379> set age 28
OK
127.0.0.1:6379> get age
"28"
127.0.0.1:6379> setex age 30 38
OK
127.0.0.1:6379> get age
"38"
127.0.0.1:6379> ttl age
(integer) 22
127.0.0.1:6379> ttl age
(integer) 19
127.0.0.1:6379> ttl age
(integer) 4
127.0.0.1:6379> ttl age
(integer) -2
127.0.0.1:6379> get age
(nil)
特殊情况
1、persist 可以清除超时,使其变成一个永久的key;expire的反命令;
2、如果key被rename命令修改,相关的超时时间就会移到新key上面;
127.0.0.1:6379> setex age 20 18
OK
127.0.0.1:6379> get age
"18"
127.0.0.1:6379> rename age new_age
OK
127.0.0.1:6379> ttl age
(integer) -2
127.0.0.1:6379> ttl new_age //age的有效期移到了new_age上面
(integer) 5
127.0.0.1:6379> ttl new_age
(integer) -2
过期精度
在Redis2.4以前版本,过期时间可能不是十分准确,有0-1秒的误差;
在Redis2.6之后,过期时间误差缩小到0-1毫秒;
Redis keys过期的方式有两种:
1、被动(惰性删除):当一些数据过期后,并不会马上删除,而是有客户端尝试访问它时,对数据进行检查,如果数据过期,则删除数据;
优点:不需要单独启动额外的扫描线程,减少了CPU资源的消耗;
缺点:大量的过期数据滞留在内存中,需要主动触发、检查、删除、否则这些过期的key会一直占用内存资源;
2、定期删除:每隔一段时间,默认100ms,Redis会随机挑选一定数量的key,检查是否过期,并将过期的数据删除;
被动删除的步骤:
1、测试随机的设置的个keys进行相关过期检测;
2、删除所有已经过期的keys;
3、如果有多于25%的keys过期,重复步骤1;
3、数据淘汰策略【具体内容会在内存优化方面详细说】
当内存超过maxmemory限定时,也会触发主动清理策略
虽然我们设置了Redis key的过期时间,但如果业务应用写入量很大,并且过期时间设置的比较久,那么短时间内Redis的内存还是会快速增长
对于这种场景,需要提前预估业务数据量,然后给这个实例设置maxmemory控制实例的内存上限,这样就可以避免Redis的内存持续膨胀;
设置数据淘汰策略,则具体业务场景具体分析了;
volatile-lru / allkeys-lru:优先保留最近访问过的数据
volatile-lfu / allkeys-lfu:优先保留访问次数最频繁的数据(4.0+版本支持)
volatile-ttl :优先淘汰即将过期的数据
volatile-random / allkeys-random:随机淘汰数据
Redis 中过期键的删除策略正是惰性删除与定期删除的结合。
持久化的两种方式:RDB和AOF;
对于过期也分开讨论:
在生成RDB的文件的过程中,如果一个键已经过期了,那么其不会被保存到RDB文件中。在载入RDB的时候,分两种情况:
1、如果Redis以主服务器的模式运行,那么会对RDB中的键进行时间检查,过期的键不会被恢复到Redis中。
2、如果Redis以从服务器的模式运行,那么RDB中所有的键都会被载入,忽略时间的检查。在从服务器与主服务器进行数据同步的时候,从服务器的数据会先被清空,所以载入的过期键不会有问题。
对于AOF来说,如果一个键过期了,那么不会立刻对AOF文件造成影响。因为Redis使用的是惰性删除+定期删除,只有这个键被删除了,才会往AOF文件里追加一条del删除命令。在重写AOF的过程中,会检查数据库中的键,已经过期的键不会被保存到AOF文件中。
在运行的过程中,对于主从复制的Redis,主服务器和从服务器对于过期键的处理也不相同:
1、对于主服务器,一个过期的键被删除了后,会想从服务器发送del命令,通知从服务器删除对应的键;
2、从服务器接受到读取一个键的命令时,即使这个键已经过期,也不会删除,而是照常处理这个命令。
3、从服务器接收到主服务器的del命令,才会删除对应的过期键。Redis 2.6之后,从服务器只允许执行读命令,为了确保与主服务器进行数据同步得到更新,保证主从服务器之间数据一致性;
void expireGenericCommand(client *c, long long basetime, int unit) {
robj *key = c->argv[1], *param = c->argv[2];
long long when; /* unix time in milliseconds when the key will expire. */
if (getLongLongFromObjectOrReply(c, param, &when, NULL) != C_OK)
return;
if (unit == UNIT_SECONDS) when *= 1000;
when += basetime;
/* No key, return zero. */
if (lookupKeyWrite(c->db,key) == NULL) {
addReply(c,shared.czero);
return;
}
/* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
* should never be executed as a DEL when load the AOF or in the context
* of a slave instance.
*
* Instead we take the other branch of the IF statement setting an expire
* (possibly in the past) and wait for an explicit DEL from the master. */
if (when <= mstime() && !server.loading && !server.masterhost) {
robj *aux;
int deleted = server.lazyfree_lazy_expire ? dbAsyncDelete(c->db,key) :
dbSyncDelete(c->db,key);
serverAssertWithInfo(c,key,deleted);
server.dirty++;
/* Replicate/AOF this as an explicit DEL or UNLINK. */
aux = server.lazyfree_lazy_expire ? shared.unlink : shared.del;
rewriteClientCommandVector(c,2,aux,key);
signalModifiedKey(c->db,key);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
addReply(c, shared.cone);
return;
} else {
setExpire(c,c->db,key,when);
addReply(c,shared.cone);
signalModifiedKey(c->db,key);
notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);
server.dirty++;
return;
}
}
/* EXPIRE key seconds */
void expireCommand(client *c) {
expireGenericCommand(c,mstime(),UNIT_SECONDS);
}