<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>缓存雪崩 on Zero Day Notes</title>
        <link>https://blog.5772447.xyz/tags/%E7%BC%93%E5%AD%98%E9%9B%AA%E5%B4%A9/</link>
        <description>Recent content in 缓存雪崩 on Zero Day Notes</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh</language>
        <lastBuildDate>Tue, 11 Mar 2025 22:43:30 +0000</lastBuildDate><atom:link href="https://blog.5772447.xyz/tags/%E7%BC%93%E5%AD%98%E9%9B%AA%E5%B4%A9/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>秒杀接口大面积超时：Redis 缓存雪崩的定位与修复</title>
            <link>https://blog.5772447.xyz/posts/2025-03-11-%E4%B8%80%E6%AC%A1redis%E7%BC%93%E5%AD%98%E9%9B%AA%E5%B4%A9%E5%AF%BC%E8%87%B4%E7%A7%92%E6%9D%80%E6%8E%A5%E5%8F%A3%E5%A4%A7%E9%9D%A2%E7%A7%AF%E8%B6%85%E6%97%B6%E7%9A%84%E6%8E%92%E6%9F%A5%E8%AE%B0%E5%BD%95/</link>
            <pubDate>Tue, 11 Mar 2025 22:43:30 +0000</pubDate>
            <guid>https://blog.5772447.xyz/posts/2025-03-11-%E4%B8%80%E6%AC%A1redis%E7%BC%93%E5%AD%98%E9%9B%AA%E5%B4%A9%E5%AF%BC%E8%87%B4%E7%A7%92%E6%9D%80%E6%8E%A5%E5%8F%A3%E5%A4%A7%E9%9D%A2%E7%A7%AF%E8%B6%85%E6%97%B6%E7%9A%84%E6%8E%92%E6%9F%A5%E8%AE%B0%E5%BD%95/</guid>
            <description>&lt;h2 id=&#34;一问题背景&#34;&gt;&lt;a href=&#34;#%e4%b8%80%e9%97%ae%e9%a2%98%e8%83%8c%e6%99%af&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;一、问题背景&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;我们的电商系统在大促期间会开放限量秒杀活动，秒杀商品几十款，单款库存几千到几万不等。秒杀接口 &lt;code&gt;POST /api/seckill&lt;/code&gt; 是系统流量入口，正常情况下 QPS 在 2000 左右，大促开场瞬间会冲到 30000+。&lt;/p&gt;&#xA;&lt;p&gt;为了扛住读压力，商品详情、库存余量、用户限购状态等热点数据全部缓存在 Redis 集群（3 主 3 从，每主 8G maxmemory）。缓存设计上，运营在后台批量导入秒杀商品时，统一把缓存 TTL 设置为 30 分钟，并且由于是同一时刻批量导入，大量 key 的过期时间高度集中在开场后的同一个时间窗口内。&lt;/p&gt;&#xA;&lt;p&gt;本次大促的秒杀在晚上 20:00 准时开始，商品导入动作发生在 19:25 左右，这意味着绝大部分缓存 key 会在 19:55 ~ 20:00 之间集中失效。&lt;/p&gt;&#xA;&lt;h2 id=&#34;二故障现象&#34;&gt;&lt;a href=&#34;#%e4%ba%8c%e6%95%85%e9%9a%9c%e7%8e%b0%e8%b1%a1&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;二、故障现象&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;20:00 活动一开抢，监控告警在 30 秒内全部触发：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;秒杀接口 P99 延迟从平时的 50ms 飙升至 8s，P999 直接超过 15s；&lt;/li&gt;&#xA;&lt;li&gt;网关层 504 超时大量出现，错误率从 0.1% 涨到 23%；&lt;/li&gt;&#xA;&lt;li&gt;Redis 集群 CPU 使用率被打满（各节点 user + sys 接近 100%），但 QPS 反而比平时低——因为大量请求已经穿透；&lt;/li&gt;&#xA;&lt;li&gt;后端 MySQL 主库连接数瞬间打满（max_connections=2000），出现 &lt;code&gt;Too many connections&lt;/code&gt;；&lt;/li&gt;&#xA;&lt;li&gt;订单创建成功率从 99.5% 掉到 41%，大量用户反馈&amp;quot;点了没反应&amp;quot;&amp;ldquo;一直在转圈&amp;rdquo;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;最诡异的一点是：Redis 本身并没有宕机，节点都是 &lt;code&gt;connected&lt;/code&gt; 状态，但业务就是全面变慢。这让我们一开始怀疑是网关或应用层的问题，而不是缓存。&lt;/p&gt;&#xA;&lt;h2 id=&#34;三排查过程&#34;&gt;&lt;a href=&#34;#%e4%b8%89%e6%8e%92%e6%9f%a5%e8%bf%87%e7%a8%8b&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;三、排查过程&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;第一步，先确认慢在哪里。在网关机器上抓取秒杀链路的 tracing：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div 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;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&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&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;1&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;2&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;3&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&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-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;span: gateway -&amp;gt; seckill-svc -&amp;gt; redis (GET stock:sku_123)  3ms&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;span: gateway -&amp;gt; seckill-svc -&amp;gt; mysql (SELECT stock ...)    7200ms&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;span: gateway -&amp;gt; seckill-svc -&amp;gt; mysql (INSERT order)        timeout&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;火焰图清楚显示耗时全部卡在 MySQL 的 &lt;code&gt;SELECT stock&lt;/code&gt; 上，而本该走 Redis 的 &lt;code&gt;GET stock:sku_xxx&lt;/code&gt; 几乎不见了。第一反应是 Redis 挂了或者连接池耗尽。&lt;/p&gt;&#xA;&lt;p&gt;第二步，登录 Redis 节点验证。连接正常，但发现一个反常现象：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div 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;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&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&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;1&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;2&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;3&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;4&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;5&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;6&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&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-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ redis-cli info stats | grep -E &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;instantaneous_ops_per_sec|evicted_keys|keyspace_hits|keyspace_misses&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;instantaneous_ops_per_sec:1850&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;evicted_keys:0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;keyspace_hits:201&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;keyspace_misses:1640&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;expired_keys:98231&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;keyspace_misses&lt;/code&gt; 远大于 &lt;code&gt;keyspace_hits&lt;/code&gt;，说明大量 key 查不到，请求全部穿透到 MySQL。再看 &lt;code&gt;expired_keys&lt;/code&gt;，在故障窗口内一秒过期近 10 万个 key——典型的&lt;strong&gt;集中过期&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;第三步，确认过期时间分布。抽样几个秒杀 key：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div 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;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&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&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;1&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;2&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;3&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;4&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;5&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;6&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;7&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;8&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&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-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ redis-cli TTL stock:sku_1001&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;integer&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; -2&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ redis-cli TTL stock:sku_1002&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;integer&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; -2&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ redis-cli --scan --pattern stock:* | head -5&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;stock:sku_1021&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ redis-cli TTL stock:sku_1021&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;integer&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;绝大多数热点 key 都已失效或即将在几秒内失效，印证了&amp;quot;批量导入时统一 TTL&amp;quot;导致的集中过期。这就是缓存雪崩：同一时刻大量 key 失效，所有请求同时回源数据库。&lt;/p&gt;&#xA;&lt;p&gt;第四步，确认数据库承压。&lt;code&gt;SHOW PROCESSLIST&lt;/code&gt; 里堆满了 &lt;code&gt;SELECT stock FROM seckill_stock WHERE sku=?&lt;/code&gt;，几乎都是 &lt;code&gt;Sending data&lt;/code&gt; 状态。MySQL 本身能力不足以承接 3 万 QPS 的纯读，连接池被瞬间占满，进而拖垮整个订单链路。&lt;/p&gt;&#xA;&lt;h2 id=&#34;四解决方案&#34;&gt;&lt;a href=&#34;#%e5%9b%9b%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;四、解决方案&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;应急止血（先恢复，再根治）：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;临时对秒杀接口做&lt;strong&gt;请求合并 + 本地缓存降级&lt;/strong&gt;。在应用层对库存查询加一层 Caffeine 本地缓存（TTL 2s），即使 Redis 没值，也先返回上一次的非空结果，避免所有线程同时打库。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;给热点读加&lt;strong&gt;互斥锁（singleflight）&lt;/strong&gt;，同一 sku 的回源只允许一个线程查库并写回 Redis，其余线程等待结果：&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div 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;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&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&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;1&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;2&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;3&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;4&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;5&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;6&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;7&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;8&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;9&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&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-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 伪代码：singleflight 回源&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;String val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; redis.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(key);&#xA;&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; (val &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; singleflight.&lt;span style=&#34;color:#a6e22e&#34;&gt;exec&lt;/span&gt;(key, () &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        String dbVal &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; db.&lt;span style=&#34;color:#a6e22e&#34;&gt;queryStock&lt;/span&gt;(sku);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        redis.&lt;span style=&#34;color:#a6e22e&#34;&gt;setex&lt;/span&gt;(key, baseTtl &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; random(0,300), dbVal); &lt;span style=&#34;color:#75715e&#34;&gt;// 加随机抖动&lt;/span&gt;&#xA;&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;return&lt;/span&gt; dbVal;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;ol start=&#34;3&#34;&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;紧急扩容 MySQL 只读从库，将库存读流量导向从库，主库只承接下单写。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;网关侧对 &lt;code&gt;/api/seckill&lt;/code&gt; 开启&lt;strong&gt;令牌桶限流&lt;/strong&gt;（20000 QPS），超出直接快速失败返回&amp;quot;活动太火爆&amp;quot;，保护后端不被彻底压垮。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;5 分钟后 P99 回落到 600ms，10 分钟后恢复到 120ms，订单成功率回到 92%。&lt;/p&gt;&#xA;&lt;h2 id=&#34;五根因分析&#34;&gt;&lt;a href=&#34;#%e4%ba%94%e6%a0%b9%e5%9b%a0%e5%88%86%e6%9e%90&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;五、根因分析&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;根因是&lt;strong&gt;缓存 key 集中过期引发的缓存雪崩&lt;/strong&gt;，叠加了两个设计缺陷：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;TTL 高度一致&lt;/strong&gt;：运营批量导入时统一设 30 分钟 TTL，导致活动开场瞬间海量 key 同时失效，请求在极短时间内全部击穿到数据库。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;缺乏防穿透/防雪崩机制&lt;/strong&gt;：缓存未命中时没有任何合并、降级或随机抖动策略，数据库在没有任何缓冲的情况下直面 3 万 QPS 的回源洪峰。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Redis 没宕机但 CPU 打满，是因为雪崩期间大量 key 同时过期触发主动/被动淘汰扫描，加上连接风暴，使单节点处理效率骤降；而真正的瓶颈在下游 MySQL——它本就不是为承载全部读流量设计的。&lt;/p&gt;&#xA;&lt;h2 id=&#34;六预防措施&#34;&gt;&lt;a href=&#34;#%e5%85%ad%e9%a2%84%e9%98%b2%e6%8e%aa%e6%96%bd&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;六、预防措施&#xD;&#xA;&lt;/h2&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;TTL 加随机抖动&lt;/strong&gt;：导入缓存时 TTL = 基础值 + 随机(0, 300s)，打散过期时间，避免集体失效。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div 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;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&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&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;1&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;2&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&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-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; ttl &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; 1800 &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; ThreadLocalRandom.&lt;span style=&#34;color:#a6e22e&#34;&gt;current&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;nextInt&lt;/span&gt;(0, 300);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;redis.&lt;span style=&#34;color:#a6e22e&#34;&gt;setex&lt;/span&gt;(key, ttl, value);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;ol start=&#34;2&#34;&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;热点 key 永不过期 + 后台异步刷新&lt;/strong&gt;：对秒杀库存这类极热点数据，设为逻辑不过期，由定时任务在后台主动刷新，彻底规避过期瞬间空窗。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;回源合并&lt;/strong&gt;：全站接入 singleflight / 分布式锁，保证同一 key 同一时刻只有一个回源。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;多级缓存&lt;/strong&gt;：Redis + 应用本地缓存（Caffeine）双层，Redis 失效时本地缓存仍能兜底。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;数据库保护&lt;/strong&gt;：库存读走从库，写主库；并配置合理的连接池上限与熔断（Sentinel/Resilience4j），避免被回源打挂。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;压测与演练&lt;/strong&gt;：大促前对&amp;quot;缓存全失效&amp;quot;场景做专项混沌演练，验证降级链路可用。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;七总结&#34;&gt;&lt;a href=&#34;#%e4%b8%83%e6%80%bb%e7%bb%93&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;七、总结&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;这次故障本质不是 Redis 性能问题，而是&lt;strong&gt;缓存生命周期设计缺陷&lt;/strong&gt;引发的雪崩。集中 TTL 是很容易被忽视的&amp;quot;定时炸弹&amp;quot;，平时相安无事，一到流量洪峰就炸。排查上，关键抓手是 Redis 的 &lt;code&gt;keyspace_misses / expired_keys&lt;/code&gt; 两个指标——它们能一眼定性&amp;quot;请求是否穿透&amp;quot;。记住：缓存层永远要假设自己会失效，回源必须有合并、必须有抖动、必须有降级。把数据库当成最后一道防线，而不是第一道承受洪峰的堤坝。&lt;/p&gt;&#xA;</description>
        </item></channel>
</rss>
