一、需求场景 高并发查询场景:先查缓存,缓存未命中再查数据库/计算。
核心要求:
并发安全 :多goroutine同时读写
高性能 :读多写少优化
过期机制 :防止数据过时
二、最简实现:sync.Map 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package cacheimport "sync" type Cache struct { data sync.Map } func New () *Cache { return &Cache{} } func (c *Cache) Get(key string ) (any, bool ) { return c.data.Load(key) } func (c *Cache) Set(key string , value any) { c.data.Store(key, value) } func (c *Cache) Delete(key string ) { c.data.Delete(key) }
优点 :简单、无锁读取缺点 :无过期机制
三、进阶实现:带过期时间 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package cacheimport ( "sync" "time" ) type entry struct { value any expireAt int64 } type Cache struct { data sync.Map } func New () *Cache { return &Cache{} } func (c *Cache) Get(key string ) (any, bool ) { v, ok := c.data.Load(key) if !ok { return nil , false } e := v.(*entry) if time.Now().UnixNano() > e.expireAt { c.data.Delete(key) return nil , false } return e.value, true } func (c *Cache) Set(key string , value any, ttl time.Duration) { c.data.Store(key, &entry{ value: value, expireAt: time.Now().Add(ttl).UnixNano(), }) } func (c *Cache) Delete(key string ) { c.data.Delete(key) }
惰性删除 :读取时检查过期,减少后台开销。
四、高并发场景:分片锁优化 sync.Map在大量写入时性能下降,改用分片锁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 package cacheimport ( "sync" "time" ) const shardCount = 32 type entry struct { value any expireAt int64 } type shard struct { mu sync.RWMutex data map [string ]*entry } type Cache struct { shards [shardCount]*shard } func New () *Cache { c := &Cache{} for i := 0 ; i < shardCount; i++ { c.shards[i] = &shard{data: make (map [string ]*entry)} } return c } func (c *Cache) getShard(key string ) *shard { hash := fnv32(key) return c.shards[hash%shardCount] } func (c *Cache) Get(key string ) (any, bool ) { s := c.getShard(key) s.mu.RLock() defer s.mu.RUnlock() e, ok := s.data[key] if !ok { return nil , false } if time.Now().UnixNano() > e.expireAt { return nil , false } return e.value, true } func (c *Cache) Set(key string , value any, ttl time.Duration) { s := c.getShard(key) s.mu.Lock() defer s.mu.Unlock() s.data[key] = &entry{ value: value, expireAt: time.Now().Add(ttl).UnixNano(), } } func (c *Cache) Delete(key string ) { s := c.getShard(key) s.mu.Lock() defer s.mu.Unlock() delete (s.data, key) } func fnv32 (key string ) uint32 { hash := uint32 (2166136261 ) const prime32 = uint32 (16777619 ) for i := 0 ; i < len (key); i++ { hash *= prime32 hash ^= uint32 (key[i]) } return hash }
优化原理 :
32个分片,锁竞争概率降低32倍
读操作用RLock,允许并发读
写操作只锁定对应分片
五、使用示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package mainimport ( "fmt" "time" "yourpkg/cache" ) func main () { c := cache.New() c.Set("user:123" , map [string ]any{"name" : "张三" }, 10 *time.Second) var wg sync.WaitGroup for i := 0 ; i < 100 ; i++ { wg.Add(1 ) go func () { defer wg.Done() if v, ok := c.Get("user:123" ); ok { fmt.Println(v) } }() } wg.Wait() }
六、超时处理:缓存查询超时控制 实际场景中,缓存查询可能涉及外部调用(如Redis),需要超时控制:
6.1 带超时的Get 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import ( "context" "time" ) func (c *Cache) GetWithContext(ctx context.Context, key string ) (any, bool ) { type result struct { value any ok bool } ch := make (chan result, 1 ) go func () { v, ok := c.Get(key) ch <- result{v, ok} }() select { case r := <-ch: return r.value, r.ok case <-ctx.Done(): return nil , false } } ctx, cancel := context.WithTimeout(context.Background(), 100 *time.Millisecond) defer cancel()value, ok := cache.GetWithContext(ctx, "user:123" )
6.2 单飞模式:防止缓存穿透 高并发时,多个请求同时查询一个不存在的key,导致大量请求打到数据库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import "golang.org/x/sync/singleflight" type Cache struct { data sync.Map group singleflight.Group loader func (key string ) (any, error ) } func (c *Cache) GetWithLoad(key string ) (any, error ) { if v, ok := c.data.Load(key); ok { return v, nil } v, err, _ := c.group.Do(key, func () (any, error ) { if v, ok := c.data.Load(key); ok { return v, nil } data, err := c.loader(key) if err != nil { return nil , err } c.data.Store(key, data) return data, nil }) return v, err }
singleflight原理 :相同key的多个请求,只有第一个会真正执行,其他等待结果。
6.3 完整的单飞+超时实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 func (c *Cache) GetWithLoadAndTimeout(ctx context.Context, key string , timeout time.Duration) (any, error ) { if v, ok := c.data.Load(key); ok { return v, nil } resultCh := make (chan singleflight.Result, 1 ) go func () { v, err, _ := c.group.Do(key, func () (any, error ) { if v, ok := c.data.Load(key); ok { return v, nil } data, err := c.loader(key) if err != nil { return nil , err } c.data.Store(key, data) return data, nil }) resultCh <- singleflight.Result{Val: v, Err: err} }() select { case r := <-resultCh: return r.Val, r.Err case <-time.After(timeout): return nil , fmt.Errorf("cache load timeout" ) case <-ctx.Done(): return nil , ctx.Err() } }
七、读写锁 vs sync.Map 深度对比 7.1 两种方案实现对比 方案A:sync.RWMutex + map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type CacheRWMutex struct { mu sync.RWMutex data map [string ]any } func (c *CacheRWMutex) Get(key string ) (any, bool ) { c.mu.RLock() defer c.mu.RUnlock() v, ok := c.data[key] return v, ok } func (c *CacheRWMutex) Set(key string , value any) { c.mu.Lock() defer c.mu.Unlock() c.data[key] = value }
方案B:sync.Map
1 2 3 4 5 6 7 8 9 10 11 type CacheSyncMap struct { data sync.Map } func (c *CacheSyncMap) Get(key string ) (any, bool ) { return c.data.Load(key) } func (c *CacheSyncMap) Set(key string , value any) { c.data.Store(key, value) }
7.2 核心区别
特性
RWMutex + map
sync.Map
读操作
RLock,有锁
无锁(atomic)
写操作
Lock,互斥
CAS + 分离读写map
内存模型
单一map
读写分离(read + dirty)
适用场景
读写均衡
读多写少
删除策略
直接删除
延迟删除(标记删除)
7.3 sync.Map的读写分离原理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Map struct { mu sync.Mutex read atomic.Pointer[readOnly] dirty map [any]*entry } type readOnly struct { m map [any]*entry amended bool } type entry struct { p unsafe.Pointer }
读取流程 :1 2 3 1. 先查read(无锁) 2. 找到且未被删除 → 返回 3. 找不到且amended=true → 加锁查dirty → 更新read → 返回
写入流程 :1 2 3 1. read中找到且未被删除 → CAS更新(无锁) 2. 否则加锁,写入dirty 3. 定期将dirty提升为read
7.4 性能测试对比 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func BenchmarkRWMutexRead (b *testing.B) { c := &CacheRWMutex{data: make (map [string ]any)} c.Set("key" , "value" ) b.RunParallel(func (pb *testing.PB) { for pb.Next() { c.Get("key" ) } }) } func BenchmarkSyncMapRead (b *testing.B) { c := &CacheSyncMap{} c.Set("key" , "value" ) b.RunParallel(func (pb *testing.PB) { for pb.Next() { c.Get("key" ) } }) }
典型结果 (读多写少场景):
1 2 BenchmarkRWMutexRead-8 10000000 120 ns/op BenchmarkSyncMapRead-8 50000000 25 ns/op // 快5倍
典型结果 (读写均衡场景):
1 2 BenchmarkRWMutexReadWrite-8 5000000 280 ns/op BenchmarkSyncMapReadWrite-8 3000000 450 ns/op // 更慢
7.5 选择建议 1 2 3 4 5 6 7 8 9 ┌─────────────────────────────────────────────────────────┐ │ 如何选择? │ ├─────────────────────────────────────────────────────────┤ │ 读>>写(如配置缓存) → sync.Map │ │ 读写均衡 → RWMutex + map 或 分片锁 │ │ 需要遍历所有key → RWMutex + map │ │ key集合稳定不变 → sync.Map 更优 │ │ key频繁增删 → 分片锁 RWMutex │ └─────────────────────────────────────────────────────────┘
7.6 分片锁:结合两者优势 对于高并发读写均衡场景,分片锁是最佳选择:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type shardedCache struct { shards [32 ]struct { mu sync.RWMutex data map [string ]any } } func (c *shardedCache) Get(key string ) (any, bool ) { s := c.getShard(key) s.mu.RLock() defer s.mu.RUnlock() v, ok := s.data[key] return v, ok } func (c *shardedCache) Set(key string , value any) { s := c.getShard(key) s.mu.Lock() defer s.mu.Unlock() s.data[key] = value }
优势 :
32个分片,锁竞争概率降低32倍
每个分片内RWMutex支持并发读
比sync.Map在写多场景更稳定
八、性能对比总结
实现方式
读性能
写性能
适用场景
sync.Map
⭐⭐⭐⭐⭐
⭐⭐
读多写少
RWMutex + map
⭐⭐⭐
⭐⭐⭐
读写均衡、需遍历
分片锁
⭐⭐⭐⭐
⭐⭐⭐⭐
高并发读写
单锁
⭐⭐
⭐⭐
简单场景
九、生产级考虑 实际生产环境还需考虑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type Cache struct { maxSize int current int } func (c *Cache) StartCleanup(interval time.Duration) { go func () { ticker := time.NewTicker(interval) for range ticker.C { c.cleanup() } }() } type Stats struct { Hits int64 Misses int64 }
十、总结
方案
复杂度
推荐场景
sync.Map
低
读多写少、无过期需求
sync.Map + 过期
中
读多写少、有过期需求
RWMutex + map
中
读写均衡、需要遍历所有key
分片锁
高
高并发读写、性能敏感
分片锁 + singleflight
高
防穿透、防雪崩
核心选择原则
读写比例 :读>>写选sync.Map,读写均衡选分片锁
是否需要遍历 :sync.Map不支持高效遍历,需要遍历用RWMutex
防穿透需求 :加singleflight
超时控制 :用context + channel
对于大多数场景,sync.Map + 惰性过期 已经足够;高并发场景推荐 分片锁 + singleflight 组合。