Redis中删除策略的几种实现方式
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">前言</a></li><li><a href="#_label1">一、设计背景:为什么需要删除策略?</a></li><li><a href="#_label2">二、第一类:过期键的 3 种核心删除策略</a></li><ul class="second_class_ul"><li><a href="#_lab2_2_0">1. 定时删除(Timed Delete):“实时清理,CPU 杀手”</a></li><li><a href="#_lab2_2_1">2. 惰性删除(Lazy Delete):“用的时候再删,内存隐患”</a></li><li><a href="#_lab2_2_2">3. 定期删除(Periodic Delete):“折中方案,平衡 CPU 与内存”</a></li><li><a href="#_lab2_2_3">4. Redis 的最终选择:惰性删除 + 定期删除</a></li></ul><li><a href="#_label3">三、第二类:内存淘汰策略(Maxmemory Eviction)</a></li><ul class="second_class_ul"><li><a href="#_lab2_3_4">1. 核心前提:maxmemory配置</a></li><li><a href="#_lab2_3_5">2. 8 种内存淘汰策略(Redis 5.0+)</a></li></ul><li><a href="#_label4">四、实践建议:如何选择删除策略?</a></li><ul class="second_class_ul"></ul><li><a href="#_label5">总结</a></li><ul class="second_class_ul"></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>前言</h2><p>要全面、深入理解 Redis 的删除策略,需从<strong>设计背景</strong>、<strong>核心分类(过期键删除策略 + 内存淘汰策略)</strong>、<strong>内部实现细节</strong>、<strong>与持久化的关联</strong>及<strong>实践选择</strong>五个维度展开。Redis 的删除策略本质是为了解决 “内存有限” 与 “性能优先” 的核心矛盾 —— 既要避免内存溢出,又要减少删除操作对单线程主线程的阻塞,同时保证数据一致性。</p>
<p class="maodian"><a name="_label1"></a></p><h2>一、设计背景:为什么需要删除策略?</h2>
<p>Redis 是<strong>内存数据库</strong>,内存资源昂贵且有限,若不主动管理内存,会导致内存溢出(OOM);同时,Redis 支持为键设置<strong>过期时间(TTL)</strong>,需保证过期键能被合理清理;此外,Redis 是<strong>单线程模型</strong>,删除操作若过于耗时,会阻塞主线程,导致响应延迟。因此,Redis 设计了两类互补的删除策略:</p>
<ol><li><strong>过期键删除策略</strong>:主动处理 “已过期但未删除” 的键,减少无效内存占用;</li><li><strong>内存淘汰策略</strong>:当内存达到上限时,被动删除部分键以释放内存,保证服务可用性。</li></ol>
<p class="maodian"><a name="_label2"></a></p><h2>二、第一类:过期键的 3 种核心删除策略</h2>
<p>Redis 通过<strong>过期字典(expires dict)</strong> 单独存储所有键的过期时间(key 为目标键,value 为过期时间戳),所有过期键删除策略均基于此字典实现。三种策略各有优劣,Redis 最终采用 “惰性删除 + 定期删除” 的组合方案。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202511/2025111310140746.png" /></p>
<p class="maodian"><a name="_lab2_2_0"></a></p><h3>1. 定时删除(Timed Delete):“实时清理,CPU 杀手”</h3>
<p>定义:为每个设置了过期时间的键,创建一个<strong>定时器(Timer)</strong>,当键的过期时间到达时,定时器立即触发,执行<code>DEL</code>命令删除该键。</p>
<p>实现逻辑:</p>
<ul><li>当调用<code>EXPIRE key ttl</code>时,Redis 不仅在过期字典中记录过期时间,还会注册一个定时器;</li><li>定时器到期后,直接在主线程中执行删除操作。</li></ul>
<p>优缺点:</p>
<table><thead><tr><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td>内存最 “干净”:过期键立即删除,无无效内存占用;</td><td>CPU 压力大:若存在大量过期键,定时器密集触发,会占用大量 CPU 资源,导致主线程阻塞,影响 Redis 响应速度;</td></tr><tr><td>数据一致性高:不会出现 “读取到过期键” 的情况;</td><td>定时器管理成本高:每个过期键对应一个定时器,内存开销也会增加。</td></tr></tbody></table>
<p>适用场景:</p>
<p>几乎不适用。Redis 是单线程模型,定时删除的 CPU 开销会严重影响服务性能,因此 Redis<strong>未采</strong>用此策略。</p>
<p class="maodian"><a name="_lab2_2_1"></a></p><h3>2. 惰性删除(Lazy Delete):“用的时候再删,内存隐患”</h3>
<p>定义:Redis 不主动删除过期键,而是在<strong>访问键的瞬间</strong>(如<code>GET</code>、<code>HGET</code>、<code>SETNX</code>等操作),才检查该键是否过期:</p>
<ul><li>若未过期:正常返回键值;</li><li>若已过期:执行<code>DEL</code>命令删除该键,返回<code>nil</code>(空)。</li></ul>
<p>实现逻辑:</p>
<p>以<code>GET key</code>为例:</p>
<ol><li>先检查<code>key</code>是否存在于过期字典中;</li><li>若不存在:直接返回键值;</li><li>若存在:对比当前时间与过期时间戳,判断是否过期;</li><li>若已过期:删除<code>key</code>(从数据库字典和过期字典中同时移除),返回<code>nil</code>;</li><li>若未过期:返回键值。</li></ol>
<p>优缺点:</p>
<table><thead><tr><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td>CPU 友好:仅在 “访问过期键” 时才执行删除,无额外 CPU 开销,不影响主线程正常请求;</td><td>内存泄漏风险:若大量过期键长期不被访问,会一直占用内存(“僵尸键”),导致内存利用率低,甚至触发内存上限;</td></tr><tr><td>实现简单:无需管理定时器,逻辑轻量;</td><td>可能出现 “瞬时过期键”:若过期键未被访问,其他操作(如<code>KEYS</code>、<code>DBSIZE</code>)会统计到这些过期键,导致数据统计不准确。</td></tr></tbody></table>
<p class="maodian"><a name="_lab2_2_2"></a></p><h3>3. 定期删除(Periodic Delete):“折中方案,平衡 CPU 与内存”</h3>
<p>定义: Redis 每隔一段时间(默认 100ms),<strong>主动扫描部分过期键</strong>并删除,扫描频率和范围由配置控制,避免一次性扫描所有过期键导致阻塞。</p>
<p>核心实现逻辑(Redis 6.0+)</p>
<ol><li><strong>扫描频率</strong>:由<code>hz</code>配置控制(默认<code>hz 10</code>,即每秒执行 10 次定期扫描,每次间隔约 100ms);</li><li><strong>扫描范围</strong>:每次扫描不遍历所有过期键,而是采用 “采样 + 限制时间” 的方式:<ul><li>从过期字典中随机抽取<code>N</code>个键(默认<code>N=20</code>);</li><li>检查这些键是否过期,删除已过期的键;</li><li>若删除的键占比超过<code>25%</code>(即删除数≥5),则继续抽取<code>N</code>个键重复扫描;</li><li>若占比≤25%,或扫描时间超过<code>2ms</code>(避免阻塞主线程),则停止本次扫描。</li></ul></li></ol>
<p>优缺点:</p>
<table><thead><tr><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td>平衡 CPU 与内存:既避免了定时删除的 CPU 压力,也缓解了惰性删除的内存泄漏问题;</td><td>扫描参数难调优:<code>hz</code>、<code>N</code>、<code>25%</code>阈值需根据业务调整,若<code>hz</code>过高或<code>N</code>过大,会增加 CPU 负担;若过低,则内存清理不及时;</td></tr><tr><td>非阻塞设计:单次扫描时间限制在<code>2ms</code>内,不影响主线程处理请求;</td><td>仍有 “漏删” 可能:部分过期键可能在两次扫描间隔内未被访问,也未被扫描到,暂时占用内存。</td></tr></tbody></table>
<p class="maodian"><a name="_lab2_2_3"></a></p><h3>4. Redis 的最终选择:惰性删除 + 定期删除</h3>
<p>两种策略互补,覆盖大部分场景:</p>
<ul><li><strong>定期删除</strong>:主动 “批量清理” 过期键,减少 “僵尸键” 数量,缓解内存压力;</li><li><strong>惰性删除</strong>:兜底清理 “漏网之鱼”,确保访问时不会返回过期键,保证数据有效性。</li></ul>
<p>例如:一个过期键未被定期扫描删除,但用户访问时,惰性删除会立即清理它;反之,若过期键长期不被访问,定期扫描会逐步清理它。</p>
<p class="maodian"><a name="_label3"></a></p><h2>三、第二类:内存淘汰策略(Maxmemory Eviction)</h2>
<p>即使有过期键删除策略,Redis 仍可能因 “大量未设置过期时间的键” 或 “过期键删除不及时” 导致内存达到上限(<code>maxmemory</code>配置)。此时,Redis 会触发<strong>内存淘汰策略</strong>,删除部分键以释放内存,避免 OOM。</p>
<p class="maodian"><a name="_lab2_3_4"></a></p><h3>1. 核心前提:maxmemory配置</h3>
<ul><li>默认值:<code>0</code>(即不限制内存,仅受系统内存限制,生产环境<strong>必须手动设置</strong>,如<code>maxmemory 4gb</code>);</li><li>触发时机:当 Redis 使用的内存(包括键值对、过期字典、缓冲区等)达到<code>maxmemory</code>时,触发淘汰策略(仅对 “写操作” 触发,读操作不受影响)。</li></ul>
<p class="maodian"><a name="_lab2_3_5"></a></p><h3>2. 8 种内存淘汰策略(Redis 5.0+)</h3>
<p>根据 “淘汰范围” 和 “淘汰算法”,分为 4 类,核心区别是 “是否只淘汰过期键” 和 “用什么规则淘汰”:</p>
<table><thead><tr><th>策略常量</th><th>淘汰范围</th><th>淘汰算法</th><th>适用场景</th></tr></thead><tbody><tr><td>noeviction(默认)</td><td>无(不淘汰任何键)</td><td>-</td><td>禁止淘汰,内存满时拒绝所有写请求(返回OOM command not allowed),适合不允许数据丢失的场景(如核心配置存储)。</td></tr><tr><td>volatile-lru</td><td>仅淘汰 “设置了过期时间” 的键</td><td>LRU(最近最少使用)</td><td>希望保留常用的过期键,淘汰不常用的,适合有明确过期时间且需优先保留热点数据的场景(如缓存用户会话)。</td></tr><tr><td>allkeys-lru</td><td>所有键(无论是否过期)</td><td>LRU(最近最少使用)</td><td>不清楚哪些键常用,希望淘汰长期未访问的键,适合通用缓存场景(如商品详情缓存)。</td></tr><tr><td>volatile-lfu</td><td>仅淘汰 “设置了过期时间” 的键</td><td>LFU(最不经常使用)</td><td>比 LRU 更精准(统计 “访问频率” 而非 “最近访问时间”),适合淘汰低频访问的过期键(如低频访问的活动页面缓存)。</td></tr><tr><td>allkeys-lfu</td><td>所有键(无论是否过期)</td><td>LFU(最不经常使用)</td><td>适合需要精准淘汰 “低频率访问” 键的场景(如内容推荐系统,淘汰很少被点击的内容)。</td></tr><tr><td>volatile-random</td><td>仅淘汰 “设置了过期时间” 的键</td><td>随机淘汰</td><td>适合对淘汰键无明确优先级,仅需释放内存的场景(较少用)。</td></tr><tr><td>allkeys-random</td><td>所有键(无论是否过期)</td><td>随机淘汰</td><td>适合数据无热点、淘汰任意键均可的场景(极少用)。</td></tr><tr><td>volatile-ttl</td><td>仅淘汰 “设置了过期时间” 的键</td><td>淘汰 “剩余 TTL 最短” 的键</td><td>希望尽快删除快过期的键,释放内存给新键,适合需优先保留 “剩余时间长” 的过期键的场景(如短期活动缓存,优先保留刚创建的活动数据)。</td></tr></tbody></table>
<p>maxmemory最大可使用内存 占用物理内存的比例,默认值为0,表示不限制,生产环境中根据需求设定,通常设置在50%以上。</p>
<p>maxmemory-samples每次选取待删除数据的个数 选取数据时并不会全库扫描,导致严重的性能消耗,降低读写性能。因此采用随机获取数据的方式 作为待检测删除数据</p>
<p>maxmemory-policy删除策略</p>
<p>检测易失数据(可能会过期的数据集server.db.expires )</p>
<p>① volatile-lru:挑选最近最少使用的数据淘汰</p>
<p>② volatile-lfu:挑选最近使用次数最少的数据淘汰</p>
<p>③ volatile-ttl:挑选将要过期的数据淘汰</p>
<p>④ volatile-random:任意选择数据淘汰 检测全库数据(所有数据集server.db.dict )</p>
<p>⑤ allkeys-lru:挑选最近最少使用的数据淘汰</p>
<p>⑥ allkeys-lfu:挑选最近使用次数最少的数据淘汰</p>
<p>⑦ allkeys-random:任意选择数据淘汰 放弃数据驱逐</p>
<p>⑧ no-enviction(驱逐):禁止驱逐数据(redis4.0中默认策略),会引发错误OOM(Out Of Memory)达到最大内存后的,对被挑选出来的数据进行删除的策略,如下图:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202511/2025111310140773.png" /></p>
<p class="maodian"><a name="_label4"></a></p><h2>四、实践建议:如何选择删除策略?</h2>
<ol><li>优先关闭默认的noeviction策略:生产环境若用 Redis 做缓存,noeviction会导致内存满时写请求失败,建议替换为allkeys-lru或allkeys-lfu;</li><li>根据业务场景选择淘汰算法:<ul><li>通用缓存(如商品、页面缓存):选allkeys-lru(简单且有效);</li><li>访问频率波动大的场景(如内容推荐、促销活动):选allkeys-lfu(更精准);</li><li>有明确过期时间的场景(如会话、短期活动):选volatile-ttl或volatile-lfu;</li></ul></li><li>合理配置maxmemory和hz:<ul><li>maxmemory:建议设置为物理内存的 50%-70%(避免 Redis 占用过多内存导致系统 OOM);</li><li>hz:默认 10 即可,若内存压力大,可适当提高到 20(增加定期扫描频率),但不建议超过 100(避免 CPU 开销过高);</li></ul></li><li>避免大量设置短期过期键:若需频繁清理短期数据,优先用volatile-ttl策略,而非依赖定时删除。</li></ol>
<p class="maodian"><a name="_label5"></a></p><h2>总结</h2>
<p>Redis 的删除策略是 “主动清理(定期删除)+ 被动兜底(惰性删除)+ 内存溢出保护(内存淘汰)” 的三层架构,核心目标是<strong>平衡 CPU 资源、内存资源与数据一致性</strong>。理解每种策略的适用场景,结合业务需求选择合适的内存淘汰策略,是保障 Redis 高性能、高可用的关键。</p>
頁:
[1]