键数量描述数据库中键的个数,不描述每个值的大小、内部表示或进程持有的内存页。键数平稳而内存曲线上升,常见原因是对象内容持续扩张、对象编码转换、过期数据回收存在时间差,或分配器产生并保留了碎片。排查应使用同一时间窗口的多项指标,先确认逻辑数据变化,再区分回收与物理内存行为。
Redis 会根据对象类型、元素数量和元素长度选择内部编码。小型哈希、列表或集合可能采用紧凑表示;超过相关阈值后转为更通用的表示,额外的指针和元数据会提高开销。编码转换通常是正常行为,并不意味数据损坏。即使编码不变,字符串变长、集合成员增加或哈希字段不断累积,也会在键数不变时推高占用。
按固定间隔记录 DBSIZE、各库键数和 INFO memory。对业务允许的代表性键检查类型、逻辑长度及 OBJECT ENCODING,把样本按类型和编码分组,对比增长前后分布。不要在繁忙实例上为了排查而无节制逐键扫描;采用渐进扫描、限速,并从已知业务键或小样本开始。若大量对象同时转换编码,核对写入模式、集合规模及生效的编码阈值;若只有特定对象增长,重点审视对应字段或成员是否无界累积。
TTL 到期并不保证键会在那个时刻立刻从内存中消失。惰性删除通常在访问过期键时发生,主动过期则由后台周期性处理。冷键因此可能在 TTL 到期后仍短暂占用内存。结合 INFO keyspace 的键数、过期键数,以及 INFO stats 的过期和淘汰计数,观察它们与内存斜率是否同步。过期键比例高且内存释放滞后时,抽样核对 TTL,并检查更新路径是否续设过期时间、过期处理是否承受额外负荷。
过期键计数不是尚未释放字节数的精确估算:键的大小各不相同,指标也有采样和处理时机差异。会话缓存等有明确生命周期的数据可以设置符合业务语义的期限;需要长期保存的数据不能只为降低内存而随意增加 TTL。不要通过批量改 TTL 或删键制造测试结果,这会改变线上数据语义。
比较 used_memory、used_memory_rss、内存碎片率及分配器相关统计。前者反映 Redis 记账的内存,RSS 体现进程驻留的物理页。对象分配和释放后,分配器可能保留页面供后续复用,未必立即归还操作系统;不同大小对象频繁变化也可能留下难以复用的空洞。若逻辑占用趋稳而 RSS 仍高、碎片指标同时偏高,才有理由进一步调查分配器行为。
建立基线:用固定采样间隔记录键数、内存、RSS、过期与淘汰计数,并标记流量和部署变化。
核实对象:限速抽样检查类型、长度和编码,比较增长对象的分布与业务写入字段,寻找少数异常大对象或整体编码变化。
检查 TTL:抽样查看剩余 TTL,对照应用的创建、更新路径和 keyspace 变化,确认本应过期的数据确实持续获得 TTL。
评估碎片:持续对比 used_memory 与 RSS,不用单次比值下结论;检查 allocator 统计和缓冲区占用,识别物理页保留与数据增长的差别。
小步修正:根据证据限制无界集合、修正应用 TTL 设置,或在评估版本、延迟及峰值风险后安排内存整理。变更分批进行并保留回退条件。
键数稳定不代表值大小稳定;应同时追踪对象长度和编码分布。
TTL 到期不等于瞬间释放;应结合过期计数、访问情况和内存趋势判断。
RSS 短期不降不等于修复失败;分配器可能保留已释放页面供复用。
不要直接重启或触发大规模整理作为首个动作,它们可能造成延迟、复制流量或额外内存峰值。
验收时,在相同负载窗口比较键数、对象样本、TTL 状态、过期与淘汰计数、used_memory、RSS 和碎片指标。连续多个采样周期中,内存增长斜率应符合预期;同时确认命中率、延迟与淘汰行为没有因修正而恶化。最终结论应能说明增长来自对象、过期回收还是物理页,并由至少两类相互独立的观测支持。🎯