缓存预热必须在main()中同步执行,且严格按InitDB→InitRedis→preloadCache→http.ListenAndServe()顺序,禁用init()和goroutine;需分页查必要字段、用pipeline写入Redis,并设降级标记与验证机制。

预热必须在 main() 里同步执行,不能放 init() 或 goroutine
缓存预热失败最常见原因就是执行时机错位:放在 init() 里,DB 连接池和 Redis 客户端还没初始化,一调用 db.QueryRow() 就 panic;用 go preloadCache() 异步启动,主 goroutine 在 http.ListenAndServe() 返回后退出,预热中途被强制终止,Redis pipeline 只写了一半,日志显示 “loaded 127/10000”。
正确顺序只能是:InitDB() → InitRedis() → preloadCache() → http.ListenAndServe()。所有依赖必须显式初始化完毕、可安全调用,预热函数才允许执行。
- 禁用任何包级
init()函数做预热逻辑 - 若用 Wire 等 DI 框架,确保
preloadCache是显式调用的函数,不是自动注入的服务 - 异步唯一可行方式:用
sync.WaitGroup+context.WithTimeout包裹,主流程必须等待完成或超时
从 MySQL 加热点数据要分页、限字段、走 pipeline
直接 SELECT * FROM product WHERE is_hot = 1 是线上事故高发操作——大表触发深分页、锁表、OOM,且冗余字段让 Redis 内存暴涨 3 倍。预热不是 dump 全量,而是按访问热度和缓存容量精筛。
实操要点:
立即学习“go语言免费学习笔记(深入)”;
- 只查必要字段:
SELECT id, name, price, updated_at FROM product WHERE status = 1 ORDER BY pv DESC LIMIT 10000 - 用游标分页替代 OFFSET:
WHERE id > ? ORDER BY id LIMIT 100,每次取上一批最后id继续 - 加事务隔离:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED,避免预热中途被更新阻塞 - Redis 写入必须走 pipeline:
pipe := redisClient.Pipeline(),每批 ≤100 条,pipe.Exec(ctx)一次提交
本地缓存选 sync.Map 还是 bigcache?看规模和过期需求
sync.Map 和 bigcache 都是线程安全的,但适用场景差异明显:前者适合万级以内 key、读多写少、无需过期;后者适合十万级以上、需内存可控、能接受少量 GC 开销。
常见误用:
- 用原生
map替代sync.Map—— 并发写直接 panic - 用
sync.Map存带 TTL 的数据 —— 它不支持过期,得自己配定时器清理,极易漏删 - 用
bigcache存结构体 —— 它只认[]byte,必须提前序列化,反序列化失败会静默丢数据
示例(sync.Map 预热):
func preloadCache() {<br> data := fetchHotDataFromDB()<br> for _, item := range data {<br> cache.Store(item.ID, item.Name) // cache 是全局 sync.Map<br> }<br>}
预热失败不阻断启动,但必须设降级标记并验证命中
预热失败就 panic 整个服务,等于把可用性押在一次 DB 查询上,极不可靠。真实系统必须容忍部分失败:比如 Redis 临时不可用,只加载了 70% 数据,服务仍应带降级能力上线。
关键动作:
- 用
context.WithTimeout(ctx, 5*time.Second)包裹整个预热流程,超时记 warn 日志 - 设原子标记:
atomic.StoreBool(&cacheWarmedUp, false),后续请求检查该标记决定是否直连 DB - 预热后必须验证:用
redisClient.Exists(ctx, "test:key")或cache.Load("test:key")主动探活,不能只信日志 “warm up finished” - 验证失败不重试,只记录 error 并维持降级状态 —— 避免雪崩式重连
真正容易被忽略的是验证环节:很多团队写了预热逻辑,但从不校验 key 是否真写进目标 Redis DB、TTL 是否设对、序列化格式是否与运行时 handler 一致。结果服务跑着,缓存永远不命中。



















