不能直接用 redis 当唯一缓存,因网络延迟和连接池争用会显著增加 rt,高并发下易成瓶颈;应采用 l1(本地缓存)+ l2(redis)分层架构,l1 拦截 90%+ 热 key 请求,redis 作兜底与一致性源。

为什么不能直接用 redis 当唯一缓存
因为网络延迟和 Redis 连接池争用会吃掉大量 RT,尤其在高并发读场景下,单靠 redis 容易成为瓶颈。本地缓存(如 sync.Map 或 bigcache)能扛住 90%+ 的热 key 请求,把 Redis 当作兜底和一致性源——这才是 L1/L2 分层的真实动因。
怎么选 L1 缓存:用 sync.Map 还是 bigcache
sync.Map 零依赖、开箱即用,但不支持过期、无容量控制,适合生命周期短、key 数量可控的场景(比如用户登录态 token 映射)。bigcache 支持 TTL 和内存限制,底层用分片 + 时间轮,压测下吞吐比 sync.Map 高 2–3 倍,但要注意它不释放内存给 GC,必须手动调 bigcache.NewBigCache 的 OnRemove 回调做清理。
- 热 key 密度高、QPS > 5k → 优先
bigcache - 只存 session ID →
sync.Map更轻量 - 别用
map + mutex,锁粒度太粗,实测比sync.Map慢 40%+
L1/L2 协同的关键:缓存穿透和双删怎么写
缓存穿透不是加个空值就完事——L1 里塞空值没意义,因为下次请求可能命中 L2 而绕过 L1;必须让 L1 和 L2 同时写空值,且 L1 的空值要带短 TTL(比如 60s),避免长期污染。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 查缓存顺序固定为:
l1.Get(key)→ 未命中 →l2.Get(key)→ 未命中 → 查 DB → 写l2.Set(key, val, ttl)→ 再写l1.Set(key, val, shortTTL) - 更新数据时先删 L1:
l1.Delete(key),再删 L2:l2.Del(key),最后写 DB;别反过来,否则中间窗口可能读到旧 L1 + 新 DB - Redis 删操作用
DEL,别用EXPIRE,后者有竞态风险
怎么验证 L1/L2 是否真在协同工作
最简单的办法:在 L1 的 Get 和 Set 方法里埋点,打日志或统计 counter;同时监控 Redis 的 KEYS 命中率(INFO stats 中的 keyspace_hits / (keyspace_hits + keyspace_misses)),如果这个值低于 70%,说明 L1 没拦住足够流量,或者 L1 的 TTL 设得太短。
- 别依赖
redis-cli monitor看命令流,太嘈杂,容易漏掉 L1 命中路径 - L1 的 hit rate 应该稳定在 85%+,否则检查 key 的分布是否倾斜、TTL 是否统一设成了 1s
- 注意 Go 的
time.Now().Unix()在容器里可能不准,L1 的 TTL 计算别直接用它做基准
多级缓存真正的复杂点不在代码结构,而在时间窗口里的一致性博弈——L1 的 TTL、L2 的 TTL、DB 更新耗时,三者只要差 100ms,就可能暴露脏数据。别迷信“自动刷新”,得按业务容忍度手工对齐这几个值。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










