Redis针对过期key的删除策略

Redis使用三种策略处理过期key:定时删除、惰性删除和定期删除。定时删除在key过期时立即删除,最节省内存但可能消耗较多CPU时间。惰性删除在获取key时检查并删除过期key,最节省CPU时间但可能导致内存浪费。定期删除则是两者折中,按固定周期检查并删除过期key,防止过度消耗CPU和内存。Redis实际采用惰性和定期删除策略结合,平衡资源使用。

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

Redis数据库键的过期时间都保存在过期字典中,可使用exit key 或ttl key 或者pttl key命令判断key是否过期。若内存中存放的key-value对较多,Redis应该如何才能保证不影响查询效率又能控制其中的有效缓存?(尽可能保证Redis中存放的key是未过期的key)。
这个问题有三种可能的答案,分别代表了三种不同的过期删除策略:

  • 定时删除:在设置key的过期时间的同时,创建一个定时器,让定时器在key的过期时间来临时,立即执行对key的删除操作;
  • 惰性删除:放任key过期不管,但是每次从key空间中获取key时,都检查取得的key是否过期,如果过期的话,就删除该key,如果没有过期就返回该key;
  • 定期删除:每隔一段时间程序就对数据库进行一次检查,删除里面的过期key,至于要删除多少过期key,以及要检查多少个数据库,则由算法决定。
  • 在这三种策略中,第一种和第三种是主动删除策略,第二种是被动删除策略。

1.定时删除:

定时删除策略对内存是最友好的:通过使用定时器,定时器删除策略可以保证过期key会尽可能快的被删除,并释放过期key所占用的内存。
另一方面定时删除策略缺点是,它对CPU时间是最不友好的:在过期key较多的情况下,删除过期key这一行为可能会占用相当一部分CPU时间,在内存不紧张但是CPU时间非常紧张的情况下,将CPU时间用在删除和当前任务无关的过期key上,无疑会对服务器的响应时间和吞吐量造成影响。

2.惰性删除:

惰性删除策略对CPU时间来说是最友好的:程序只会在取出key时才对key进行过期检查,这里可以保证删除过期key的操作只会在非做不可的情况下进行,并且删除的目标仅限于当前处理的key,这个策略不会在删除其他无关的过期key上花费任何CPU时间。
惰性删除策略的缺点是:它对内存是最不友好的:如果一个key已经过期,而这个key有仍然保留在数据库中,那么只要这个过期key不被删除,它所占用的内存就不会释放。
在使用惰性删除策略时,如果数据库中有非常多的过期key,而这些过期key又恰好没有被访问到的话,那么它们也许永远也不会被删除(除非用户手动执行FLUSHDB),我们甚至可以将这种情况看作是一种内存泄露——无用的垃圾数据占用大量内存,而服务器不能自己去释放他们,对于运行状态非常依赖内存的Redis服务器来说肯定不是一个好消息。
2.1惰性删除策略的实现:
过期key的惰性删除策略由db.c/expireIfNeeded函数实现,所有读写该数据库的Redis命令在执行前都会调用expireIfNeeded函数对输入key进行检查:

  • 如果输入key已经过期,那么expireIfNeeded函数将输入key从数据库中删除;
  • 如果输入key未过期那么expireIfNeeded函数不做任何操作。
    调用expireIfNeeded函数过程如下:
    这里写图片描述
    expireIfNeeded函数就像一个过滤器,它可以在命令真正执行前,过滤掉过期的输入key,从而避免命令接触到过期key。另外因为每个被访问的key都可能因为过期而被expireIfNeeded函数删除,所以每个命令的实现函数都必须能同时处理key存在以及不存在的情况:
  • 当key存在时:命令按照key存在的情况执行;
  • 当key不存在或者因为过期而被expireIfNeeded函数删除时,命令按照key不存在的情况执行。

3.定期删除:

定期删除策略是定时删除策略和惰性删除策略的一个折中,既兼顾了内存占用又兼顾CPU时间,但是该策略的难点是确定删除操作执行的市场和频率:
1)如果删除操作执行的太频繁,或者执行时间太长,定期删除策略就会退化成定时删除策略,以至于将CPU时间过多的消耗在删除过期key上面;
2)如果删除操作执行的太少,或者执行的时间太短,定期删除策略又会和惰性删除策略一样,出现浪费内存的情况。
3.1定期删除策略的实现:
定期删除策略的实现由expire.c/activeExpireCycle函数实现。每当Redis的服务器周期性执行server.c/activeExpireCycle函数时,activeExpireCycle函数就会被调用,它在规定的时间内,分多次遍历服务器中的各个数据库,从数据库的expires字典中随机检查一部分key的过期时间,并删除其中的过期key。整个过程可用伪代码描述如下:

#默认每次检查的数据库数量
DEFAULT_DB_NUMBERS = 16
#默认每个数据库检查的键数量
DEFAULT_KEY_NUMBERS = 20
#全局变量,记录检查进度
current_db = 0
def activeExpireCycle():
#初始化要检查的数据库数量
#如果服务榕的数据库数量比DEFAULT DB NUMBERS 要小
#那么以服务器的数据库数量为准
if server.dbnum < DEFAULT_DB_NUMBERS :
db_numbers = server.dbnum
else :
db_numbers = DEFAULT_DB_NUMBERS
#遍历各个数据库
for i in range(db_numbers) :
#如果current_db 的值等于服务榕的数据库数量
#这表示检查程序已经遍历了服务榕的所有数据库一次
#将current_db重置为0 ,开始新的一轮遍历
if current_db == server.dbnum:
current_db = 0
#获取当前要处理的数据库
redisDB = server.db[current db)
#将数据库索引增1 ,指向下一个要处理的数据库
current_db += 1
#检查数据库键
for j in range(DEFAULT_KEY_NUMBERS):
#如果数据库中没有一个键带有过期时间,那么跳过这个数据库
if redisDB.expires.size () == 0: break
# 随机获取一个带有过期时间的键
key with_ttl = redisDb.expires.get_random_key ()
#检查键是否过期,如果过期就删除它
if is_expired (key with ttl):
delete_key (key_with_ttl )
#已达到时间上限,停止处理
if reach_time_limit(): return

activeExpireCycle函数工作模式可总结如下:

  • 函数每次运行时,都从一定数量的数据库中取出一定数量的随机key进行检查并删除其中的过期key;
  • 全局变量current_db会记录当前activeExpireCycle函数检查的进度,并在下一次activeExpireCycle被调用时接着上一次的进度进行处理。
  • 随着activeExpireCycle函数的不断执行,服务器中的所有数据库都会被检查一遍,这时函数将current_db变量重置为0,然后再次开始新一轮的检查工作。

    在调用定期删除策略时需要传个type参数,用以区分是用“快速模式”还是“正常模式”。
    在“正常模式”下,会遍历每一个编号下的库,然后最多随机获取20个带过期时间的key(20是宏ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP的默认值),倘若key太多则直接return了。接着调用redis.c/activeExpireCycleTryExpire函数尝试去删除它,能删除成功则再发送expired的pub通知给订阅者即可。定期删除的定时时长是100ms.

在Redis服务器实际使用的是惰性删除策略和定期删除策略两种策略:通过配合使用这两种删除策略,服务器可以很好的在合理使用CPU时间和避免浪费内存空间之间取得平衡。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值