redis适合读多写少、变更不频繁的接口缓存,核心逻辑是请求先查redis命中则返回,未命中则查业务并回填;需注意缓存粒度、穿透/雪崩防护、序列化存储、key设计、ttl设置及连接复用。

Iris 框架本身不内置 HTTP 响应级缓存中间件,但结合 Redis 或内存缓存(如 sync.Map)可快速实现接口数据缓存。关键不是“有没有”,而是“怎么选缓存粒度+怎么避免穿透/雪崩/脏读”。
用 Redis 实现带过期的响应缓存
适合读多写少、数据变更不频繁的接口(如配置中心、商品详情页)。核心逻辑是:请求进来先查 Redis,命中则直接返回;未命中则调用业务逻辑,写入 Redis 后再返回。
常见错误是直接缓存 iris.Context 或未序列化结构体——Redis 只存字节流,必须用 json.Marshal 或 gob 编码。
- 缓存 key 建议包含路由路径 + 查询参数哈希(如
sha256("/api/user?id=123")),避免不同参数共用一个 key - 设置合理
ttl(如time.Minute * 10),别硬写0导致永久缓存 - 注意 Redis 连接池复用:全局初始化一次
redis.Client,不要每次请求都redis.NewClient() - 若用
iris-admin,它已封装了auth2/redis.go的连接管理,可直接复用其redisClient实例
用 sync.Map 实现进程内轻量缓存
适合低并发、无分布式需求、且数据更新极少的场景(如本地配置、白名单列表)。比 Redis 快,但无法跨实例共享,也不支持 TTL 自动清理。
直接用 sync.Map 存 map[string]any 会丢失类型信息,建议封装一层:
var cache = &sync.Map{} // key: string, value: struct{ data []byte; expiresAt time.Time }
<p>func getFromCache(key string) ([]byte, bool) {
if v, ok := cache.Load(key); ok {
item := v.(struct{ data []byte; expiresAt time.Time })
if time.Now().Before(item.expiresAt) {
return item.data, true
}
cache.Delete(key)
}
return nil, false
}
</p>
- 别在
sync.Map里存指针或未导出字段,json.Unmarshal会失败 - 手动检查
expiresAt是必须的,sync.Map不提供自动过期 - 高并发下仍需注意 GC 压力——频繁写入大 JSON 字节流会触发堆分配
绕过缓存的典型条件处理
不是所有请求都该走缓存。比如带 Cache-Control: no-cache 头、或含 _t=xxx 时间戳参数的请求,应跳过缓存逻辑。
- 检查
ctx.GetHeader("Cache-Control") == "no-cache"或ctx.URLParam("_t") != "" - 管理员后台接口默认不缓存,可在路由注册时加标记:
app.Get("/admin/stats", adminHandler).SetRegisterRule(iris.NoCache)(需自定义中间件识别该 rule) - POST/PUT/DELETE 请求默认不缓存,但若业务允许幂等写操作,也可缓存其响应(需显式控制)
缓存击穿与空值陷阱
当某个热点 key 过期瞬间大量请求涌入,同时查 DB 再回填缓存,会造成 DB 瞬时压力飙升——这就是击穿。更隐蔽的是“空值缓存”没设 ttl,导致查不到的数据被永久缓存。
- 对可能为空的结果,也写入 Redis,但用较短 ttl(如
time.Second * 30),防止反复穿透 - 用
SET key value EX 60 NX原子操作防止并发回源,iris的redis.Client支持SetNX方法 - 不要依赖
if !exists { load(); set() }这种非原子逻辑,竞态下多个 goroutine 会同时 load
缓存最难的不是存和取,而是失效时机和一致性边界。比如用户修改资料后,是删缓存还是设空值?删的话,删 key 还是模糊匹配前缀?这些没结合业务写死的策略,光靠框架层没法兜底。











