启动即预热基础缓存:服务启动时在HTTP监听前批量加载高频数据至Redis,使用pipeline、带前缀与TTL;失败不panic,支持降级;大数据量则通过日志或SCAN识别热点再加载。

启动时直接加载基础缓存数据
服务启动后立刻从数据库批量读取高频访问的固定数据(如配置表、字典项、城市列表),写入 Redis,避免首个请求触发慢查询。关键不是“等请求来再缓存”,而是“启动即就绪”。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 在
main()函数中,redis.Client初始化完成后、HTTP 服务器ListenAndServe之前执行预热逻辑 - 用
pipeline批量写入,避免单条SET循环:调用pipe := client.Pipeline(),累积操作后统一pipe.Exec(ctx) - 对每类数据加前缀(如
"dict:status:")并设合理EX(TTL),别全用永不过期——冷数据残留会挤占内存 - 预热失败(如 DB 连接异常)要记录错误日志但**不 panic**,允许服务降级启动;后续请求仍可走缓存穿透防护逻辑
用 Redis 的 SCAN + 按需 SET 实现热点数据初始化
有些数据量大、无法全量加载(如百万级商品),又需要保障首屏/首页接口响应快,就得识别“真热点”再加载。SCAN 不阻塞主线程,适合启动时轻量扫描 key 或结合业务日志做二次筛选。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 若已有历史访问日志(如 Kafka / 文件),启动时消费最近 1 小时 top 1000
product_id,查 DB 后批量SET product:123 {...} EX 3600 - 若无日志,可用
SCAN遍历旧缓存中带hot:前缀的 key,检查其 TTL 剩余是否 - 避免在 SCAN 过程中调用
GET回源查 DB:先收集 key 列表,再分批(如每次 100 个)查库+写缓存,防止 Redis 连接池耗尽 - SCAN 游标记得用
uint64类型接收,Go 的redis.ScanCmd返回值是cursor, keys, err,别把 cursor 当字符串解析
用 Lua 脚本原子化预热 + 校验
当预热要求强一致性(比如配置变更后必须全部缓存生效才对外提供服务),单靠 Go 代码无法保证多 key 写入中途不被中断。Lua 脚本能一次性执行、原子完成校验与写入。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 写一个 Lua 脚本:先
EXISTS检查所有目标 key 是否都不存在,再MSET+EXPIRE批量设置——避免重复预热覆盖线上更新 - 用
client.Eval(ctx, script, []string{}, keys...)调用,keys参数传 key 列表,ARGV传对应 value 和 TTL,别把 value 拼进脚本字符串里(有注入风险) - 注意 Lua 中
redis.call("SET", KEYS[1], ARGV[1])不能直接设 TTL,得额外调一次redis.call("EXPIRE", KEYS[1], ARGV[2]) - Eval 返回
int64类型结果(1 表示成功写入),Go 层要显式类型断言,别直接fmt.Println(res)看成interface{}导致 panic
预热完成后如何确认没漏关键数据?
上线后发现某些接口首屏慢,大概率是预热遗漏了关联字段(比如查订单页预热了 order:123,但没预热 user:456 导致连带查询慢)。不能只看“是否执行了预热函数”,要看“是否覆盖了真实调用链路”。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 在预热逻辑末尾,用
client.Exists(ctx, "preheat:done:config", "preheat:done:product")检查几个核心 key 是否已存在,存在才认为该模块预热完成 - 把预热过程中的 key 列表写入本地临时文件(如
/tmp/preheat_keys_20240520.log),上线后抽样比对监控系统中实际被访问的 key 前缀分布 - 对 HTTP handler 加一层轻量埋点:首次命中缓存且
ttl 的请求,打日志并上报 key 名——这些就是预热未覆盖但已被访问的“漏网热点” - 别依赖 Redis info command 的
keyspace_hits统计,它包含所有操作(包括运维命令),噪声太大;盯具体业务 key 的GET请求日志更准


















