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+

九、结语

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

相关推荐
sing~~1 小时前
SpringCloud的了解和使用
后端·spring·spring cloud
随风,奔跑1 小时前
Spring Cloud Alibaba(六)-链路追踪SkyWalking
java·后端·spring·skywalking
云烟成雨TD1 小时前
Spring AI 1.x 系列【30】向量数据库:核心 API 和入门案例
java·人工智能·spring
KeyonY2 小时前
车联网规则引擎设计之热更新与版本管理
redis·golang·车联网
许彰午2 小时前
CacheSQL:一个面向政务系统的内存缓存数据库中间件
java·数据库·缓存·中间件·面试·开源软件·政务
代码中介商2 小时前
Linux多线程编程完全指南(下):线程同步与互斥锁
linux·redis·线程·互斥锁
敖正炀2 小时前
Spring 深度内核-核心容器与扩展机制-SpringFactoriesLoader 到 AutoConfiguration.imports:插件化演进
spring
敖正炀2 小时前
Spring 深度内核-核心容器与扩展机制-类型转换与数据绑定体系:ConversionService、PropertyEditor
spring
Lyyaoo.2 小时前
Session粘滞性问题->Redis实现session共享
数据库·redis·缓存
空中海2 小时前
Spring Cloud第三篇:通信篇 — OpenFeign 与负载均衡
spring·spring cloud·负载均衡