Appearance
05|过期与内存管理
Redis 是内存数据库,数据全部放内存。这带来一个根本问题:内存是有限的,存进去的 key 永远不清理迟早撑爆。所以 Redis 有两套机制管内存——过 key 设过期时间让它自动消失、内存满了按策略淘汰。
这篇讲两件事:怎么给 key 设过期、内存满了怎么淘汰。前提是已经会基本操作(前面讲过 EXPIRE、SET EX)。
一、过期机制:key 到期怎么删
上一篇讲过怎么给 key 设过期:EXPIRE、SET key value EX seconds。但 key 到了过期时间,是怎么被删掉的?Redis 用两种机制配合:
惰性删除——访问 key 时检查一下,过期了就删。这是被动的,没有访问就没人检查。
定期删除——后台每隔一段时间随机抽样一批 key,发现过期的就删。这保证没人访问的过期 key 也会被清理,不至于一直占内存。
两种机制配合的结果:key 到期不一定会被立刻删掉。没人访问、又没被定期扫描抽样到的过期 key 会暂时留着,等某次访问或扫描再删。
后果一:内存里其实有不少"过期了但还没被清理"的 key,看 INFO keyspace 的 expires 和 keys 差值、avg_ttl 能看出端倪。
后果二:大量 key 集中在同一秒过期(比如整点建的缓存全在同一时刻到期),定期删除会短时间消耗明显 CPU,可能拖慢正常命令。规划时别让大量 key 同时过期——加随机抖动:
bash
# 让过期时间分散在 300-360 秒之间,避免雷同
EXPIRE cache:user:1001 $((300 + RANDOM % 60))这是 Redis 限流和缓存设计的常见技巧,叫"过期抖动"。
二、过期和持久化的关系
RDB 和 AOF 的细节在下一篇专门讲,这里只带过期的关系。
设了过期的 key,重启后还在不在?
RDB 快照里:过期信息随 key 一起存。重启加载后 key 还在、带过期时间,到期会被正常淘汰。但 RDB 只保留快照那一刻还存在且未过期的 key——已经过期的不会再出现在快照里。
AOF 里:每条 EXPIRE 命令都会被记录,重放时重新设过期。考虑到期前能否重放完——AOF 重放期间 key 可能被访问,过期机制照常工作。
一个要知道的边角:RDB 快照时机和 key 是否过期有关。BGSAVE 把当下未过期的 key 写盘,过期的不写。所以从 RDB 恢复后,那批"快照时还没过期、但现在已过期"的 key 也回来了,但带着过期时间,会按上面机制清理。
三、内存上限:maxmemory
内存有限,必须给 Redis 一个使用上限:
ini
maxmemory 2gbmaxmemory 是 Redis 使用内存的上限,达到后按淘汰策略处理新写入。不设的话 Redis 会一直吃内存直到系统 OOM,那时整个机器垮——生产必须设。
看当前用的对不对:
bash
redis-cli CONFIG GET maxmemory
redis-cli INFO memory | grep used_memory_humanmaxmemory 单位用字节或带 K/M/G 后缀,配置文件里写成 2gb 也行。
设多少合适:给 Redis 留出"机器总内存 - 系统占用 - 其他进程"的 60-70%。如果机器是 Redis 专用,留 20% 给系统、备份、AOF 重写就够了。
四、淘汰策略:内存满了怎么办
内存到了上限,新数据要写入时怎么决定删谁?maxmemory-policy 决定:
| 策略 | 行为 | 适合 |
|---|---|---|
noeviction | 不淘汰,写入直接报错 | 不能丢任何数据 |
allkeys-lru | 所有 key 中淘汰最近最少使用的 | 纯缓存,都可丢 |
volatile-lru | 只在设了过期的 key 中淘汰 | 永久数据 + 缓存混合 |
allkeys-lfu | 淘汰访问频率最低的(7.4 推荐) | 访问模式偏斜的缓存 |
volatile-ttl | 淘汰 TTL 最短的 | 希望长 TTL 优先保留 |
allkeys-random | 随机淘汰 | 访问均匀的缓存 |
选策略的核心问题:Redis 里的数据,哪些可以丢?
- 全部可以丢(纯缓存)→
allkeys-lru,绝大多数缓存场景都适用,不确定就选它 - 一部分不能丢(缓存 + 持久数据混在一台)→
volatile-lru。但前提是:不能丢的 key 没设过期、缓存的都设了过期,约定要在团队统一好,否则保护形同虚设 - 都不能丢(存的是业务状态如会话、计数)→
noeviction,但要做好"写入偶尔报错"的心理准备
五、看淘汰有没有发生
bash
redis-cli INFO stats | grep evicted_keys
redis-cli INFO stats | grep expired_keys两个数要区分清楚:
evicted_keys:因为内存满了被 maxmemory 策略踢掉的 key。allkeys-lru下这是预期行为,纯缓存的实例会有这个数正常增长。expired_keys:因为 TTL 到期被过期机制删掉的 key。这是正常的过期删除,和内存满没关系。
混在一起的判断:纯缓存的实例 evicted_keys 在涨,要看缓存命中率有没有掉——掉得多说明内存不够用,该扩内存了。业务状态实例 evicted_keys > 0 是在丢数据,要立即处理。
六、内存分析
内存报警时,几个命令定位问题。
看总体:
bash
redis-cli INFO memory关键字段:
| 字段 | 含义 |
|---|---|
used_memory | Redis 分配器统计的已用内存(不含碎片) |
used_memory_rss | 操作系统看到的该进程实际占用(含碎片) |
mem_fragmentation_ratio | rss / used_memory,大于 1.5 说明碎片严重 |
maxmemory | 配置的上限 |
used_memory 稳定但 rss 一直在涨,mem_fragmentation_ratio 高——是内存碎片。Redis 释放 key 后内存归 allocator 池不一定还给 OS,长短期 key 混存的实例常有。能重启的话重启重置碎片(但这是有损操作)。Redis 4+ 有 activedefrag 主动整理碎片的选项,但效果不如重启彻底。
used_memory 自己一直涨,keys 数量也在涨——是真实数据增长。看是哪些类型的 key 在增多:INFO keyspace、SCAN 抽样,找没设过期的"僵尸 key"。
看 key 空间分布:
bash
redis-cli INFO keyspace
# db0:keys=120000,expires=90000,avg_ttl=3600000keys 是当前 key 总数,expires 是其中设了过期的。keys 大但 expires 极小,说明大部分 key 没设过期——这些没过期的 key 是吃内存的主因。要么是有意存的永久数据,要么是忘设过期的缓存慢慢累积,某天就 OOM。
七、找没有过期的 key
内存涨但找不到元凶时,scan 一下找没设过期的 key:
bash
redis-cli --scan --pattern 'cache:*' | head -20然后对每个怀疑的 key 看 TTL:
bash
redis-cli TTL cache:user:1001
# -1 表示永久(没有过期),-2 表示 key 不存在-1 的多,说明这一类 key 没设过期,要么主动改设过期,要么排查应用代码为什么没设 TTL。
八、一个完整的内存排查思路
内存报警,按这个顺序查:
INFO memory:看used_memory和rss谁涨——是数据增长还是碎片INFO keyspace:看keys和expires比例——大量无过期的 key 是元凶INFO stats:看evicted_keys是否在涨——确认是否已经触顶淘汰- SCAN 找具体的"僵尸 key"类
- 决定:补过期时间、扩 maxmemory、还是清僵尸 key
每一步用一条命令拿到一个事实,不要凭感觉猜。
过期和淘汰是管理内存的两个机制:用 EXPIRE 让 key 自然消失、用 maxmemory + 淘汰策略防止内存撑爆。volatile-lru、allkeys-lru、noeviction 三种策略按"哪些数据可丢"区分场景。INFO memory、INFO keyspace、evicted_keys/expired_keys 是内存排查的入口。
下一篇讲持久化——内存数据怎么存盘不死。