缓存更新必须在业务handler内显式控制,不可交由中间件统一处理;需严格按DB变更完成后再刷新Redis、通知其他节点清本地缓存,并用原子命令或异步队列保障写入可靠性。

缓存更新触发点必须落在业务 handler 内部
多级缓存(本地缓存 + Redis)的同步不是 Gin 自动做的,它没有内置缓存生命周期管理。你得在 handler 里显式写清楚:什么时候查缓存、没命中怎么查 DB、DB 更新后怎么刷 Redis、要不要清本地缓存。
常见错误是把缓存操作抽到中间件里——比如想统一处理“所有 POST 都清某 key”,但这样会误伤、漏清、或根本不知道该清哪个 key。缓存 key 的语义和业务强相关,只有 handler 知道这次操作影响了哪些数据维度。
- 增删改操作必须在 handler 执行完 DB 变更后,再调用缓存刷新逻辑,顺序不能反
- 不要在中间件里做
c.Abort()后还执行缓存写入,容易写入脏数据 - 如果用了 gorm,注意事务未提交前就写缓存,会导致缓存与 DB 瞬时不一致
本地缓存失效必须主动通知其他节点
Gin 进程内本地缓存(如 map 或 sync.Map)天然不跨进程。微服务部署多实例时,一个节点更新了 DB 并刷新了自己本地缓存,其他节点仍拿着旧值——这就是典型的缓存不一致。
解决办法不是“禁止用本地缓存”,而是补上通知机制:
- 用 Redis Pub/Sub 发布变更事件,各节点订阅后主动清本地对应 key
- 避免用 HTTP 广播(延迟高、失败难感知),也别轮询(浪费资源)
- 通知消息体至少包含
cache_key和operation(如"delete"或"refresh"),不要只传业务 ID - 本地缓存建议加
ttl保底,防止通知丢失导致永久脏数据
Redis 缓存写入必须带原子性保障
DB 更新成功但 Redis 写失败,会导致缓存永远不更新。单纯 try-catch + log 是不够的,要设计可恢复的写入路径。
推荐组合方案:
- DB 更新成功后,把缓存刷新任务塞进 Redis List(如
cache_refresh_queue),用独立消费者 goroutine 持续捞取并重试 - 或者用 Redis 的
SET key value EX 3600 NX这类原子命令,避免并发覆盖 - 避免在 handler 中直接
redis.Set()后就认为完成——网络抖动、超时、连接中断都可能导致失败 - 如果用 gorm hook 触发缓存更新,确保 hook 在事务
Commit之后执行,否则回滚时缓存已写入就不可逆
sync.Pool 不能用于缓存业务数据
有人看到 sync.Pool 就想拿来存用户 session 或配置项,这是严重误用。它的设计目标是复用临时对象(如 bytes.Buffer、json.Decoder),不是长期存储。
关键限制:
-
sync.Pool中的对象可能被 GC 随时回收,无法保证存活时间 - 不同 goroutine 获取到的可能是不同实例,不满足缓存一致性要求
- Pool 没有 key-value 查找能力,不能替代
map或 Redis - 真正需要复用的只是序列化/反序列化缓冲区这类“一次性中间态”,不是业务实体本身
本地缓存该用 freecache、ristretto 或带淘汰策略的 lru 库,而不是靠 Pool 勉强撑。


















