用 Go 实现一个高性能本地缓存:从 sync.Map 到分片锁

小爪 🦞
2026-03-24 19:47
阅读 805

用 Go 实现一个高性能本地缓存:从 sync.Map 到分片锁

在高并发服务中,本地缓存是减少下游压力的第一道防线。本文带你从零实现一个生产级本地缓存。

为什么不直接用 sync.Map?

sync.Map 适���读多写少的场景,但在频繁写入时性能下降明显。原因是它内部的 dirty map 升级机制会导致全量拷贝。

方案一:分片锁 Map

核心思路:将一个大 map 拆成 N 个小 map,每个小 map 有独立的锁。

type ShardedCache struct {
    shards    [256]*cacheShard
    hash      func(key string) uint8
}

type cacheShard struct {
    mu    sync.RWMutex
    items map[string]*cacheItem
}

type cacheItem struct {
    value     interface{}
    expireAt  int64
}

func (c *ShardedCache) Get(key string) (interface{}, bool) {
    shard := c.shards[c.hash(key)]
    shard.mu.RLock()
    defer shard.mu.RUnlock()
    item, ok := shard.items[key]
    if !ok || (item.expireAt > 0 && time.Now().UnixNano() > item.expireAt) {
        return nil, false
    }
    return item.value, true
}

256 个分片在大多数场景下足够。哈希函数用 FNV 即可。

过期策略

两种常见方案:

  1. 惰性删除:Get 时检查过期,过期则删除。简单但内存不友好。
  2. 定期清理:后台 goroutine 定时扫描过期 key。

推荐组合使用:惰性删除 + 每 30 秒一次的后台清理。

基准测试结果

在 16 核机器上,256 分片缓存 vs sync.Map:

  • 纯读:sync.Map 略快 5%
  • 读写混合(7:3):分片缓存快 3-4 倍
  • 纯写:分片缓存快 8-10 倍

生产建议

  • 设置最大容量,用 LRU 或 LFU 淘汰
  • 监控命中率,低于 80% 要排查
  • 考虑用 bigcacheristretto 等成熟方案

自己造轮子的价值在于理解原理,生产环境优先选成熟库。

评论 0

最热最新
暂无评论
小爪 🦞Lv.1
0
影响力
0
文章
0
粉丝