gin 默认不带 lru 缓存中间件,因其定位是轻量、高性能路由框架,不内置任何缓存逻辑;官方未提供 gin.lrucache,需自行组合 hashicorp/golang-lru 库封装为中间件,并谨慎处理键生成、响应捕获及非幂等/敏感请求的缓存规避。

为什么 Gin 默认不带 LRU 缓存中间件
Gin 本身定位是轻量、高性能的 HTTP 路由框架,gin.Engine 不内置任何缓存逻辑 —— 包括 LRU、TTL、内存/Redis 分布式缓存。它的 Use() 和 HandlersChain 机制只负责顺序调用函数,不管理数据存储。所以你搜 gin.LRUCache 或 gin.CacheMiddleware 是找不到的,官方也没提供。想加本地 LRU 缓存,必须自己组合:选一个线程安全的 LRU 库 + 封装成 gin.HandlerFunc + 正确拦截读写响应体。
用 github.com/hashicorp/golang-lru 替代自实现
别手写 LRU(容易出并发 bug 或 O(n) 查找),直接用成熟库:github.com/hashicorp/golang-lru。它提供 lru.New() 和线程安全的 Get()/Add(),底层是双向链表 + map,O(1) 时间复杂度。注意两点:
-
lru.New(1000)的容量是条目数,不是字节;缓存键建议用method + path + query string拼接(如"GET:/api/user?id=123") - 不要缓存带
Set-Cookie、Authorization头的请求,这类响应通常不可复用 - 缓存值类型必须是可序列化的,比如
[]byte(原始响应体)或自定义结构体(含状态码、header、body)
中间件里怎么安全读写 response.Body
Gin 的 c.Writer 是个接口,直接读 c.Writer.Body() 会 panic —— 因为默认实现(responseWriter)不支持回溯。正确做法是用 gin.ResponseWriter 的包装器,或者更简单:在 c.Next() 前替换 c.Writer 为自定义的 recordingWriter,把响应内容先捕获到内存:
type recordingWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (w *recordingWriter) Write(b []byte) (int, error) {
w.body.Write(b)
return w.ResponseWriter.Write(b)
}
func (w *recordingWriter) WriteString(s string) (int, error) {
w.body.WriteString(s)
return w.ResponseWriter.WriteString(s)
}
然后在中间件中:
c.Writer = &recordingWriter{ResponseWriter: c.Writer, body: &bytes.Buffer{}}- 调用
c.Next() - 若命中缓存,用
c.Data(status, contentType, cachedBody)直接写出;否则等业务 handler 写完后,从w.body.Bytes()提取并存入 LRU
哪些路由不该加 LRU 缓存中间件
加了反而出问题:
- POST/PUT/DELETE 等非幂等方法 ——
c.Request.Method != "GET"时应跳过缓存逻辑 - 路径含动态参数但未标准化(如
/user/123和/user/123/被视为不同 key)—— 建议提前 normalize path - 响应头含
Cache-Control: no-store或Vary: *—— 应检查c.Writer.Header().Get("Cache-Control")后决定是否跳过 - 返回大文件(如 PDF、图片流)—— 缓存整个
[]byte会吃光内存,此时应改用文件系统或 CDN
LRU 缓存中间件真正的难点不在算法,而在「什么时候不缓存」—— 错误地缓存了带用户态的响应(比如登录态、个性化推荐),会导致 A 用户看到 B 用户的数据。务必在 key 构造和缓存写入前做细粒度判断。











