Redis缓存预热

一、前言:为什么需要 Redis 缓存预热?

想象这样一个场景:

  • 系统刚上线或重启
  • 所有缓存为空(冷启动)
  • 大量用户同时访问商品详情、用户信息等接口
  • 请求全部穿透到数据库 → DB 瞬间被打垮

这就是典型的"缓存雪崩"前置问题------缓存未就绪!

缓存预热(Cache Warm-up) 就是在系统启动前或低峰期,主动将热点数据加载到 Redis 中,避免冷启动带来的性能灾难。

本文将从策略设计、实现方案、避坑指南三个维度,带你掌握 Redis 缓存预热的完整实践。


二、什么数据值得预热?

不是所有数据都需要预热!聚焦高访问频次 + 低变更频率的数据:

数据类型 是否适合预热 示例
✅ 热点商品信息 双11主会场商品
✅ 用户基础资料 VIP 用户 profile
✅ 配置字典表 国家列表、状态码映射
❌ 用户订单列表 每人不同,且实时变化
❌ 实时库存 高频更新,预热即过期

💡 经验法则

  • 访问量 Top 1000 的 key
  • 更新频率 < 1 次/小时
  • 数据量可控(避免 OOM)

三、四大预热策略对比

策略 原理 优点 缺点 适用场景
启动时预热 应用启动时加载 简单直接 阻塞启动、单点压力大 小型系统
定时任务预热 定时从 DB 同步 不阻塞主流程 有延迟 中低频更新数据
双写+渐进预热 写 DB 时同步写缓存 实时性强 逻辑复杂 核心热点数据
离线计算+批量导入 大数据平台生成快照 海量数据支持 架构复杂 超大型系统

推荐组合启动预热(核心) + 定时任务(兜底)


四、实战 1:Spring Boot 启动时预热(最常用)

场景

  • 系统启动时加载 1000 个热门商品到 Redis

代码实现

java 复制代码
@Component
public class CacheWarmUpRunner implements CommandLineRunner {

    @Autowired
    private ProductMapper productMapper;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public void run(String... args) {
        log.info("开始缓存预热...");

        // 1. 查询热点商品(按访问量排序)
        List<Product> hotProducts = productMapper.selectHotProducts(1000);

        // 2. 批量写入 Redis(使用 Pipeline 提升性能)
        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            hotProducts.forEach(product -> {
                String key = "product:" + product.getId();
                String value = JSON.toJSONString(product);
                connection.set(key.getBytes(), value.getBytes());
                connection.expire(key.getBytes(), Duration.ofHours(2).getSeconds());
            });
            return null;
        });

        log.info("缓存预热完成,共加载 {} 个商品", hotProducts.size());
    }
}

关键优化

  • 使用 executePipelined 减少网络往返
  • 设置合理 TTL(如 2 小时),避免脏数据长期滞留

五、实战 2:OpenResty + Lua 预热(网关层兜底)

即使后端做了预热,仍可能因扩容、故障导致部分实例缓存缺失。
在 OpenResty 层实现"懒加载 + 自动回源预热"

Lua 复制代码
location /api/product {
    content_by_lua_block {
        local id = ngx.var.arg_id
        local key = "product:" .. id

        -- 1. 先查 Redis
        local redis = require "resty.redis"
        local red = redis:new()
        red:connect("127.0.0.1", 6379)
        local cache = red:get(key)

        if cache and cache ~= ngx.null then
            ngx.say(cache)
            return
        end

        -- 2. 缓存未命中:回源查询 DB(通过内部 API)
        local res = ngx.location.capture("/internal/product?id=" .. id)
        
        if res.status == 200 then
            -- 3. 写入 Redis(设置较短 TTL,如 60s)
            red:set(key, res.body)
            red:expire(key, 60)
            ngx.print(res.body)
        else
            ngx.status = res.status
            ngx.print(res.body)
        end

        red:close()
    }
}

🔁 效果

  • 首次请求稍慢,但后续请求极速响应
  • 自动重建缓存,无需人工干预

六、高级技巧:分片预热 + 限流保护

问题

  • 一次性加载 100 万条数据 → Redis CPU 打满
  • 网络带宽耗尽

解决方案:分批次 + 限速

java 复制代码
// 分页预热
int pageSize = 1000;
int total = productMapper.countHotProducts();
int pages = (total + pageSize - 1) / pageSize;

for (int i = 0; i < pages; i++) {
    List<Product> batch = productMapper.selectHotProductsPage(i, pageSize);
    // 写入 Redis...
    
    // 限速:每批间隔 100ms
    Thread.sleep(100);
}

📊 建议指标

  • 单次批量 ≤ 5000 条
  • QPS 控制在 Redis 承载能力的 50% 以内

七、避坑指南:常见错误与解决方案

坑点 后果 解决方案
未设 TTL 脏数据永久滞留 所有预热 key 必须设置 TTL
全量预热 Redis OOM 只预热 Top N 热点数据
同步阻塞启动 应用启动超时 改为异步线程 + 健康检查延迟就绪
忽略数据一致性 缓存与 DB 不一致 预热后监听 binlog 增量更新

💡 健康检查示例(K8s)

复制代码
readinessProbe:
  exec:
    command: ["sh", "-c", "redis-cli EXISTS product:1"]
  initialDelaySeconds: 30  # 等待预热完成

八、监控与验证

如何确认预热成功?

  1. Redis 监控

    bash 复制代码
    redis-cli INFO memory  # 查看 used_memory
    redis-cli DBSIZE       # 查看 key 数量
  2. 应用日志:记录预热数量与耗时

  3. 压测对比

    • 冷启动 QPS:500
    • 预热后 QPS:15,000+

九、结语

感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!

相关推荐
用户3074596982076 小时前
Redis 延时队列详解
redis
烤代码的吐司君8 小时前
Redis 数据结构 ZSet, BIT, HyperLogLog,Geo 空间数据
redis·后端
Flittly14 小时前
【AgentScope Java新手村系列】(14)人机交互
java·spring boot·spring
leeyi3 天前
Checkpoint 机制:Agent 怎么在断电后接着跑
redis·aigc·agent
云技纵横4 天前
一个 @Async 让循环依赖暴雷:Spring 代理的暗坑
redis
犯困蛋挞yy4 天前
用Claude快速解决Redis代码报错反复无解的问题
redis
唐青枫5 天前
Java Spring WebFlux 实战指南:用 Mono、Flux 和 WebClient 写响应式接口
java·spring
小七-七牛开发者5 天前
TokenPilot:让 LLM Agent 长会话成本降 60%+ 的上下文管理
缓存·agent·token·context·上下文·推理成本
咖啡八杯6 天前
GoF设计模式——策略模式
java·后端·spring·设计模式
Flittly7 天前
【AgentScope Java新手村系列】(11)中断与恢复
java·spring boot·spring