闽公网安备 35020302035485号
| 缓存模式 | 你做了什么 | 出现的问题 |
|---|---|---|
| TTL-only | 设置 user:42 缓存 5 分钟 | 写入后读者可能看到最多 5 分钟的旧数据,缩短 TTL 又会增加 DB 压力 |
| Delete then Set | 先 DEL user:42 再 SET user:42 | 两个操作之间存在 race,可能导致旧数据重新写入缓存 |
| Write-through | 先写 DB 再更新缓存 | 并发读取时仍可能读到旧数据,除非完美协调缓存失效 |
| Value with version | 值中带版本号 {version, payload} | 读者仍需获取当前版本,协调版本仍是难题 |
.需要 dogpile 预防机制(single-flight),CasCache 不内置,需自行添加
五.快速上手 CasCache
1️⃣ 初始化缓存
import (
"context"
"time"
"github.com/unkn0wn-root/cascache"
"github.com/unkn0wn-root/cascache/codec"
rp "github.com/unkn0wn-root/cascache/provider/ristretto"
)
type User struct{ ID, Name string }
func newUserCache() (cascache.CAS[User], error) {
rist, err := rp.New(rp.Config{
NumCounters: 1_000_000,
MaxCost: 64 << 20, // 64 MiB
BufferItems: 64,
Metrics: false,
})
if err != nil { return nil, err }
return cascache.New[User](cascache.Options[User]{
Namespace: "user",
Provider: rist,
Codec: codec.JSON[User]{},
DefaultTTL: 5 * time.Minute,
BulkTTL: 5 * time.Minute,
// GenStore: nil -> 本地 generation(单进程)
})
}
2️⃣ 安全读取单个 keytype UserRepo struct {
Cache cascache.CAS[User]
// db handle...
}
func (r *UserRepo) GetByID(ctx context.Context, id string) (User, error) {
if u, ok, _ := r.Cache.Get(ctx, id); ok {
return u, nil
}
// CAS snapshot BEFORE reading DB
obs := r.Cache.SnapshotGen(id)
u, err := r.dbSelectUser(ctx, id) // your DB load
if err != nil { return User{}, err }
// Conditionally cache only if generation didn't move
_ = r.Cache.SetWithGen(ctx, id, u, obs, 0)
return u, nil
}
3️⃣ 写入时失效 keyfunc (r *UserRepo) UpdateName(ctx context.Context, id, name string) error {
if err := r.dbUpdateName(ctx, id, name); err != nil { return err }
_ = r.Cache.Invalidate(ctx, id) // bump gen + clear single
return nil
}
4️⃣ 批量读写(可选)func (r *UserRepo) GetMany(ctx context.Context, ids []string) (map[string]User, error) {
values, missing, _ := r.Cache.GetBulk(ctx, ids)
if len(missing) == 0 {
return values, nil
}
// Snapshot *before* DB read
obs := r.Cache.SnapshotGens(missing)
// Load missing from DB in one shot
loaded, err := r.dbSelectUsers(ctx, missing)
if err != nil { return nil, err }
// 堆代码 duidaima.com
// Index for SetBulkWithGens
items := make(map[string]User, len(loaded))
for _, u := range loaded { items[u.ID] = u }
// Conditionally write bulk (or it will seed singles if any gen moved)
_ = r.Cache.SetBulkWithGens(ctx, items, obs, 0)
// Merge and return
for k, v := range items { values[k] = v }
return values, nil
}
六.多副本部署:使用 RedisGenStoreimport (
rgs "github.com/unkn0wn-root/cascache/genstore/redis"
)
func newRedisGenStore(client *redis.Client) cascache.GenStore {
return rgs.New(client, rgs.Options{
Namespace: "genstore",
})
}
初始化缓存时传入:return cascache.New[User](cascache.Options[User]{
// ...
GenStore: newRedisGenStore(redisClient),
})
总结
CasCache 是一个专为解决缓存 stale 问题而设计的 Go 缓存库。它通过 generation-guarded writes 和 read-time validation,确保一旦你失效了一个 key,就不会再读到旧值。结合插件化设计、批量缓存校验和多副本安全机制,CasCache 成为了现代高并发、多副本服务架构中缓存层的不二之选。如果你也在为缓存一致性头疼,不妨试试 CasCache,给你的缓存系统来一次“无 stale”的升级体验!