根本原因是cow导致rss内存暴涨触碰maxmemory上限而被迫淘汰;bgsave时fork子进程触发copy-on-write,父进程修改内存页即复制物理页,临近maxmemory时瞬时内存增长直接触发淘汰。

为什么bgsave会突然拉高evicted_keys计数
根本不是淘汰策略变激进了,而是fork()触发的Copy-On-Write让RSS内存瞬间突破maxmemory——淘汰是结果,不是原因。子进程创建后,父子共享物理页;一旦主线程修改任意一个被引用的内存页(比如INCR一个计数器、HSET一个字段),内核就得复制整页(通常4KB),哪怕只改1字节。若此时Redis已占用95%+的maxmemory,几页复制就足以触发淘汰逻辑。
哪些写操作在bgsave期间最危险
高频小更新和大value部分写,表面轻量,实则COW开销巨大:
-
INCR/DECR类计数器操作:每调用一次都可能触发一页复制,尤其当key分布在不同内存页时 -
HSET单个字段(对10MB HASH):只改一个field,但整个HASH对象所在页被标记为“需写入”,COW立即发生 -
DEL+SET替换大key:旧对象未完成lazyfree释放,新对象又占内存,COW叠加内存双占 - 大量
EXPIRE或PEXPIRE:修改key元数据也会触碰内存页
如何验证当前是否正受COW内存冲击
别只看used_memory,要盯住mem_rss和evicted_keys的联动变化:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 执行
INFO memory,对比used_memory和mem_rss:若mem_rss比used_memory高出30%以上,且bgsave_in_progress为1,大概率正在COW膨胀 - 观察
evicted_keys是否在bgsave启动后1–2秒内陡增,同时instantaneous_ops_per_sec无明显上涨 - 检查
latest_fork_usec是否较大(>100ms),说明fork耗时长,也意味着内存页表复杂,COW压力更大
生产环境最有效的缓解手段
不靠调策略,而靠控内存冗余和写行为:
- 预留至少20%内存冗余:把
maxmemory设为物理内存的75%~80%,而非95%+ - 禁用
stop-writes-on-bgsave-error yes:避免因COW失败直接拒绝写入,改用noeviction保服务可用性 - 对高频小key,改用
INCRBYFLOAT批量聚合,或迁移到Redis Streams降低写频次 - 大value改用
JSON.SET+$.路径更新(Redis 7.0+),比HSET更细粒度,减少页污染
COW不是bug,是Linux内核的通用机制;问题出在把Redis当纯缓存用,却没给它留够“喘气”的物理页空间。监控mem_rss比盯着evicted_keys更能提前发现问题。










