Redis过期键未及时删除主因是hz值过低,导致周期性抽样清理频率不足;建议调至100并验证expired_keys增速与内存稳定性。

Redis过期键没被及时删除,hz 设置太低是常见原因
Redis 不是每次操作都扫描过期键,而是靠周期性抽样清理,频率由 hz 配置控制。默认 hz 10 表示每秒最多执行 10 次过期检查,但实际执行次数还受 CPU 负载、键数量和分布影响。当缓存写入密集、大量键集中在相近时间过期时,低 hz 值会导致过期键堆积,TTL 返回正值但对应键已逻辑失效——应用读到空值或旧值。
实操建议:
-
hz可安全调高至100(最大推荐值),尤其在内存压力大、过期键多的场景;但不要盲目设为1000,会显著增加主线程 CPU 占用,可能影响响应延迟 - 调整后必须重启 Redis 或使用
CONFIG SET hz 100动态生效(注意:部分云托管 Redis 实例禁用该命令) - 验证是否生效:用
INFO stats查看expired_keys和evicted_keys的增长速率,再对比INFO memory中mem_used_human是否趋于稳定
maxmemory-policy 设为 noeviction 时,过期机制仍运行但内存不会释放
很多人误以为 maxmemory-policy noeviction 会让 Redis 完全不处理过期键,其实不然:过期判断和惰性删除(访问时检查)依然存在,但主动淘汰(active expiry)不会触发内存回收。结果就是——过期键残留、内存持续上涨、最终 OOM command not allowed when used memory > 'maxmemory' 报错。
实操建议:
- 除非业务能严格保证写入量可控且所有键都有明确 TTL,否则避免使用
noeviction - 更稳妥的选择是
allkeys-lru或volatile-lru,前者对所有键做 LRU 淘汰,后者只淘汰带过期时间的键,二者都会配合过期策略协同释放内存 - 若必须保留
noeviction,需搭配外部监控(如redis-cli --stat或 Prometheus + redis_exporter)实时跟踪used_memory_peak_human,提前告警扩容
混合使用 EXPIRE 和 PEXPIRE 导致毫秒级过期键被忽略
Redis 内部过期时间统一以毫秒存储,但 EXPIRE 接收秒级参数,PEXPIRE 接收毫秒级参数。如果代码中混用两者,且未对齐单位(比如把毫秒值传给 EXPIRE),会导致键被设为「数千年之后过期」,等效于永不过期。
典型错误现象:TTL key 返回一个极大正整数(如 2147483647),KEYS * 能查到,但应用始终读不到新值。
实操建议:
- 统一使用
PEXPIRE+ 毫秒时间戳(如System.currentTimeMillis() + 30000),避免单位混淆 - 在客户端 SDK 层封装过期设置逻辑,强制校验输入范围(例如拒绝大于
3600000的秒值) - 上线前用
redis-cli --scan --pattern "*"配合TTL批量抽查,快速识别异常长 TTL 键
从 redis.conf 到运行时,maxmemory-policy 和 hz 的优先级关系
Redis 启动时读取 redis.conf 加载初始配置,但运行时可通过 CONFIG SET 修改多数参数。需要注意的是:maxmemory-policy 改动立即生效,而 hz 修改后,**当前正在执行的过期扫描周期不会中断重算**,要等下一轮调度才应用新频率——这意味着即使 CONFIG SET hz 100 成功,也可能有最多 100ms 延迟才看到效果。
实操建议:
- 修改
hz后别只看命令返回 OK,要用CONFIG GET hz确认值已更新,并观察INFO stats中expired_keys的增量节奏是否变快 - 生产环境变更前,先在同规格从节点上验证:改完
hz后模拟批量写入+过期,确认mem_used_human不再持续爬升 - 云服务(如阿里云 ApsaraDB、腾讯云 CRS)可能锁定部分
CONFIG SET权限,此时只能提工单修改或重建实例——这点容易被忽略
实际调试时,最常卡在「以为策略改了就立刻生效」,其实 Redis 的过期清理是概率性+分片式推进的,既要看配置,也要看数据分布和负载节奏。











