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 次
