必须重写Write和WriteHeader以捕获状态码和响应体,避免缓存500等错误响应或漏状态码;key需含method、path、query、accept等字段,禁用cookie/authorization;内存缓存须自带过期检查,Redis缓存需防穿透雪崩。

Go 没有内置响应缓存中间件,但用 http.Handler 包裹业务逻辑就能实现,关键不是“能不能”,而是“怎么避免缓存 500 或漏状态码”。
为什么必须重写 Write 和 WriteHeader?
常见错误是只拦截 Write,结果 handler 先调 WriteHeader(500) 再 panic 或写空 body,缓存里就存了 500 状态却没 body,下游拿到半截响应。
-
WriteHeader必须捕获状态码,缓存逻辑要基于它判断是否可存(比如只存 200/304) -
Write负责收集响应 body 和 header,但不能等它才决定是否缓存——状态码可能早已发出 - 建议把状态码、header、body 都暂存在自定义
responseWriter结构体里,WriteHeader和Write各负责一部分,最后统一落库
缓存 key 该包含哪些字段?
key 不够全,就会把不同 Accept 或参数的响应混在一起;太全,又导致缓存碎片化。实际用时得看接口是否支持内容协商或用户态差异。
- 必含:
r.Method(非GET/HEAD默认不缓存)、r.URL.Path、r.URL.RawQuery - 按需加:
r.Header.Get("Accept")(如 API 返回 JSON/XML)、r.Header.Get("User-Agent")(极少数需 UA 差异化时) - 不要加 Cookie 或 Authorization —— 这类请求本就不该缓存,否则会泄露用户数据
用 sync.Map 做内存缓存要注意什么?
sync.Map 不是“带过期的 map”,直接塞 []byte 会导致内存只增不减,且并发读可能拿到过期项。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- 必须封装结构体,例如:
type cacheItem struct { data []byte; createdAt time.Time; maxAge time.Duration } - 每次
Load时检查是否过期,过期就Delete并返回未命中 —— 不能依赖后台 goroutine 定时清理,因为Range不是快照,遍历时可能漏删 - 缓存项超过 200 个,建议换
github.com/hashicorp/golang-lru,它自带容量限制和 LRU 驱逐
对接 Redis 时如何防穿透和雪崩?
单纯 “查不到 → 回源 → 写入” 在高并发下会瞬间打穿 DB,尤其 Go 的 goroutine 调度快,几十个请求几乎同时触发回源。
- 对空结果也缓存,值设为
"null"字符串,TTL 设短(如60s),避免反复刷不存在的 ID - key 过期时间加随机偏移:
baseTTL + rand.Intn(300),防止大量 key 同一时刻失效 - 回源时加单 key 互斥锁(如用
redis.SetNX实现),确保同一 key 只有一个请求回源,其余等待或直接返回旧缓存
真正难的不是写缓存逻辑,而是判断“这个接口到底该不该缓存”——比如带用户权限校验的 GET 接口,看似只读,但响应内容因登录态而异,缓存前必须确认是否已剥离用户上下文。

















