<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>内存淘汰 on 止语Lab</title>
        <link>https://www.wujiachen.com.cn/tags/%E5%86%85%E5%AD%98%E6%B7%98%E6%B1%B0/</link>
        <description>Recent content in 内存淘汰 on 止语Lab</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-cn</language>
        <lastBuildDate>Sun, 28 Jun 2026 10:59:27 +0800</lastBuildDate><atom:link href="https://www.wujiachen.com.cn/tags/%E5%86%85%E5%AD%98%E6%B7%98%E6%B1%B0/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>Redis 过期策略 vs 内存淘汰：你分清了吗？</title>
            <link>https://www.wujiachen.com.cn/posts/redis-expiry-eviction/</link>
            <pubDate>Fri, 26 Jun 2026 23:37:33 +0800</pubDate>
            <guid>https://www.wujiachen.com.cn/posts/redis-expiry-eviction/</guid>
            <description>&lt;img src=&#34;https://img.wujiachen.com.cn/redis-expiry-eviction/cover.png&#34; alt=&#34;Featured image of post Redis 过期策略 vs 内存淘汰：你分清了吗？&#34; /&gt;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-expiry-eviction/cover.png&#34; alt=&#34;Redis 过期 vs 淘汰概念辨析&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;你有没有想过一个问题：你给 Redis 的一个 key 设置了 5 分钟过期，5 分钟到了，这个 key 真的被删掉了吗？&lt;/p&gt;&#xA;&lt;p&gt;直觉告诉你：当然删了，不然设置过期干嘛。&lt;/p&gt;&#xA;&lt;p&gt;但 Redis 的实际情况是：&lt;strong&gt;不一定&lt;/strong&gt;。如果你在这 5 分钟内再也没有访问过这个 key，它可能还在内存里躺着，等下一次被访问才被清理。&lt;/p&gt;&#xA;&lt;p&gt;或者更极端的情况：内存满了，它还没被访问，结果它和其他 key 一起被&amp;quot;淘汰&amp;quot;了。&amp;ldquo;淘汰&amp;quot;和&amp;quot;过期&amp;quot;是两回事，后面会详细讲。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-expiry-eviction/ch1-timeline.png&#34; alt=&#34;Redis key 的时间线&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;我遇到过类似的问题。线上有个服务用 Redis 缓存用户 session，所有 key 统一设置 8 小时过期。结果某天下午 3 点，接口 P99 从 20ms 飙升到 2 秒，CPU 跳到 95%。查了半天才发现：20 万个 session key 在同一时间过期，后台的&amp;quot;扫地机器人&amp;quot;忙不过来，大量过期 key 占了内存，淘汰器一触发，反而把还在活跃的 key 给淘汰了。这个案例我做了简化，但问题的链条是真实的。&lt;/p&gt;&#xA;&lt;p&gt;你看，&amp;ldquo;过期&amp;quot;和&amp;quot;淘汰&amp;quot;两套机制在这里都起作用了。但不是你以为的那个方式。&lt;/p&gt;&#xA;&lt;p&gt;很多人把 Redis 的过期策略和内存淘汰机制混为一谈，面试的时候能说出一堆策略名字，但问&amp;quot;它们什么关系&amp;quot;就卡住了。这篇文章的目的很简单：帮你把这两套机制拆开看，再看它们怎么配合。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;一过期策略两把尺子量到底&#34;&gt;&lt;a href=&#34;#%e4%b8%80%e8%bf%87%e6%9c%9f%e7%ad%96%e7%95%a5%e4%b8%a4%e6%8a%8a%e5%b0%ba%e5%ad%90%e9%87%8f%e5%88%b0%e5%ba%95&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;一、过期策略：两把尺子量到底&#xA;&lt;/h2&gt;&lt;p&gt;过期策略解决一个问题：&lt;strong&gt;设了 TTL 的 key，时间到了怎么删&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;注意这个限定——&amp;ldquo;设了 TTL 的 key&amp;rdquo;。如果你没设过期时间，过期策略跟你没关系。这个后面还会提到。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;过期策略（Expiry Policy）&lt;/strong&gt;——管理设置了 TTL 的 key 在过期后的删除方式。类比：冰箱里的牛奶标了保质期，过期了不一定立刻扔，但要喝的时候会检查。&lt;/p&gt;&#xA;&lt;p&gt;Redis 用了两把尺子：一把叫&amp;quot;惰性删除&amp;rdquo;，一把叫&amp;quot;定期删除&amp;rdquo;。一懒一勤，配合干活。&lt;/p&gt;&#xA;&lt;h3 id=&#34;11-惰性删除你不碰我我就不删&#34;&gt;&lt;a href=&#34;#11-%e6%83%b0%e6%80%a7%e5%88%a0%e9%99%a4%e4%bd%a0%e4%b8%8d%e7%a2%b0%e6%88%91%e6%88%91%e5%b0%b1%e4%b8%8d%e5%88%a0&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;1.1 惰性删除——你不碰我，我就不删&#xA;&lt;/h3&gt;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;下面所有实验都在本地 Docker Redis 7.0（macOS）上运行，使用默认配置，除非特别说明。实验数据可能因环境差异略有不同。&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;我跑了一组实验来验证惰性删除的实际行为。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;redis-cli SET mykey &lt;span class=&#34;s2&#34;&gt;&amp;#34;hello&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;redis-cli EXPIRE mykey &lt;span class=&#34;m&#34;&gt;5&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后等了 6 秒，肯定过期了对吧？但这个时候，Redis 并没有主动删掉这个 key。如果你立刻查 &lt;code&gt;EXISTS mykey&lt;/code&gt;，结果取决于你有没有&amp;quot;碰&amp;quot;它。&lt;/p&gt;&#xA;&lt;p&gt;我做了两组对比：&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;场景一：过期后立刻访问（GET）&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SET mykey &amp;#34;hello&amp;#34; + EXPIRE 5&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;→ 等待 6 秒&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; GET mykey → (nil)  ← 被删了&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; EXISTS mykey → 0    ← 确实没了&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;场景二：过期后不访问，直接用 EXISTS 检查&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SET lazykey &amp;#34;i-will-expire&amp;#34; + EXPIRE 3&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;→ 等待 4 秒&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; EXISTS lazykey → 1   ← key 还在内存里！&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; GET lazykey → (nil)  ← GET 触发了惰性删除&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; EXISTS lazykey → 0   ← 这次真的没了&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;发现了吗？上面的实验揭示��一个细节：&lt;strong&gt;EXISTS 返回了 1，说明它不一定触发惰性删除&lt;/strong&gt;——至少在这个 Redis 版本中是这样。而 GET 确实触发了删除。&lt;/p&gt;&#xA;&lt;p&gt;准确地说，任何读操作（GET、MGET、TTL 等）都会检查被访问的 key 是否过期，过期就删掉再返回。Redis 源码里负责这个检查的函数叫 &lt;code&gt;expireIfNeeded&lt;/code&gt;，你不需要记住这个名字。EXISTS 的行为在不同版本中有差异，旧版本中它可能只检查 key 是否在字典里，不触发过期删除——所以实验中才会出现&amp;quot;EXISTS 返回 1，但随后 GET 返回 nil&amp;quot;的现象。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-expiry-eviction/ch2-lazy-expiry.png&#34; alt=&#34;惰性删除机制&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;但关键问题是：&lt;strong&gt;如果你再也不碰这个 key，它就永远占用内存&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;为了验证这个结论，我写了一个 10KB 的大 key，设 2 秒过期：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SET bigdata &amp;#34;xxxxx...（10KB）&amp;#34; + EXPIRE 2&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;→ 等待 3 秒&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; INFO memory → used_memory: 1.16M  ← 内存还在&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; GET bigdata → (nil)               ← 触发惰性删除&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; INFO memory → used_memory: 1.16M  ← Redis 释放内存需要时间&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;惰性删除释放内存不是即时的，但 key 确实在 GET 时被删了。只是 Redis 的内存分配器不会立刻把内存还给 OS。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;一句话总结惰性删除&lt;/strong&gt;：它只在有人访问时才干活。没人访问的过期 key，它就是视而不见。&lt;/p&gt;&#xA;&lt;p&gt;这听起来很&amp;quot;懒&amp;quot;，但 Redis 这么做有它的道理：如果一个 key 过期后再也没人访问，删除它也是白费 CPU。惰性删除就是&amp;quot;按需清理&amp;quot;：需要读才删，不需要就不删，CPU 成本几乎为零。每次读取操作多一次过期检查而已。&lt;/p&gt;&#xA;&lt;h3 id=&#34;12-定期删除后台的扫地机器人&#34;&gt;&lt;a href=&#34;#12-%e5%ae%9a%e6%9c%9f%e5%88%a0%e9%99%a4%e5%90%8e%e5%8f%b0%e7%9a%84%e6%89%ab%e5%9c%b0%e6%9c%ba%e5%99%a8%e4%ba%ba&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;1.2 定期删除——后台的扫地机器人&#xA;&lt;/h3&gt;&lt;p&gt;但问题来了：如果一个 key 过期后再也没人访问，它就一直占着内存？那缓存雪崩不就是迟早的事？&lt;/p&gt;&#xA;&lt;p&gt;这就是第二把尺子的用武之地。Redis 在后台跑了一个定时任务 &lt;code&gt;activeExpireCycle&lt;/code&gt;（源码里叫这个名字），默认配置（hz=10）下每 100 毫秒执行一次。hz 是可配置的，调大频率就更高。它的工作方式很有意思：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;从设置了过期时间的 key 中随机采样 20 个&lt;/li&gt;&#xA;&lt;li&gt;检查哪些已过期，删除它们&lt;/li&gt;&#xA;&lt;li&gt;如果过期比例超过 25%，就继续扫下一批&lt;/li&gt;&#xA;&lt;li&gt;每次执行有严格时间上限（约 1ms），不会成为主线程瓶颈&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;这个&amp;quot;25% 比例触发继续扫&amp;quot;的自适应机制是关键设计。如果过期 key 比例低，Redis 就少扫几次，省 CPU；如果突然有大量 key 过期，它会持续扫直到比例降下来。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-expiry-eviction/ch3-active-cycle.png&#34; alt=&#34;定期删除自适应算法&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;我跑了三组实验来验证定期删除的能力：&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第一组：1000 个 key 同时过期&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;写入 1000 个 key，全部 TTL=5 秒&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;→ 等待 8 秒&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;→ 过期 key 全部被清理（expired_keys: 1006，含前序实验残留的 6 个）&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; DBSIZE: 0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;第二组：10000 个 key 同时过期&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;写入 10000 个 key，全部 TTL=5 秒&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;→ 等待 10 秒&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;→ 全部清理完毕&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; DBSIZE: 0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;第三组：50000 个 key 同时过期（极端情况）&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;写入 50000 个 key，全部 TTL=5 秒&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;→ 等待 15 秒&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;→ 全部清理完毕&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; DBSIZE: 0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;50000 个 key 同时过期，Redis 在 15 秒内全部清理完毕。这个效率相当高，Redis 是单线程的，5 万个 key 在 15 秒内清理完，相当于每秒处理 3000+ 个 key。&lt;code&gt;activeExpireCycle&lt;/code&gt; 每 100ms 只扫 20 个 key，但它有自适应机制：当过期比例高时，它会持续扫描，不会扫完 20 个就休息。&lt;/p&gt;&#xA;&lt;p&gt;不过，定期删除也有它的限制。每次执行的时间被限制在约 1ms 以内，而且每次只采样 20 个 key。如果你的业务场景是&amp;quot;几百万个 key 在极短时间内同时过期&amp;quot;，定期删除可能还是忙不过来。&lt;/p&gt;&#xA;&lt;p&gt;这里有个关键的设计细节：&lt;code&gt;activeExpireCycle&lt;/code&gt; 默认每 100ms 执行一次（hz=10 时），每次最多运行约 1ms。如果 1ms 内扫完了 20 个 key 且过期比例低于 25%，它就提前结束，等下一个 100ms 周期再跑。这个设计保证了定期删除不会成为 Redis 主线程的瓶颈。&lt;/p&gt;&#xA;&lt;p&gt;惰性删除作为兜底就显得很重要了：如果定期删除没扫到的过期 key，只要有客户端访问它，惰性删除就会出手。两把尺子互为补充。&lt;/p&gt;&#xA;&lt;p&gt;定期删除扫描的是&lt;strong&gt;设置了过期时间的 key 空间&lt;/strong&gt;，不是所有 key。也就是说，如果某个 key 没有设 TTL，定期删除根本不会检查它。这个设计很合理，没设 TTL 的 key 本来就不需要过期检查，何必浪费 CPU。&lt;/p&gt;&#xA;&lt;p&gt;还有一个细节：定期删除每次扫描的 20 个 key 是从&lt;strong&gt;设置了过期时间的 key 字典&lt;/strong&gt;中随机选择的，不是从整个数据库。这意味着如果你的数据库有 100 万个 key，但只有 100 个设置了 TTL，定期删除只会在那 100 个里采样。不会因为你总 key 多就增加扫描范围。&lt;/p&gt;&#xA;&lt;p&gt;所以如果你的业务特征是&amp;quot;大量 key 设了 TTL 但很快就不用了&amp;quot;，定期删除是主要清理手段。而如果你的业务特征是&amp;quot;少量 key 设了 TTL，大部分是无 TTL 的持久数据&amp;quot;，那过期 key 主要靠惰性删除来清理——谁读到了谁删。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;过期策略到这里就讲完了&lt;/strong&gt;。核心记住一点：过期策略只管&amp;quot;设了 TTL 的 key&amp;quot;，它的目标是&amp;quot;在过期后尽快释放内存&amp;quot;，但不是&amp;quot;立即释放&amp;quot;。&lt;/p&gt;&#xA;&lt;p&gt;讲完过期策略，该讲淘汰了。那些&lt;strong&gt;没设 TTL 的 key&lt;/strong&gt;，如果内存满了怎么办？&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;二内存淘汰八把刀选一把&#34;&gt;&lt;a href=&#34;#%e4%ba%8c%e5%86%85%e5%ad%98%e6%b7%98%e6%b1%b0%e5%85%ab%e6%8a%8a%e5%88%80%e9%80%89%e4%b8%80%e6%8a%8a&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;二、内存淘汰：八把刀选一把&#xA;&lt;/h2&gt;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;注意，淘汰机制和过期机制是完全独立的。它的触发条件是&amp;quot;内存满了&amp;quot;，不是&amp;quot;时间到了&amp;quot;。&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;h3 id=&#34;21-八种策略&#34;&gt;&lt;a href=&#34;#21-%e5%85%ab%e7%a7%8d%e7%ad%96%e7%95%a5&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;2.1 八种策略&#xA;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;内存淘汰（Eviction Policy）&lt;/strong&gt;：当 Redis 内存达到 maxmemory 上限时，选择哪些 key 删除来腾出空间。冰箱装不下了，不管有没有过期标签，总要扔点东西才能放进新的。&lt;/p&gt;&#xA;&lt;p&gt;Redis 提供了 8 种淘汰策略，默认是 noeviction——内存满了直接报错，需要你主动配置为其他策略。4.0 之前有 6 种（没有 LFU 系列），4.0 开始补全到 8 种。可以分三组来看：&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第一组：拒绝组（1 种）&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;noeviction&lt;/strong&gt;——内存满了就不让写，直接报错。简单粗暴，适合数据绝对不能丢的场景。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;我实测了一下：设 maxmemory=2MB，然后用 SET 逐个写入 10KB 的 key。前 78 个 key 都成功了，第 79 个时报了 &lt;code&gt;OOM command not allowed when used memory &amp;gt; &#39;maxmemory&#39;&lt;/code&gt;。noeviction 说到做到——满了就真不让写。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第二组：全体组（3 种）——从所有 key 里选&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;allkeys-lru&lt;/strong&gt;——淘汰最近最少使用的 key&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;allkeys-lfu&lt;/strong&gt;——淘汰访问频率最低的 key（4.0 引入）&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;allkeys-random&lt;/strong&gt;——随机淘汰&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;strong&gt;第三组：过期组（4 种）——只从设了 TTL 的 key 里选&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;volatile-lru&lt;/strong&gt;——从过期 key 里淘汰最近最少使用的&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;volatile-lfu&lt;/strong&gt;——从过期 key 里淘汰访问频率最低的&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;volatile-random&lt;/strong&gt;——从过期 key 里随机淘汰&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;volatile-ttl&lt;/strong&gt;——淘汰剩余 TTL 最短的&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-expiry-eviction/ch4-eviction-quadrant.png&#34; alt=&#34;8 种淘汰策略象限图&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;我跑了一组实验来展示 volatile-lru 和 allkeys-lru 的关键区别：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;实验：volatile-lru 策略下，有 TTL 的 key vs 无 TTL 的 key&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;创建 10 个有 TTL 的 key（withttl:1-10）+ 10 个无 TTL 的 key（nottl:1-10）&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;设置 maxmemory=128KB 触发淘汰&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;结果：&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  有 TTL 的 key 剩余：0/10  ← 全部被淘汰&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  无 TTL 的 key 剩余：10/10 ← 全部保留&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个实验完美展示了 volatile-* 策略的行为：&lt;strong&gt;它只在设了 TTL 的 key 里做选择，无 TTL 的 key 永远不会被淘汰&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;反过来，allkeys-* 策略则一视同仁——不管有没有 TTL，全都可以被淘汰。&lt;/p&gt;&#xA;&lt;p&gt;这就是为什么说&amp;quot;过期&amp;quot;和&amp;quot;淘汰&amp;quot;是两套独立机制：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;过期策略只关心&amp;quot;设了 TTL 的 key 过期了没有&amp;quot;&lt;/li&gt;&#xA;&lt;li&gt;淘汰策略关心&amp;quot;内存满了要删谁&amp;quot;&lt;/li&gt;&#xA;&lt;li&gt;它们的交集是&amp;quot;设了 TTL 的 key 也可能被淘汰器选中&amp;quot;——但这是偶然，不是必然&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;22-近似-lru精确的代价太高&#34;&gt;&lt;a href=&#34;#22-%e8%bf%91%e4%bc%bc-lru%e7%b2%be%e7%a1%ae%e7%9a%84%e4%bb%a3%e4%bb%b7%e5%a4%aa%e9%ab%98&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;2.2 近似 LRU——精确的代价太高&#xA;&lt;/h3&gt;&lt;p&gt;LRU（Least Recently Used）是最常用的淘汰策略。但你猜 Redis 用的是不是精确 LRU？&lt;/p&gt;&#xA;&lt;p&gt;不是。精确 LRU 需要维护一个全局的访问时间链表，每个 key 被访问时都要更新链表——内存消耗大，更新成本高。Redis 用的是&lt;strong&gt;近似 LRU&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;怎么近似呢？Redis 的做法是：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;从所有 key 中随机采样 N 个（N = maxmemory-samples，默认 5）&lt;/li&gt;&#xA;&lt;li&gt;从这 N 个里选一个最久没被访问的淘汰&lt;/li&gt;&#xA;&lt;li&gt;重复直到内存降到 maxmemory 以下&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;采样数 N 决定了淘汰的精度。我跑了一组实验来验证这个参数的影响：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;maxmemory-samples&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;热 key（前 200 个访问 10 次）保留&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;冷 key（后 200 个不访问）保留&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;5（默认）&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;200/200&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;172/200&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;20（高采样）&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;200/200&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;194/200&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;1（极低采样）&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;96/200&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;92/200&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;实验结果很有意思：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;samples=5（默认）&lt;/strong&gt;：热 key 全部保留，冷 key 大部分被淘汰——精度已经不错&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;samples=20&lt;/strong&gt;：热 key 全部保留，冷 key 保留更多（194/200）——淘汰选择更精确&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;samples=1&lt;/strong&gt;：热 key 和冷 key 保留率接近（~50%）——基本退化为随机淘汰&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-expiry-eviction/ch5-lru-compare.png&#34; alt=&#34;近似 LRU 采样对比&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;samples=20 和 samples=5 的差异不大，说明 Redis 默认的 5 已经是一个很好的平衡点。但在内存非常紧张的场景下，把 samples 提高到 10 可以换来更好的精度，代价是每次淘汰时多采样 5 个 key 的 CPU 开销。&lt;/p&gt;&#xA;&lt;p&gt;Redis 3.0 之后还加了 eviction pool 优化，维护一个淘汰候选池让近似 LRU 更接近精确 LRU。这个优化我没单独做实验验证，但 Redis 官方文档和源码注释里有说明，日常使用中你不需要关心这个细节。&lt;/p&gt;&#xA;&lt;h3 id=&#34;23-lfu治偶发热点的良药&#34;&gt;&lt;a href=&#34;#23-lfu%e6%b2%bb%e5%81%b6%e5%8f%91%e7%83%ad%e7%82%b9%e7%9a%84%e8%89%af%e8%8d%af&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;2.3 LFU——治&amp;quot;偶发热点&amp;quot;的良药&#xA;&lt;/h3&gt;&lt;p&gt;LRU 有个经典问题：一个 key 被大量访问一次后就再也没人用了，但它在 LRU 链上排在&amp;quot;最近使用&amp;quot;的位置，淘汰时会绕过它。&lt;/p&gt;&#xA;&lt;p&gt;这就是&amp;quot;偶发热点&amp;quot;问题。举个例子：一个定时任务每分钟批量读一批 key，这批 key 每分钟被&amp;quot;刷&amp;quot;一次最近访问时间，但它们其实不热——只是运气好，在 LRU 检查时刚好被访问了。&lt;/p&gt;&#xA;&lt;p&gt;LFU（Least Frequently Used）就是来解决这个问题的。它不看&amp;quot;最近一次访问时间&amp;quot;，而是看&lt;strong&gt;访问频率&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;Redis 的 LFU 有两个关键参数：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;lfu-log-factor&lt;/strong&gt;：频率增长速率。值越大，频率增长越慢，越难变&amp;quot;热&amp;quot;。默认 10，如果你想让热点数据更&amp;quot;黏&amp;quot;，可以调低到 5&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;lfu-decay-time&lt;/strong&gt;：频率衰减周期。以分钟为单位，N 分钟内没被访问，频率减半。默认 1，即 1 分钟不访问频率就衰减&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;实现上，Redis 用了 Morris counter（概率计数器）来追踪访问频率，而不是精确计数。好处是只需要 8 位就能表示约百万次访问（在默认 lfu-log-factor=10 下），非常适合嵌入 key 的 metadata。这个设计很紧凑，代价是访问频率是近似值——需要精确访问次数统计的业务场景，LFU 不合适。具体位分配等实现细节见源码，这里不展开。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-expiry-eviction/ch6-lfu-counter.png&#34; alt=&#34;LFU 8 位 counter&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;一个小细节：LFU 和 LRU 共享同一个 &lt;code&gt;maxmemory-policy&lt;/code&gt; 配置空间，不能同时启用。你在配置里写 &lt;code&gt;allkeys-lfu&lt;/code&gt;，那就是 LFU 模式；写 &lt;code&gt;allkeys-lru&lt;/code&gt;，那就是 LRU 模式。它们各自有自己的算法实现，不冲突。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;三过期-vs-淘汰5-个维度说清楚&#34;&gt;&lt;a href=&#34;#%e4%b8%89%e8%bf%87%e6%9c%9f-vs-%e6%b7%98%e6%b1%b05-%e4%b8%aa%e7%bb%b4%e5%ba%a6%e8%af%b4%e6%b8%85%e6%a5%9a&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;三、过期 vs 淘汰：5 个维度说清楚&#xA;&lt;/h2&gt;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-expiry-eviction/ch9-comparison.png&#34; alt=&#34;过期 vs 淘汰&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;讲到这里，两套机制的核心都拆开了。最后用一个对比表来收束：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;维度&lt;/th&gt;&#xA;          &lt;th&gt;过期策略&lt;/th&gt;&#xA;          &lt;th&gt;内存淘汰&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&lt;strong&gt;触发条件&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;时间到了&lt;/td&gt;&#xA;          &lt;td&gt;内存满了&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&lt;strong&gt;目标&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;释放过期 key 的内存&lt;/td&gt;&#xA;          &lt;td&gt;腾出空间给新写入&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&lt;strong&gt;作用对象&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;仅限设了 TTL 的 key&lt;/td&gt;&#xA;          &lt;td&gt;取决于策略（allkeys: 所有 key / volatile: 仅设 TTL 的 key）&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&lt;strong&gt;执行者&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;expireIfNeeded（惰性）+ activeExpireCycle（定期）&lt;/td&gt;&#xA;          &lt;td&gt;eviction pool + 策略算法&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&lt;strong&gt;可控性&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;不可控（TTL 到了就过期）&lt;/td&gt;&#xA;          &lt;td&gt;可控（8 种策略可选）&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;最关键的区分在这张表里。很多人把&amp;quot;过期&amp;quot;和&amp;quot;淘汰&amp;quot;混为一谈，其实是没分清这两者的触发条件和作用对象。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;过期是时间问题，淘汰是空间问题。&lt;/strong&gt; 一个 key 可以同时经历这两件事：先过期（时间到了），然后因为没人访问它而继续占内存，最后被淘汰器选中（空间满了）。&lt;/p&gt;&#xA;&lt;p&gt;但它们也可以是独立发生的：一个没设 TTL 的 key，永远不会触发过期策略；一个设了 TTL 的 key，如果内存永远不满，淘汰器永远不会被触发。&lt;/p&gt;&#xA;&lt;h3 id=&#34;31-它们怎么配合&#34;&gt;&lt;a href=&#34;#31-%e5%ae%83%e4%bb%ac%e6%80%8e%e4%b9%88%e9%85%8d%e5%90%88&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;3.1 它们怎么配合&#xA;&lt;/h3&gt;&lt;p&gt;两套机制在实际运行中是配合的：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;定期删除释放了内存 → 淘汰器可能不必触发&lt;/strong&gt;。如果你的过期 key 被定期删除及时清理了，内存就有空间给新 key，淘汰器不需要出手。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;设了 TTL 的 key 被淘汰器选中&lt;/strong&gt;。在 volatile-* 策略下，淘汰器只从设了 TTL 的 key 里选——至于选哪个，按 LRU/LFU/Random/TTL 算法决定，不是因为&amp;quot;快过期所以先删&amp;quot;。被淘汰的 key 不一定已过期，只是设了 TTL。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;过期 key 太多 → 定期删除 CPU 上升&lt;/strong&gt;。如果大量 key 在同一时间过期，定期删除会持续扫描，CPU 消耗增加。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;回到文章开头的那个案例：session key 集中过期 → 定期删除忙不过来 → 过期 key 占内存 → 淘汰器触发 → 活跃 key 被误淘汰。缓存雪崩就发生了。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-expiry-eviction/ch7-collaboration.png&#34; alt=&#34;过期与淘汰协作链条&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;这个链条里，两套机制都参与了，但不是 Redis 有 bug——它们都在按设计工作，只是我们的业务设计（统一 TTL）触发了两套机制最不利的协作模式。&lt;/p&gt;&#xA;&lt;p&gt;解决方案其实很简单：给 session key 的 TTL 加入随机偏移（±30%），让过期时间分散开。这套组合拳下来，类似的问题就很少再出现了。&lt;/p&gt;&#xA;&lt;h3 id=&#34;32-实战建议&#34;&gt;&lt;a href=&#34;#32-%e5%ae%9e%e6%88%98%e5%bb%ba%e8%ae%ae&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;3.2 实战建议&#xA;&lt;/h3&gt;&lt;p&gt;根据你的业务场景，可以这样选：&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;什么时候关注过期策略&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;你的 key 都有 TTL&lt;/li&gt;&#xA;&lt;li&gt;大量 key 同时过期&lt;/li&gt;&#xA;&lt;li&gt;对过期 key 的即时性有要求&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;strong&gt;什么时候关注淘汰策略&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;缓存是&amp;quot;写满删&amp;quot;的模式（没有 TTL）&lt;/li&gt;&#xA;&lt;li&gt;内存是瓶颈资源&lt;/li&gt;&#xA;&lt;li&gt;数据有冷热分布&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;strong&gt;选什么淘汰策略&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;大部分场景 → &lt;strong&gt;allkeys-lru&lt;/strong&gt;（通用选择）。根据上面 E4 实验，samples=5 的默认值已经是不错的平衡，日常使用不需要额外调参&lt;/li&gt;&#xA;&lt;li&gt;有偶发热点 → &lt;strong&gt;allkeys-lfu&lt;/strong&gt;（4.0+ 推荐）&lt;/li&gt;&#xA;&lt;li&gt;业务数据可丢，但不能丢没 TTL 的 → &lt;strong&gt;volatile-lru&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;数据绝对不能丢 → &lt;strong&gt;noeviction&lt;/strong&gt; + 监控告警&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-expiry-eviction/ch8-decision-tree.png&#34; alt=&#34;淘汰策略选择决策树&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;另外还有一个常见的实用技巧：把 Redis 分成多个实例，不同的业务用不同的淘汰策略。比如 session 缓存用 volatile-lru（有 TTL 的保护无 TTL 的业务数据），数据缓存用 allkeys-lru（一视同仁），各管各的，互不影响。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;回到开头的那个问题：5 分钟到了，key 真的被删了吗？&lt;/p&gt;&#xA;&lt;p&gt;不一定是&amp;quot;被删除&amp;quot;。如果没人访问它，且定期删除还没扫到它，它还在内存里。但如果你设的 TTL 是 5 分钟，而你的内存限制是 1GB——那它在被访问前可能先被淘汰器盯上了。&lt;/p&gt;&#xA;&lt;p&gt;Redis 的过期和淘汰，是两套独立但又会互相影响的内存管理机制。&lt;strong&gt;过期是时间问题，淘汰是空间问题&lt;/strong&gt;。记住这句话，下次遇到 Redis 内存问题，你就知道该从哪套机制查起了——过期查 TTL，淘汰查 maxmemory 和策略配置。&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;原文发布于 &lt;a class=&#34;link&#34; href=&#34;https://www.wujiachen.com.cn/posts/redis-expiry-eviction&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;止语Lab&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;</description>
        </item></channel>
</rss>
