必须重写responsewriter的writeheader和write方法以正确缓存状态码与响应体,仅缓存200 json响应,key需含method+path+query+accept,用带过期控制的lru替代sync.map并确保createdat内存可见。

别直接用 fiber.Default() 配缓存中间件——它自带的 logger 会把未加密的请求头(比如 Authorization)全打到日志里,缓存逻辑还没跑,安全扫描就先告警了;而且默认中间件堆叠让 TTFB 多出 0.3–0.8ms,高并发下缓存收益可能被抵消。
缓存中间件必须重写 WriteHeader 和 Write
只监听 Write 方法会导致状态码丢失。比如 handler 先调 w.WriteHeader(500) 再 panic,缓存层没捕获状态码,就把空 body 当作 200 OK 存下来,后续所有请求都拿到错误响应。
- 必须实现自定义
ResponseWriter类型,同时重写WriteHeader(int)和Write([]byte) -
WriteHeader中记录statusCode字段,Write中累积body并加锁保护共享字段 - 只缓存
200且Content-Type: application/json的响应,跳过302、4xx、5xx - 非
GET或HEAD请求默认不缓存,避免 POST 结果被复用
缓存 key 必须包含 method + path + query + accept
只用 r.URL.Path 作 key 会命中错误:同一路径下,Accept: application/json 和 Accept: text/html 应返回不同内容,但若 key 不含 Accept 头,JSON 响应就可能错返给 HTML 请求者。
- 推荐 key 构建方式:
r.Method + ":" + r.URL.EscapedPath() + "?" + r.URL.RawQuery + "|" + r.Header.Get("Accept") -
EscapedPath()比Path更安全,能处理带特殊字符的路径(如/user/张三) - 忽略其他 header(如
User-Agent),除非业务明确依赖;否则缓存项爆炸式增长 - 若需忽略 query 参数(如分页参数
?page=1),应先归一化 URL,而非直接拼RawQuery
别用 sync.Map 存原始 []byte
用 sync.Map 直接塞 []byte 响应体看似简单,但无过期控制、无容量限制,服务跑几小时 RSS 就持续上涨,pprof 显示大量 sync.mapRead 占 heap。
- 必须封装结构体:
type cacheItem struct { body []byte; headers map[string][]string; createdAt time.Time } - 过期检查必须在每次
Load时惰性执行:if time.Since(item.createdAt) > ttl { return },不能只靠后台 goroutine 清理 - 缓存项稳定超过 300–500 个时,换
github.com/hashicorp/golang-lru/v2,它自带容量上限、OnEvicted回调和线程安全的 LRU/2Q/ARC 实现 -
sync.Map.Range不是快照语义,遍历时可能漏掉新写入项
最常被忽略的是缓存项的内存可见性与惰性过期——哪怕你用了 golang-lru,如果 cacheItem 里没把 createdAt 设为值类型或没加 atomic 保护,高并发下多个 goroutine 可能读到未初始化的时间戳,导致过期判断失效。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











