缓存中间件需调用c.abort()才能跳过数据库查询,否则c.next()仍执行db操作;缓存键须包含请求方法、路径、参数及用户标识;ttl应加随机偏移防雪崩。

缓存中间件在 Gin 中不是“自动减少数据库查询”的魔法开关,它只在你明确命中缓存键、且跳过后续处理链时才真正省掉一次 DB 查询。关键在于:是否短路、缓存键设计是否合理、以及业务逻辑是否把 DB 查询放在了中间件之后。
缓存中间件必须调用 c.Abort() 才能跳过数据库查询
很多新手写的“缓存中间件”只是查了 Redis,但没终止流程,结果 c.Next() 仍会执行后续的数据库操作——缓存形同虚设。
- 正确做法:查到缓存后立即调用
c.String(200, value)或c.JSON(200, data),再跟c.Abort() - 错误写法:
if hit { cacheValue := ... } c.Next()—— 这里c.Next()一定会触发下游 handler,DB 查询照常发生 -
c.Abort()的作用是把c.index设为最大值,让 Gin 跳过所有剩余中间件和最终 handler
缓存键要能区分不同请求参数和用户上下文
用固定 key(如 "user:list")缓存接口,会导致所有用户看到同一份数据;忽略 query 参数或 header,也会造成脏读。
- 推荐组合键:
"cache:" + c.Request.Method + ":" + c.Request.URL.Path + ":" + md5hash(c.Request.URL.RawQuery) - 需要鉴权的接口,务必加入用户标识,例如:
userID := c.GetString("user_id"),然后拼进 key - 避免用
c.Request.URL.String()直接当 key——它含 protocol/host,不适合服务端缓存
缓存失效策略直接影响 DB 压力峰值
如果所有缓存项在同一时间过期,大量并发请求会同时穿透到数据库,引发雪崩。
- 不要写死 TTL:
redis.Setex(ctx, key, 300, val)是高危操作 - 改用随机偏移:
ttl := 300 + rand.Intn(120)(5~7 分钟波动),分散失效点 - 对热点接口(如首页、排行榜),考虑“逻辑过期”:value 里存
{"data": ..., "expire_at": 1724120180},由应用层判断是否需后台刷新
最常被忽略的一点:缓存中间件无法绕过 Gin 路由匹配本身,但它能彻底跳过 handler 函数体里的任何代码——包括 ORM 查询、RPC 调用、文件读取。所以 DB 查询是否被跳过,只取决于你有没有在缓存命中的分支里调用 c.Abort(),而不是中间件注册顺序或命名是否带 “cache”。











