sync.Map 仅适用于读多写少场景,高并发写会导致性能断崖下跌;它无过期、无淘汰、不支持遍历一致性,大容量下易引发 GC 压力与内存碎片。

用 sync.Map 做本地高频读缓存,但别当它能扛写压力
高并发读场景下,sync.Map 是 Go 标准库最直接的选择,但它只适合「读多写少」——比如配置热更新、用户权限白名单这类变更不频繁的数据。一旦写操作变多(比如每秒几百次 Store),性能会断崖式下跌,因为内部会退化成加锁的 map。
- 读操作无锁,比普通
map+sync.RWMutex快 2–3 倍(实测 100 万次读) -
LoadOrStore是原子操作,适合初始化场景,但要注意:如果 key 已存在,不会触发回调或校验逻辑 - 不能遍历 ——
Range是快照式遍历,期间新增/删除条目可能不被看到,也**不会反映最新状态** - 别把它当通用缓存容器:没过期、没大小限制、不淘汰,内存只增不减
加 bigcache 或 freecache 解决本地缓存的 GC 和内存碎片问题
当缓存数据量大(比如几十万条字符串)、生命周期较短时,sync.Map 的指针引用会让 GC 压力陡增,且小对象堆积导致内存碎片。这时候得换更“轻量”的本地缓存库。
-
bigcache把 key/value 拆开存储:key 存哈希表(map[uint64]uint32),value 存大块字节切片,规避 GC 扫描;但 value 必须是[]byte,需要自己序列化 -
freecache支持任意interface{},内部用 ring buffer 管理内存,有 LRU 淘汰和软过期(expireSeconds),但写入吞吐略低于bigcache - 两者都不支持自定义淘汰策略(比如 LFU),也不提供监听事件,想做缓存穿透防护得自己 wrap
Get方法
分布式缓存选 redis 时,go-redis 的连接池和超时必须显式调优
本地缓存只能缓解一部分压力,真正扛不住的热点 key 还得靠 redis。但默认的 go-redis 配置在高并发下极易出问题:连接打满、请求卡死、超时混乱。
-
PoolSize别设成 CPU 核数 —— 实际应按 QPS × 平均响应时间估算,比如 5000 QPS × 5ms ≈ 25,再留 20% 余量,设为 30 更稳 -
MinIdleConns至少设为PoolSize / 2,避免突发流量时反复建连 -
ReadTimeout/WriteTimeout必须设(建议 ≤ 100ms),否则一个慢查询会拖垮整个连接池;Context超时也要同步设,防止 goroutine 泄漏 - 用
GETEX或SET带EX参数,别依赖客户端过期逻辑 —— 分布式环境下时钟不同步会导致过期不准
缓存穿透/击穿/雪崩不是理论问题,得在 Get 调用链里埋具体防御点
这三类问题往往在压测或上线后突然爆发,根源不在缓存本身,而在业务调用方式。防御必须落在代码里,而不是靠事后加监控。
立即学习“go语言免费学习笔记(深入)”;
- 穿透:对空结果(
nil或空结构体)也写入本地缓存,带短过期(如 2min),并用redis.SetNX加一层布隆过滤器(bloomfilter库)预检 - 击穿:热点 key 过期瞬间大量请求打到 DB,用
redis.GetSet或SET key val EX 60 NX实现互斥重建,失败方 sleep 后重试 - 雪崩:批量 key 过期时间别写死,加随机偏移(
rand.Int63n(60)秒),同时本地缓存设独立过期时间,与 redis 错开
最常被忽略的是:本地缓存和分布式缓存的过期逻辑不一致,比如本地设了 10 分钟,redis 设了 5 分钟,中间 5 分钟会出现「本地有但已脏」的状态 —— 这种隐性不一致比崩溃更难排查。


















