缓存中间件必须同时重写writeheader和write方法,以准确捕获状态码和响应体;仅监听write会导致500等错误响应被误存为200。

缓存中间件必须重写 WriteHeader 和 Write
直接包装 http.HandlerFunc 却只监听 Write 调用,会导致状态码丢失——比如 handler 先调 w.WriteHeader(500) 再写 body,缓存逻辑若没捕获该调用,就会把 500 响应当成 200 存下来并返回给客户端。
- 必须用自定义
ResponseWriter类型,同时实现WriteHeader(int)和Write([]byte)方法 - 在
WriteHeader中记录状态码,在Write中累积 body,两者都需加锁保护共享字段 - 只缓存
200 OK且Content-Type为application/json的响应;跳过302、4xx、5xx - 非
GET或HEAD请求默认不缓存,避免意外缓存POST结果
缓存 key 必须包含 method + path + query + accept
仅用 req.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
bigcache 初始化时 Shards/LifeWindow/MaxEntrySize 三坑
bigcache 高性能的前提是参数配对,错一个就静默失效或 panic,文档却没强调组合约束。
-
Shards必须是 2 的幂(如256),否则启动报错"shards number must be power of two" -
LifeWindow设为0表示永不过期,但 LRU 淘汰后Get返回false,不会自动回源——得靠外部逻辑触发重载 -
MaxEntrySize要大于预期响应体长度(建议预留 20%),超长写入会静默失败并返回ErrEntryTooLarge,不抛 panic
别直接往 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 清理 - 超过几百个缓存项时,换
github.com/hashicorp/golang-lru,它自带 TTL 和容量控制 -
sync.Map.Range不是快照语义,遍历时可能漏掉新写入项,不适合做批量清理
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











