缓存预热必须在main()中db/redis初始化后、http.listenandserve前同步执行;禁用init()和goroutine异步预热,需超时控制、分页查询、pipeline写入、锁机制及降级校验。

缓存预热必须在 http.ListenAndServe 启动前完成,且所有依赖(DB、Redis 客户端、配置)已初始化完毕;否则请求会穿透到下游,或直接 panic 报 redis: connection closed、nil pointer dereference。
预热该放在 main() 里还是 init()?
必须放在 main() 函数内,紧接在 DB/Redis 初始化之后、http.ListenAndServe 之前。init() 太早——此时 redis.Client 可能还没赋值,sql.DB 连接池也未建立,一调用 client.Get() 或 db.QueryRow() 就 panic。
常见错误现象:panic: runtime error: invalid memory address or nil pointer dereference,堆栈指向预热函数里某次 Redis 查询。
- 正确顺序:InitDB() → InitRedis() → preloadCache() → http.ListenAndServe()
- 别把预热逻辑塞进任何包级
init()函数 - 如果用了依赖注入框架(如 wire),确保预热函数是显式调用的,不是被自动 resolve 的“服务”
用 goroutine 异步预热安全吗?
不安全。Go 主 goroutine 在 http.ListenAndServe 返回后就退出,其他 goroutine 可能被强制终止,导致预热中途丢弃、缓存写入不全、甚至连接泄漏。
常见错误现象:日志显示只加载了前 200 条数据,但 DB 查了 1w 行;或者预热刚写完几条,redis: connection closed 就报出来。
- 禁止写
go preloadCache() - 若耗时确实长(>5s),用
context.WithTimeout包裹整个预热流程,超时后记录 warn 日志并设降级标记(如atomic.StoreBool(&cacheWarmedUp, false)) - 真要“后台补全”,可启动一个带 WaitGroup 的 goroutine,但主流程仍需同步等待其完成或超时 —— 不是“放出去不管”
从 MySQL 加热点数据到 Redis,怎么避免拖垮数据库?
直接 SELECT * FROM items WHERE is_hot = true 是高危操作:大表会触发深分页、锁表、OOM;字段冗余还会让 Redis 内存暴涨。
使用场景:电商首页商品、用户中心 profile、运营开关配置。
- 只查必要字段:
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,每次取 last_id 继续 - 加显式隔离级别:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ,避免预热中途数据被改 - 批量走 pipeline:
pipe := redisClient.Pipeline(); for ... { pipe.Set(...); }; _, err := pipe.Exec(ctx),每批 ≤1000 条 - 跨 Pod 去重靠 SETNX 锁:
redisClient.SetNX(ctx, "cache_warmup_lock", "1", 30*time.Second),失败则跳过或重试
内存缓存(sync.Map / bigcache)预热要注意什么?
sync.Map 不支持 TTL 和容量淘汰,bigcache 不支持单 key 过期 —— 预热进去的数据,除非手动清理,否则一直留着。这对「几乎不变」的数据(国家列表、协议版本)没问题,但对「低频变更」数据(活动开关)容易脏读。
常见错误现象:运营改了开关,前端仍看到旧状态,重启服务才生效。
- 别拿
sync.Map当通用缓存用;高频更新 + 过期需求,直接上ristretto或github.com/bluele/gcache - 预热后别忘了校验:
if n := cache.Len(); n == 0 { log.Warn("preloadCache returned zero items") } - bigcache 预热时注意
MaxCost单位是字节,不是条数;设成100 * 1024 * 1024(100MB)比NumCounters: 1e7更靠谱 - 对一致性敏感的场景(如用户余额),内存缓存预热本身就不适用,应走 Redis + Lua 原子读写
最易被忽略的一点:预热失败不等于服务不可用,但必须有明确的降级路径和可观测性 —— 比如日志里带 cache_warmup_failed=1 标签,监控告警能捕获,且 handler 中检查 !cacheWarmedUp 时自动 fallback 到 DB 查询。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











