<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Redis on 洛克里亚的五度 博客</title>
    <link>https://lv-blog.pages.dev/tags/redis/</link>
    <description>Recent content in Redis on 洛克里亚的五度 博客</description>
    <generator>Hugo</generator>
    <language>zh</language>
    <lastBuildDate>Tue, 05 May 2026 22:46:44 +0800</lastBuildDate>
    <atom:link href="https://lv-blog.pages.dev/tags/redis/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>常见缓存策略精析</title>
      <link>https://lv-blog.pages.dev/posts/programming/backend/redis/common-cache-strategies/</link>
      <pubDate>Tue, 05 May 2026 22:46:44 +0800</pubDate>
      <guid>https://lv-blog.pages.dev/posts/programming/backend/redis/common-cache-strategies/</guid>
      <description>&lt;h1 id=&#34;缓存三兄弟&#34;&gt;缓存三兄弟&lt;/h1&gt;
&lt;h2 id=&#34;缓存穿透&#34;&gt;缓存穿透&lt;/h2&gt;
&lt;p&gt;是什么：大量请求查询数据库不存在的数据。缓存没有，持续击打数据库
防范方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;缓存空值
&lt;ol&gt;
&lt;li&gt;数据库查不到也往缓存放一个空标记（TTL短，2分钟，该TTL过期了才能进行再次数据库查询）&lt;/li&gt;
&lt;li&gt;下一次同样的id来了直接被挡住&lt;/li&gt;
&lt;li&gt;优点：有效拦截大量穿透请求&lt;/li&gt;
&lt;li&gt;缺点：
&lt;ol&gt;
&lt;li&gt;恶意攻击造成大量不同的不存在的key，缓存堆满无效数据，浪费内存&lt;/li&gt;
&lt;li&gt;延迟了数据一致性，新数据必须等缓存过期&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;布隆过滤器&lt;/strong&gt;（推荐）
&lt;ol&gt;
&lt;li&gt;启动时把所有合法id放入布隆过滤器；&lt;/li&gt;
&lt;li&gt;布隆过滤器不给进就真的没有；&lt;/li&gt;
&lt;li&gt;优点：
&lt;ol&gt;
&lt;li&gt;没有高内存占用风险&lt;/li&gt;
&lt;li&gt;没有数据一致性风险&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;缺点：
&lt;ol&gt;
&lt;li&gt;有极小误判率&lt;/li&gt;
&lt;li&gt;增删要维护。数据更新时，要同步更新布隆过滤器&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;接口层限流和校验
&lt;ol&gt;
&lt;li&gt;API网关或Controller层做拦截&lt;/li&gt;
&lt;li&gt;参数校验、限流降级&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;互斥锁重建
&lt;ol&gt;
&lt;li&gt;互斥锁主要解决缓存击穿（热点key过期），但是在缓存空对象场景下，如果有大量并发请求一个不存在的key，可以使用锁&lt;/li&gt;
&lt;li&gt;每次访问让一个线程去查DB，其他线程等待&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;大厂的实践&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;网关层 校验参数格式，IP限流（挡掉脚本攻击）&lt;/li&gt;
&lt;li&gt;过滤器层 布隆过滤器&lt;/li&gt;
&lt;li&gt;缓存层&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;缓存击穿&#34;&gt;缓存击穿&lt;/h2&gt;
&lt;p&gt;是什么：某个被疯狂访问的热点key，因为TTL一到，失效的一瞬间海量并发请求同时miss，同时到DB重建同一个key&lt;/p&gt;
&lt;p&gt;防范方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;互斥锁
&lt;ol&gt;
&lt;li&gt;只让第1个miss的线程去查DB&lt;/li&gt;
&lt;li&gt;其他线程等一下再读缓存
&lt;ol&gt;
&lt;li&gt;高一致性：线程傻傻等待&lt;/li&gt;
&lt;li&gt;低一致性：先给旧数据，下一次来查就是新的&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;逻辑过期
&lt;ol&gt;
&lt;li&gt;key永不物理过期，把过期时间存在value里面&lt;/li&gt;
&lt;li&gt;如果发现逻辑上过期了，异步开个线程重建&lt;/li&gt;
&lt;li&gt;当前请求先返回旧值&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;互斥锁的实现&#34;&gt;互斥锁的实现&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;如何搭建出正确的锁？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;v1：SETNX key 1 抢锁，DEL key 释放&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SETNX=&amp;ldquo;不存在才设置成功&amp;rdquo;，天然互斥。但问题：抢到锁的线程崩了、DEL 没执行 → 锁永远不释放 → 死锁。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;v2：给锁加过期间&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;崩了也能自动过期。但关键：必须用一条原子命令 SET key value NX EX 30。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;陷阱：别写成 SETNX + 再 EXPIRE 两条——如果刚 SETNX 成功、还没 EXPIRE 就崩了，又变回 v1 的死锁。&amp;ldquo;抢锁&amp;quot;和&amp;quot;设过期&amp;quot;必须一步做完。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Redis LFU内存淘汰策略细究</title>
      <link>https://lv-blog.pages.dev/posts/programming/redis/redis-memory-eviction-policy-lfu/</link>
      <pubDate>Fri, 19 Jul 2024 13:10:54 +0800</pubDate>
      <guid>https://lv-blog.pages.dev/posts/programming/redis/redis-memory-eviction-policy-lfu/</guid>
      <description>&lt;p&gt;Redis 的 LFU（Least Frequently Used，最不频繁使用）淘汰策略，是在 LRU 基础上做的“升级版”近似算法。它复用了对象头上同一个 24 位的 &lt;code&gt;lru&lt;/code&gt; 字段，通过巧妙的编码和对数计数，用极小的内存代价，实现了对访问频率的近似跟踪。&lt;/p&gt;
&lt;p&gt;下面从存储结构、计数器增减、淘汰决策到参数配置，逐一细究。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;1-24-位字段的位划分&#34;&gt;1. 24 位字段的位划分&lt;/h3&gt;
&lt;p&gt;每个 Redis 对象都有一个 &lt;code&gt;lru&lt;/code&gt; 属性（24 位），在 LFU 模式下，它不再存放秒级时间戳，而是被拆成两段：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt; 高 16 位：最后衰减时间（Last Decay Time，单位：分钟）
 低  8 位：对数访问计数器（Logarithmic Counter，范围 0–255）
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;高 16 位&lt;/strong&gt;：存储的是 &lt;code&gt;(server.unixtime / 60) &amp;amp; 0xFFFF&lt;/code&gt;，即当前分钟时间戳的低 16 位。最大表示约 45 天，足够覆盖淘汰场景，即使回绕，只要间隔不超过 45 天就可以正确计算差值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;低 8 位&lt;/strong&gt;：是一个 0–255 的频率计数器，但它&lt;strong&gt;不是&lt;/strong&gt;访问次数的直接累加，而是经过对数平滑处理的“近似频率”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当键被访问时，Redis 会调用 &lt;code&gt;updateLFU()&lt;/code&gt;，先根据已流逝的时间衰减计数器，再概率性地递增计数器，最后把新的分钟时间戳和计数器重新编码写回 &lt;code&gt;lru&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;2-计数器递增对数增长&#34;&gt;2. 计数器递增：对数增长&lt;/h3&gt;
&lt;p&gt;为了让 8 位计数器（0–255）既能表示低频也能区分高频，同时不让热门键快速打满，Redis 采用&lt;strong&gt;概率递增&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;递增公式（源码级）：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; p &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; ((counter &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; LFU_INIT_VAL) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; server.lfu_log_factor &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ((&lt;span style=&#34;color:#a6e22e&#34;&gt;random&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0xFFFF&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; p &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0xFFFF&lt;/span&gt;) counter&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;LFU_INIT_VAL&lt;/code&gt; 默认为 5，新键的计数器初始值就是 5。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lfu_log_factor&lt;/code&gt; 是可配置的对数因子，&lt;strong&gt;默认 10&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;随着 &lt;code&gt;counter&lt;/code&gt; 增大，&lt;code&gt;p&lt;/code&gt; 会越来越小，递增越来越难。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;举例（factor = 10 时，典型访问次数与计数器值）：&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
