Go 用 Ristretto 构建本地缓存:TTL、淘汰与并发安全

test12026-10-2720 次阅读

为什么选本地缓存

Redis 适合分布式共享,但每一次读取都要走网络。对读多写少、允许短暂不一致的数据(配置、热点字典、限流计数),进程内缓存能把延迟从毫秒压到纳秒级。Ristretto 是 Dgraph 出品的高性能 Go 缓存,采用近似 LRU 淘汰,并发安全且命中率高。

初始化缓存

package cache

import (
	"time"

	"github.com/dgraph-io/ristretto/v2"
)

func NewCache() (*ristretto.Cache[string, []byte], error) {
	cfg := &ristretto.Config[string, []byte]{
		NumCounters: 1e7,     // 键计数器数量,约为预期键数 10 倍
		MaxCost:     1 << 30, // 最大成本,约 1GB
		BufferItems: 64,
	}
	cache, err := ristretto.NewCache(cfg)
	if err != nil {
		return nil, err
	}
	return cache, nil
}

写入与读取

// 写入:cost 用字节数估算,TTL 控制过期
cache.SetWithTTL("user:1", avatar, int64(len(avatar)), 5*time.Minute)

// 读取:返回的 ok 表示是否真的命中
if val, ok := cache.Get("user:1"); ok {
	serve(val)
}

与 singleflight 配合防击穿

var g singleflight.Group

func GetUser(id string) ([]byte, error) {
	if v, ok := cache.Get(id); ok {
		return v, nil
	}
	v, err, _ := g.Do(id, func() (any, error) {
		data, e := loadFromDB(id)
		if e == nil {
			cache.SetWithTTL(id, data, int64(len(data)), 5*time.Minute)
		}
		return data, e
	})
	return v.([]byte), err
}

工程注意点

  • NumCounters 设小了命中率会掉,设大了占内存,按预期键数 10 倍给。
  • 写进去不代表立刻可读,Ristretto 是异步写入,写完稍等一个周期或接受极短窗口的未命中。
  • 缓存值的 cost 要如实估算,否则 MaxCost 会被撑爆或提前淘汰。
  • 单进程缓存不适合做跨实例共享状态,分布式一致性还得靠 Redis。
T

test1

文章作者

为什么选本地缓存 Redis 适合分布式共享,但每一次读取都要走网络。对读多写少、允许短暂不一致的数据(配置、热点字典...

分类
技术
发布时间
2026-10-27
字数
约 1230 字
阅读
20 次