<?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/%E6%9E%B6%E6%9E%84/</link>
        <description>Recent content in 架构 on 止语Lab</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-cn</language>
        <lastBuildDate>Wed, 10 Jun 2026 00:20:41 +0800</lastBuildDate><atom:link href="https://www.wujiachen.com.cn/tags/%E6%9E%B6%E6%9E%84/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>冷启动雪崩的三种策略：惰性加载、主动预热、渐进式预热怎么选</title>
            <link>https://www.wujiachen.com.cn/posts/cache-warmup-strategies/</link>
            <pubDate>Wed, 10 Jun 2026 00:20:40 +0800</pubDate>
            <guid>https://www.wujiachen.com.cn/posts/cache-warmup-strategies/</guid>
            <description>&lt;img src=&#34;https://img.wujiachen.com.cn/cache-warmup-strategies/cover.png&#34; alt=&#34;Featured image of post 冷启动雪崩的三种策略：惰性加载、主动预热、渐进式预热怎么选&#34; /&gt;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/cache-warmup-strategies/cover.png&#34; alt=&#34;封面&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;缓存服务一重启，数据库 CPU 瞬间 100%。你不是第一个遇到这个问题的人。&lt;/p&gt;&#xA;&lt;p&gt;这个问题有个专有名词叫&amp;quot;冷启动穿透&amp;quot;——严格来说它和标准的缓存雪崩（大量 key 同时过期或节点宕机）不是一回事，但后果类似：缓存里没有数据，所有请求穿透到数据库。很多人第一反应是&amp;quot;缓存挂了&amp;quot;，其实真正的原因是缓存&lt;strong&gt;冷着&lt;/strong&gt;的时候，你的策略没选对。&lt;/p&gt;&#xA;&lt;p&gt;选择缓存策略本质上是一道选择题：你的业务能不能接受缓存冷启动时的那几秒&amp;quot;冷&amp;quot;？如果能，最省事的策略就够了；如果不能，你需要付出什么代价来预热。&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;本文是一篇决策导向的文章，不是操作教程。不教你怎么配 Redis，也不讲缓存穿透/击穿——只聚焦冷启动场景下三种策略的选择问题。&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;一先看矩阵你的系统在哪个象限&#34;&gt;&lt;a href=&#34;#%e4%b8%80%e5%85%88%e7%9c%8b%e7%9f%a9%e9%98%b5%e4%bd%a0%e7%9a%84%e7%b3%bb%e7%bb%9f%e5%9c%a8%e5%93%aa%e4%b8%aa%e8%b1%a1%e9%99%90&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;一、先看矩阵：你的系统在哪个象限？&#xA;&lt;/h2&gt;&lt;p&gt;选策略之前，先看两个变量。&lt;/p&gt;&#xA;&lt;p&gt;第一个是&lt;strong&gt;缓存缺失容忍度&lt;/strong&gt;：你的业务能不能接受缓存冷启动时的&amp;quot;缓存穿透&amp;quot;？&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;高容忍：CMS、后台管理、数据分析平台&lt;/li&gt;&#xA;&lt;li&gt;低容忍：秒杀、实时推荐、高并发 API&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;lt; 10,000 条，预热耗时 &amp;lt; 10ms&lt;/li&gt;&#xA;&lt;li&gt;高成本：热点数据 &amp;gt; 100,000 条，预热耗时 &amp;gt; 100ms 或需要大量计算&lt;/li&gt;&#xA;&lt;/ul&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 style=&#34;text-align: center&#34;&gt;低成本预热&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&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 style=&#34;text-align: center&#34;&gt;惰性加载+保护&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&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 style=&#34;text-align: center&#34;&gt;主动预热&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;渐进式预热（或架构优化）&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;下面逐个拆解每个象限。你可以在读的过程中不断回看这个矩阵，找到自己业务的位置。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;二高容忍--低成本惰性加载--保护&#34;&gt;&lt;a href=&#34;#%e4%ba%8c%e9%ab%98%e5%ae%b9%e5%bf%8d--%e4%bd%8e%e6%88%90%e6%9c%ac%e6%83%b0%e6%80%a7%e5%8a%a0%e8%bd%bd--%e4%bf%9d%e6%8a%a4&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;二、高容忍 × 低成本：惰性加载 + 保护&#xA;&lt;/h2&gt;&lt;p&gt;大多数团队第一次遇到冷启动穿透时的反应是：加预热。但有时候你不需要预热——你需要的只是一个保护方案。&lt;/p&gt;&#xA;&lt;p&gt;惰性加载的策略很简单：不预加载任何数据，请求到了才查缓存，缓存没有就去数据库查，查到了再写回缓存。零启动成本，系统重启后立刻可用。代价是第一次请求慢，并发够大时数据库可能被打穿。&lt;/p&gt;&#xA;&lt;p&gt;但惰性加载最容易被忽视的问题不是&amp;quot;慢&amp;quot;，是&lt;strong&gt;雪崩效应&lt;/strong&gt;。假设你的服务有 50 个实例同时重启，每个实例的缓存都是空的。这时候哪怕只有一个请求打到每个实例上，数据库就要承受 50 倍的瞬时压力。如果用户的请求再密集一些——比如所有实例同时收到 100 个请求——数据库瞬间就面临 5000 个并发查询。&lt;/p&gt;&#xA;&lt;p&gt;50 倍的放大效应——这就是雪崩。&lt;/p&gt;&#xA;&lt;h3 id=&#34;保护怎么做&#34;&gt;&lt;a href=&#34;#%e4%bf%9d%e6%8a%a4%e6%80%8e%e4%b9%88%e5%81%9a&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;保护怎么做？&#xA;&lt;/h3&gt;&lt;p&gt;&amp;ldquo;保护&amp;quot;就是在惰性加载的基础上加一个断路器或限流器：当数据库连接数超过某个阈值时，拒绝后续请求，或者返回降级响应。&lt;/p&gt;&#xA;&lt;p&gt;常见的保护手段有三种：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;连接池限流&lt;/strong&gt;：设置数据库连接池上限，超过上限的请求排队或超时。简单有效，但排队可能导致请求堆积。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;断路器&lt;/strong&gt;：当数据库错误率达到阈值（比如常见默认值 50%，Hystrix 标准，请根据业务调整），断路器打开，直接返回降级响应，不再请求数据库。这能保护数据库不被完全打垮。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;缓存空值&lt;/strong&gt;：当数据库查询为空时，也把&amp;quot;空结果&amp;quot;缓存起来（设置较短 TTL），避免同一 Key 的反复穿透。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;这其实是一个权衡：你是接受少量请求的慢响应，还是接受系统启动的额外延迟？&lt;/p&gt;&#xA;&lt;h3 id=&#34;实测数据&#34;&gt;&lt;a href=&#34;#%e5%ae%9e%e6%b5%8b%e6%95%b0%e6%8d%ae&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;实测数据&#xA;&lt;/h3&gt;&lt;p&gt;我跑了一组模拟实验，配置如下：10,000 个缓存键，20% 是热点键（占 80% 访问量），50 个并发 worker，总共 10,000 次请求。运行环境：Go 1.26.4，本地单机 Redis，单表简单查询。&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&gt;策略&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;缓存命中率&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;DB 查询数&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;DB 峰值连接&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;平均延迟&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;惰性加载+保护&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;61.8%&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;3,816&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;50&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;~3.6ms&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;主动预热&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;82.2%&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;1,778&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;0&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;~1.8ms&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;渐进式预热&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;82.1%&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;1,790&lt;/td&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;~1.9ms&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;惰性加载+保护的命中率只有 62%，DB 峰值连接数冲到 50（模拟的断路器上限），平均延迟接近 3.7ms——比预热方案慢了将近一倍。&lt;/p&gt;&#xA;&lt;p&gt;但注意：这个数据是在&lt;strong&gt;高并发、热点集中&lt;/strong&gt;的场景下测的。如果你的系统 QPS 很低，惰性加载的表现完全不同——命中率会更高，DB 连接数也不会成为瓶颈。&lt;/p&gt;&#xA;&lt;h3 id=&#34;什么时候选这个象限&#34;&gt;&lt;a href=&#34;#%e4%bb%80%e4%b9%88%e6%97%b6%e5%80%99%e9%80%89%e8%bf%99%e4%b8%aa%e8%b1%a1%e9%99%90&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;什么时候选这个象限？&#xA;&lt;/h3&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;QPS 低&lt;/strong&gt;：系统日常 QPS &amp;lt; 100，数据库扛得住偶尔的全量穿透&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;容忍度高&lt;/strong&gt;：首次访问的几秒延迟对业务无影响（如 CMS、后台管理）&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;数据量大&lt;/strong&gt;：全量预热成本太高，且大部分数据可能永远不会被访问&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;快速迭代&lt;/strong&gt;：服务频繁重启/发布，没有时间等预热完成&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;如果你在做一个内部管理系统，用户每天就几十个人用，缓存预热完全是浪费感情。惰性加载 + 一个简单的限流就够用了。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/cache-warmup-strategies/p2-lazy-loading-flow.png&#34; alt=&#34;惰性加载流程&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;三低容忍--低成本主动预热&#34;&gt;&lt;a href=&#34;#%e4%b8%89%e4%bd%8e%e5%ae%b9%e5%bf%8d--%e4%bd%8e%e6%88%90%e6%9c%ac%e4%b8%bb%e5%8a%a8%e9%a2%84%e7%83%ad&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;三、低容忍 × 低成本：主动预热&#xA;&lt;/h2&gt;&lt;p&gt;如果你的业务容忍度低——首次访问延迟不可接受，那就主动预热：系统启动时，先把热点数据加载到缓存里。&lt;/p&gt;&#xA;&lt;p&gt;我的做法是：既然冷启动穿透是因为缓存冷，那就让缓存不冷。提前把预测会被频繁访问的数据加载进去，让系统在对外提供服务前就已经&amp;quot;暖&amp;quot;了。&lt;/p&gt;&#xA;&lt;h3 id=&#34;关键问题预热什么&#34;&gt;&lt;a href=&#34;#%e5%85%b3%e9%94%ae%e9%97%ae%e9%a2%98%e9%a2%84%e7%83%ad%e4%bb%80%e4%b9%88&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;关键问题：预热什么？&#xA;&lt;/h3&gt;&lt;p&gt;主动预热最核心的问题不是&amp;quot;怎么预热&amp;rdquo;，而是&amp;quot;预热什么&amp;quot;。加载了不该加载的数据，比不加载更糟糕——浪费内存、拖慢启动。&lt;/p&gt;&#xA;&lt;p&gt;我一般把预热数据分为两类：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;可预测的热点&lt;/strong&gt;：比如电商系统的商品详情、配置中心的配置项、用户的权限数据——这些几乎 100% 会被访问&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;统计出来的热点&lt;/strong&gt;：通过历史访问日志分析出来的高频 Key，比如过去 24 小时 PV Top 1000&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;第二类有一个陷阱：热点是会变的。昨天的高频 Key 今天可能就冷下来了。所以主动预热通常需要一个&amp;quot;预热 + 定期刷新&amp;quot;的组合方案。热点预测永远有误差。我常用的做法是：对预测的热点设置较短的 TTL，让缓存自己&amp;quot;验证&amp;quot;这个热点是否真的热——如果访问频率低，TTL 过期后自然淘汰。&lt;/p&gt;&#xA;&lt;p&gt;怎么做定期刷新？两种思路：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;定时全量刷新&lt;/strong&gt;：每 N 分钟重新跑一次热点分析，重新加载热点数据。简单但浪费——热点数据可能没变。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;增量监听&lt;/strong&gt;：监听数据库的变更日志（如 MySQL 的 binlog，可以使用 Canal、Debezium 等成熟 CDC 工具），数据变了才更新缓存。成本高但精准。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;大部分团队从定时全量刷新开始就够了——又不是所有数据每秒都在变。&lt;/p&gt;&#xA;&lt;p&gt;还有一个容易被忽略的问题：&lt;strong&gt;预热的数据应该设置什么样的 TTL？&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;如果你预热的商品详情设置 24 小时 TTL，但商品价格在 2 小时后变了呢？用户看到的价格就是错的。我踩过这个坑之后的做法是：预热数据设置较短的 TTL（比如 5 分钟），同时配合定期刷新来续期。这其实是一个自动降级机制——刷新任务挂了后缓存自动过期，系统退回到惰性加载模式。虽然不是最优状态，但至少不会提供过期数据。&lt;/p&gt;&#xA;&lt;p&gt;注意：所有预热数据的 TTL 不要设成相同的值。加上随机偏移（比如基础值 ± 随机范围），避免同时过期触发第二次雪崩。&lt;/p&gt;&#xA;&lt;h3 id=&#34;启动时间的账&#34;&gt;&lt;a href=&#34;#%e5%90%af%e5%8a%a8%e6%97%b6%e9%97%b4%e7%9a%84%e8%b4%a6&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;启动时间的账&#xA;&lt;/h3&gt;&lt;p&gt;预热是要花时间的，而且这时间花在&amp;quot;系统启动阶段&amp;quot;——也就是你最希望系统快速上线的时候。&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 style=&#34;text-align: center&#34;&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;1,000 条&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;~0.5ms&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;10,000 条&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;~3ms&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;100,000 条&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;~28ms&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;1,000,000 条&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;~320ms&lt;/td&gt;&#xA;          &lt;td&gt;已不可忽略&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;大多数业务场景的热点数据在 1,000-10,000 条这个量级，预热的成本不到 3ms。从这个角度看，主动预热几乎是&amp;quot;免费的&amp;quot;。&lt;/p&gt;&#xA;&lt;p&gt;但有一个例外：如果你的热点数据是百万级的，320ms 的预热时间在微服务架构中可能触发健康检查的超时。这个场景我会放到后面的&amp;quot;高成本&amp;quot;象限讨论。&lt;/p&gt;&#xA;&lt;h3 id=&#34;怎么实现&#34;&gt;&lt;a href=&#34;#%e6%80%8e%e4%b9%88%e5%ae%9e%e7%8e%b0&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;怎么实现？&#xA;&lt;/h3&gt;&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-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Warmup&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;redis&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;redis&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Client&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hotKeys&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;loadHotKeys&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fmt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Errorf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;加载热点列表失败: %w&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 批量写入 Redis&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pipe&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;redis&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Pipeline&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;item&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;range&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hotKeys&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pipe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;item&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;item&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;item&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;TTL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pipe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Exec&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 预热允许部分失败，但至少记录日志&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Printf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;预热部分写入失败: %v&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这段代码在 Spring Boot 中通常放在 &lt;code&gt;@PostConstruct&lt;/code&gt; 或 &lt;code&gt;CommandLineRunner&lt;/code&gt; 里（注意：Spring Boot 3.x + Java 17+ 需要使用 &lt;code&gt;jakarta.annotation&lt;/code&gt; 包），在 Go 中放在 &lt;code&gt;main()&lt;/code&gt; 函数中服务启动之前。&lt;/p&gt;&#xA;&lt;p&gt;注意：如果使用 &lt;code&gt;go Warmup()&lt;/code&gt; 异步预热，服务启动时缓存可能尚未就绪。Spring Boot 的 &lt;code&gt;@PostConstruct&lt;/code&gt; 默认同步阻塞。两种语言的预热语义不同，需要根据业务决定。&lt;/p&gt;&#xA;&lt;p&gt;还有一个容易被忽略的问题：&lt;strong&gt;预热失败怎么办？&lt;/strong&gt; 如果数据库在预热时挂了，预热函数返回错误，你的服务应该拒绝启动，还是先启动再说？&lt;/p&gt;&#xA;&lt;p&gt;我一般这样区分：对于秒杀系统，缓存没有预热好就上线等于灾难——应该阻止启动。对于一般业务，更合理的做法是：预热失败打印警告，服务继续启动，让惰性加载兜底。&lt;/p&gt;&#xA;&lt;h3 id=&#34;多实例场景的陷阱&#34;&gt;&lt;a href=&#34;#%e5%a4%9a%e5%ae%9e%e4%be%8b%e5%9c%ba%e6%99%af%e7%9a%84%e9%99%b7%e9%98%b1&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;多实例场景的陷阱&#xA;&lt;/h3&gt;&lt;p&gt;50 个实例同时重启，每个都执行一次预热——等于 50 次同样的数据库查询同时打过去。经典解法是使用分布式锁或 leader 选举，只让一个实例执行预热，预热结果共享给其他实例。&lt;/p&gt;&#xA;&lt;p&gt;如果你的业务已经上了服务发现或配置中心，可以借助这些基础设施来做 leader 预热：启动时先尝试获取分布式锁，拿到锁的实例负责预热，其他实例等待预热完成或直接使用缓存中的现有数据。&lt;/p&gt;&#xA;&lt;h3 id=&#34;什么时候选这个象限-1&#34;&gt;&lt;a href=&#34;#%e4%bb%80%e4%b9%88%e6%97%b6%e5%80%99%e9%80%89%e8%bf%99%e4%b8%aa%e8%b1%a1%e9%99%90-1&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;什么时候选这个象限？&#xA;&lt;/h3&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;热点可预测&lt;/strong&gt;：你知道哪些数据会被频繁访问（比如商品详情、配置项）&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;容忍度低&lt;/strong&gt;：首次访问延迟不可接受（比如秒杀系统、实时推荐）&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;数据量适中&lt;/strong&gt;：热点数据量 &amp;lt; 100,000 条，预热成本可控&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;服务重启不频繁&lt;/strong&gt;：每次重启都做一次预热，启动频率越低越划算&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/cache-warmup-strategies/p3-warmup-compare.png&#34; alt=&#34;预热对比&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;四高容忍--高成本渐进式预热&#34;&gt;&lt;a href=&#34;#%e5%9b%9b%e9%ab%98%e5%ae%b9%e5%bf%8d--%e9%ab%98%e6%88%90%e6%9c%ac%e6%b8%90%e8%bf%9b%e5%bc%8f%e9%a2%84%e7%83%ad&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;四、高容忍 × 高成本：渐进式预热&#xA;&lt;/h2&gt;&lt;p&gt;主动预热的前提是你&amp;quot;知道&amp;quot;热点是什么。但如果热点不确定呢？&lt;/p&gt;&#xA;&lt;p&gt;比如你做的是一个社交 Feed 系统，每个用户看到的内容都不一样。你没法提前知道&amp;quot;哪个用户的 Feed 会被访问最多&amp;quot;，因为热点完全取决于用户行为。&lt;/p&gt;&#xA;&lt;p&gt;这时候主动预热没用——你不可能预热所有用户的 Feed。惰性加载+保护又太被动——用户量大，首次访问的延迟会拉低整体体验。&lt;/p&gt;&#xA;&lt;p&gt;渐进式预热就是第三种选择：&lt;strong&gt;先让系统起来，然后边服务边预热。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;这听起来像是&amp;quot;两全其美&amp;quot;，但它有一个隐含的代价：预热期间，部分请求仍然会穿透到数据库。渐进式预热不是在&amp;quot;要不要穿透&amp;quot;之间选，而是在&amp;quot;穿透多少&amp;quot;和&amp;quot;等待多久&amp;quot;之间找平衡点。&lt;/p&gt;&#xA;&lt;h3 id=&#34;怎么做&#34;&gt;&lt;a href=&#34;#%e6%80%8e%e4%b9%88%e5%81%9a&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;怎么做？&#xA;&lt;/h3&gt;&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-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;GradualWarmup&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;redis&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;redis&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Client&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 使用游标分页加载，避免一次性加载全部热点到内存&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cursor&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;batchSize&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hotKeys&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nextCursor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;loadHotKeysPage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cursor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;batchSize&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Printf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;分批加载热点失败: %v&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;break&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 每批写入 Redis&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pipe&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;redis&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Pipeline&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;item&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;range&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hotKeys&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pipe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;item&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;item&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;item&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;TTL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pipe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Exec&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Printf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;预热部分写入失败: %v&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 每批之间间隔 200ms，给数据库喘息空间&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Sleep&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;200&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Millisecond&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nextCursor&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;break&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cursor&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nextCursor&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;关键参数是 &lt;code&gt;batchSize&lt;/code&gt; 和批间隔。batchSize 太大等于全量预热，太小又预热太慢。我的建议是从 100 开始，根据预热期间数据库的负载动态调整。&lt;/p&gt;&#xA;&lt;p&gt;从我的模拟实验来看，渐进式预热的表现和主动预热非常接近：命中率 82.1%，DB 峰值连接只有 1。&lt;/p&gt;&#xA;&lt;p&gt;它和主动预热的核心差异不是性能，是&lt;strong&gt;灵活性&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;主动预热：启动时一次加载完，之后就不管了&lt;/li&gt;&#xA;&lt;li&gt;渐进式预热：持续加载，可以动态调整预热策略&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;比如，你可以根据预热期间的缓存命中率来动态调整：如果命中率已经超过 90%，可以放慢预热速度甚至停止；如果命中率仍然很低，加快预热速度。&lt;/p&gt;&#xA;&lt;h3 id=&#34;和主动预热怎么选&#34;&gt;&lt;a href=&#34;#%e5%92%8c%e4%b8%bb%e5%8a%a8%e9%a2%84%e7%83%ad%e6%80%8e%e4%b9%88%e9%80%89&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;和主动预热怎么选？&#xA;&lt;/h3&gt;&lt;p&gt;如果你的热点是明确的、稳定的，选主动预热就够了，不需要渐进式的复杂性。只有在你&lt;strong&gt;不确定&lt;/strong&gt;或者&lt;strong&gt;数据量大到一次加载不完&lt;/strong&gt;的时候，渐进式预热才值得。&lt;/p&gt;&#xA;&lt;p&gt;我常用的判断标准：如果预热耗时超过服务启动时间的 20%，就该考虑渐进式预热了。&lt;/p&gt;&#xA;&lt;h3 id=&#34;什么时候选这个象限-2&#34;&gt;&lt;a href=&#34;#%e4%bb%80%e4%b9%88%e6%97%b6%e5%80%99%e9%80%89%e8%bf%99%e4%b8%aa%e8%b1%a1%e9%99%90-2&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;什么时候选这个象限？&#xA;&lt;/h3&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;热点不确定&lt;/strong&gt;：你不知道哪些数据会被频繁访问&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;数据量大&lt;/strong&gt;：全量预热会显著拖慢启动（百万级以上）&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;需要持续更新&lt;/strong&gt;：热点数据会随时间变化，需要持续加载&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;高可用要求&lt;/strong&gt;：不能因为预热而延迟服务启动&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/cache-warmup-strategies/p4-gradual-warmup-flow.png&#34; alt=&#34;渐进式预热流程&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/cache-warmup-strategies/p5-gradual-warmup-timeline.png&#34; alt=&#34;渐进式预热时间轴&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;五低容忍--高成本最棘手的象限&#34;&gt;&lt;a href=&#34;#%e4%ba%94%e4%bd%8e%e5%ae%b9%e5%bf%8d--%e9%ab%98%e6%88%90%e6%9c%ac%e6%9c%80%e6%a3%98%e6%89%8b%e7%9a%84%e8%b1%a1%e9%99%90&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;五、低容忍 × 高成本：最棘手的象限&#xA;&lt;/h2&gt;&lt;p&gt;如果你的业务既对延迟敏感（低容忍），又需要预热大量数据（高成本），矩阵的右下角——这是最难的场景。&lt;/p&gt;&#xA;&lt;p&gt;矩阵上说&amp;quot;渐进式预热（或架构优化）&amp;quot;。我来展开&amp;quot;架构优化&amp;quot;是什么意思。&lt;/p&gt;&#xA;&lt;p&gt;这个象限的核心矛盾是：系统启动必须快，但缓存又不能不预热。渐进式预热可以缓解这个问题——服务先起来，边服务边预热——但预热期间仍然会有部分请求穿透到数据库，低容忍度的业务可能接受不了。&lt;/p&gt;&#xA;&lt;p&gt;这时候有两条路：&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第一条路：本地缓存 + 预热拆分&lt;/strong&gt;&#xA;在应用层加一层本地缓存（如 Go 的 sync.Map、Java 的 Caffeine），热点数据优先从本地缓存读取。预热只预热 Redis 这一层，本地缓存靠惰性加载。启动速度几乎不受影响，本地缓存命中率在预热完成后自然下降。代价是多了一层缓存一致性要维护。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/cache-warmup-strategies/p7-local-cache-split.png&#34; alt=&#34;本地缓存&amp;#43;预热拆分&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第二条路：重新评估缓存必要性&lt;/strong&gt;&#xA;有时候&amp;quot;低容忍 + 高成本&amp;quot;意味着你的架构选型有问题——缓存不是最好的解决方案。比如可以考虑：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;数据分片：把一个大 Redis 拆成多个，每个分片的数据量变小，预热成本自然降低&lt;/li&gt;&#xA;&lt;li&gt;读写分离：读走从库、写走主库，减少缓存的压力&lt;/li&gt;&#xA;&lt;li&gt;换个思路：是否可以用 CDN 或预计算来代替缓存？&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/cache-warmup-strategies/p1-decision-matrix.png&#34; alt=&#34;决策矩阵全景&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;六决策树三步走到答案&#34;&gt;&lt;a href=&#34;#%e5%85%ad%e5%86%b3%e7%ad%96%e6%a0%91%e4%b8%89%e6%ad%a5%e8%b5%b0%e5%88%b0%e7%ad%94%e6%a1%88&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;六、决策树：三步走到答案&#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;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;能 → 惰性加载+保护就够了（回到高容忍×低成本）&lt;/li&gt;&#xA;&lt;li&gt;不能 → 进入下一步&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;你知道热点数据是什么吗？&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;知道 → 主动预热（低容忍×低成本）&lt;/li&gt;&#xA;&lt;li&gt;不知道 → 进入下一步&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;预热会拖慢启动吗？&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;不会 → 主动预热（即使用统计出来的热点也值得）&lt;/li&gt;&#xA;&lt;li&gt;会 → 渐进式预热（高容忍×高成本 或 低容忍×高成本）&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;这个决策树覆盖了大多数场景。如果你走到第三层还是不确定，说明你的场景比较特殊——可能根本不需要缓存，或者需要重新考虑架构。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/cache-warmup-strategies/p6-decision-tree.png&#34; alt=&#34;决策树&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;比如有些场景下缓存 Key 设计不合理，大部分请求都集中在少数 Key 上——这时候与其纠结预热策略，不如先看看缓存的设计是否合理。&lt;/p&gt;&#xA;&lt;h3 id=&#34;矩阵之外什么时候不该用缓存&#34;&gt;&lt;a href=&#34;#%e7%9f%a9%e9%98%b5%e4%b9%8b%e5%a4%96%e4%bb%80%e4%b9%88%e6%97%b6%e5%80%99%e4%b8%8d%e8%af%a5%e7%94%a8%e7%bc%93%e5%ad%98&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;矩阵之外：什么时候不该用缓存？&#xA;&lt;/h3&gt;&lt;p&gt;最后说一个反直觉的结论：&lt;strong&gt;有时候不做缓存，比做缓存好。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;如果你的数据库本身响应就很快（比如单表百万级的 PostgreSQL，简单主键查询场景），缓存引入的复杂性可能得不偿失。缓存预热、缓存一致性、缓存穿透——这些问题的维护成本，可能超过缓存带来的性能收益。&lt;/p&gt;&#xA;&lt;p&gt;我的判断标准：如果数据库 P99 延迟 &amp;lt; 5ms，且 QPS &amp;lt; 1000，先别急着加缓存（注意：这只是简单查询场景的参考值，复杂 JOIN 或高并发下 P99 远不止 5ms）。先看看慢查询能不能优化。缓存是用来解决&amp;quot;优化解决不了的问题&amp;quot;的，不是用来掩盖设计缺陷的。&lt;/p&gt;&#xA;&lt;p&gt;还有一点：加了缓存不等于加了预热。很多团队上了 Redis 但没做预热，结果上线第一天缓存是空的，数据库被打穿。如果你决定用缓存，至少要确保惰性加载+保护这一层兜底是到位的。先让它不崩，再考虑怎么让它快。&lt;/p&gt;&#xA;&lt;p&gt;我见过一个案例：一个日活百万的 App，缓存集群重启后数据库直接被冲垮，DBA 紧急扩容才恢复。事后排查发现，他们根本没有缓存缺失的保护机制——Redis 一重启，数据库就裸奔。加一个简单的限流，整个事故就能避免。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/cache-warmup-strategies/p8-when-not-to-cache.png&#34; alt=&#34;什么时候不该用缓存&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;回到那个矩阵&#34;&gt;&lt;a href=&#34;#%e5%9b%9e%e5%88%b0%e9%82%a3%e4%b8%aa%e7%9f%a9%e9%98%b5&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;回到那个矩阵&#xA;&lt;/h2&gt;&lt;p&gt;没有银弹。选对策略比选对工具重要。&lt;/p&gt;&#xA;&lt;p&gt;大多数团队遇到冷启动穿透的第一反应是&amp;quot;加预热&amp;quot;——这没错，但问一句&amp;quot;我的场景真的需要预热吗&amp;quot;往往更值钱。&lt;/p&gt;&#xA;&lt;p&gt;回到开头的那个矩阵：你的业务在哪个象限？&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;高容忍 × 低成本&lt;/strong&gt; → 惰性加载+保护，省掉预热的运维成本&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;低容忍 × 低成本&lt;/strong&gt; → 主动预热，命中率最高&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;高容忍 × 高成本&lt;/strong&gt; → 渐进式预热，在灵活性和安全感之间找到平衡&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;低容忍 × 高成本&lt;/strong&gt; → 渐进式预热（或重新考虑架构）&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/cache-warmup-strategies/p9-back-to-matrix-summary.png&#34; alt=&#34;回到矩阵·总结&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;你的业务对&amp;quot;冷&amp;quot;有多敏感？&lt;/p&gt;&#xA;&lt;p&gt;下次遇到缓存重启后数据库 CPU 100% 的问题，先问自己三个问题：我的业务能忍多久？我知道热点在哪吗？预热会影响启动吗？问清楚了，方案自然就出来了。&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/cache-warmup-strategies&#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>
