当前位置:首页 > 问答 > 正文

用Redis怎么搞自动清理,设置啥的那些技术细节和实践分享

关于用Redis怎么搞自动清理,说白了就是怎么让Redis自己把那些没用的、过期的数据扔掉,别把内存撑爆了,这事儿主要靠两个东西:一个是Redis自己带的过期键功能,另一个是淘汰策略,下面我结合一些网上常见的讨论和实践(比如来自Redis官方文档、一些技术博客像Antirez的博客、Stack Overflow上的高赞回答、以及像《Redis设计与实现》这类书籍中的观点)来细说。

第一,先说说怎么给数据设置“死期”。

Redis最贴心的一个功能就是可以给任何一个键(key)设置一个存活时间,这就像给食品贴上保质期,到时候自动下架,具体命令很简单:

  • EXPIRE key seconds:给一个键设置多少秒后过期。
  • SET key value EX seconds:在设置值的同时直接指定过期秒数,这是一步到位的做法,更常用。
  • 对于毫秒级精度,有 PEXPIREPEXPIREAT

你想存一个验证码,5分钟后失效,就直接用 SET verification_code:13800138000 123456 EX 300,这样,300秒后,这个键就会自动被Redis清理掉,这是实现自动清理最基础、也是最有效的手段,根据Redis官方文档的说法,这是首选方案,因为对性能影响最小。

第二,关键来了,Redis是怎么在后台悄悄把这些过期键给删掉的?

你可能会想,Redis是不是有个后台线程一直在扫描所有键,看谁过期了就删谁?不是的,那样太笨了,如果键特别多,扫描一遍会卡死,Redis用的是两种策略结合的懒人方法,核心思想是“用到的时候才检查,定期再稍微扫一下”。

  1. 惰性删除:这是第一道防线,当客户端尝试去读取一个键的时候,Redis才会顺便检查一下这个键是否过期了,如果过期了,就当场删除,然后返回一个空值给客户端,这个策略的好处是,它只会在真正有请求的时候才干活,节省CPU,但坏处也很明显:如果一个过期的键永远没人来访问,那它就永远占着内存,成了“僵尸键”,这就引出了第二个策略。

  2. 定期删除:为了解决“僵尸键”的问题,Redis会每隔一段时间(默认是每秒10次,这个配置叫hz)就随机抽取一部分设置了过期时间的键(默认是20个键,这个配置是ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP),检查它们是否过期,过期的就删除,如果这一批里面过期的键比例很高(超过25%),它就立刻再随机抽一批继续删,直到过期比例降下来,这个策略相当于一个后台保洁阿姨,定期来巡检一下,看到垃圾就收走,这个机制在Redis的作者Salvatore Sanfilippo(Antirez)的博客和设计文档里都有详细解释。

通过这两种方式结合,大部分过期键都能被及时清理掉,但这里有个重要的点:定期删除是随机抽样的,所以它不能保证100%实时清除所有过期键,总会有一小部分过期键残留在内存里,直到下次被抽样抽中或者被客户端访问到。

第三,当内存满了怎么办?光靠过期还不够,得动用“淘汰策略”。

即使你给所有键都设置了过期时间,也可能出现一种情况:在某一时刻,所有键都还没到期,但新的数据已经写不进来了,因为内存达到了上限(通过maxmemory参数设置),这时候,Redis的“内存淘汰机制”就登场了,你需要告诉Redis,当内存不够时,应该扔掉哪些数据来腾地方,这就是 maxmemory-policy 配置项,它有几种常见的策略:

  • noeviction:这是默认策略,直接报错,拒绝所有写入操作,这是最保守的,能保证已有的数据不丢,但服务可能就不可用了。
  • allkeys-lru:这是最常用、最推荐的策略之一,它会尝试淘汰所有键中最近最少使用的(LRU算法),不管这个键有没有设置过期时间,只要最近用得少,就优先被踢出去。
  • volatile-lru:只从那些设置了过期时间的键中,淘汰最近最少使用的。
  • allkeys-random:从所有键中随机淘汰。
  • volatile-random:只从设置了过期时间的键中随机淘汰。
  • volatile-ttl:这个比较有意思,它只淘汰设置了过期时间的键,并且优先淘汰剩余存活时间(TTL)最短的那个,这就像是把快过期的食品先扔掉。

选择哪个策略,完全取决于你的业务,如果你的数据重要程度都差不多,只是做缓存,用 allkeys-lru 很好,如果你有些关键数据是永久的,有些是临时缓存,那可以用 volatile-lruvolatile-ttl 来保护永久数据不被误删,Stack Overflow上很多答案都强烈建议生产环境一定要设置 maxmemory 和一个非 noeviction 的策略,否则一旦内存占满,Redis就会开始写错误日志并拒绝服务。

一些实践中的细节和坑:

  1. DEL命令是阻塞的:如果你手动用DEL删除一个非常大的键(比如一个包含百万元素的Hash),可能会导致Redis卡顿一下,因为删除操作是主线程处理的,对于大键,建议用UNLINK命令(Redis 4.0以上),它会在后台异步删除,不影响当前请求。
  2. 小心持久化时的过期键:当Redis生成RDB快照时,过期的键不会被保存进去,但是在AOF日志重写时,即使键已过期,如果它还没被惰性删除或定期删除触发,它仍然会存在于旧AOF日志中,当重写完成后,Redis会生成一个新的AOF文件,这时才会检查并忽略掉过期的键,这个细节在《Redis设计与实现》书里有提到。
  3. 监控是关键:你一定要监控Redis的内存使用情况(used_memory)和键空间信息(通过info memoryinfo stats命令),可以关注expired_keys这个指标,它告诉你通过定期删除和惰性删除一共清理了多少键,如果这个数字增长很慢,但内存一直在涨,可能意味着你的过期键设置有问题,或者定期删除的力度不够(可以适当调高hz值,但会增加CPU负担)。

用Redis搞自动清理,核心三板斧是:给数据合理设置TTL过期时间 + 理解并依靠Redis自身的惰性删除与定期删除机制 + 配置一个符合业务场景的内存淘汰策略,把这三点做好了,你的Redis基本上就能自己管好自己的“房间”,不会轻易因为内存问题而崩溃。

用Redis怎么搞自动清理,设置啥的那些技术细节和实践分享