本地内存 + Redis 双层缓存:Hash 操作、过期清理、读穿透,可选接入链路追踪。
go get github.com/hecc-blot/cacheimport cacheContract "github.com/hecc-blot/cache/contract"
type ICacheFactory interface {
Local() ILocalCache
Redis() IRedisCache
Orchestrator() IOrchestrator
}
type IBaseCache interface {
Set(ctx context.Context, key string, val interface{}, expire time.Duration) error
Get(ctx context.Context, key string) (interface{}, error)
Del(ctx context.Context, key string) error
Exists(ctx context.Context, key string) (bool, error)
}
type LoadFunc func(ctx context.Context) (interface{}, error)
type IOrchestrator interface {
GetOrLoad(ctx context.Context, key string, load LoadFunc) (interface{}, error)
GetOrLoadWithTTL(ctx context.Context, key string, load LoadFunc, ttl time.Duration) (interface{}, error)
}
type ILocalCache interface {
IBaseCache
}
type IRedisCache interface {
IBaseCache
HSet(ctx context.Context, key string, values ...interface{}) error
HGet(ctx context.Context, key, field string) (string, error)
HDel(ctx context.Context, key string, fields ...string) error
Close() error
}import (
cache "github.com/hecc-blot/cache/service"
)
// 传入 traceSvc 以开启缓存操作的链路追踪,不需要可传 nil
cacheFactory := cache.NewCacheFactory(&config.Cache, traceSvc)
container.Set(new(cacheContract.ICacheFactory), cacheFactory)基于 sync.RWMutex 的内存缓存,定时清理过期条目。
// Set — 写入缓存(expire 为 0 表示永不过期)
err := cacheFactory.Local().Set(ctx, "user:1", userData, 10*time.Minute)
// Get — 读取缓存(key 不存在或已过期返回 nil, nil)
result, err := cacheFactory.Local().Get(ctx, "user:1")
// Del — 删除缓存
err = cacheFactory.Local().Del(ctx, "user:1")
// Exists — 判断 key 是否存在
ok, err := cacheFactory.Local().Exists(ctx, "user:1")配置 clear_interval 后,框架启动独立 goroutine 定期清理过期条目(读锁收集 → 写锁二次确认删除,不阻塞正常读写):
cache:
local:
enable: true
clear_interval: 3600 # 每隔 3600 秒清理一次,≤0 不启动基于 go-redis v9,支持 String 和 Hash 操作。
// 基础操作
err := cacheFactory.Redis().Set(ctx, "key", "value", time.Hour)
result, err := cacheFactory.Redis().Get(ctx, "key")
err = cacheFactory.Redis().Del(ctx, "key")
ok, err := cacheFactory.Redis().Exists(ctx, "key")
// Hash 操作
err = cacheFactory.Redis().HSet(ctx, "user:profile", "name", "john", "age", "30")
name, err := cacheFactory.Redis().HGet(ctx, "user:profile", "name")
err = cacheFactory.Redis().HDel(ctx, "user:profile", "name", "age")
// 关闭连接(框架不自动关闭)
cacheFactory.Redis().Close()基础 Get/Set 只提供原语,缓存穿透/击穿/回填需要业务手动编排。编排层 IOrchestrator 把「查缓存 → 查库 → 回填」这条链路做成一等公民:业务只传「取数闭包」,框架统一处理缓存未命中、并发合并、回填与空值防穿透。
type GetApi struct {
CacheFactory cacheContract.ICacheFactory `inject:""`
DbFactory dbContract.IDbFactory `inject:""`
}
func (a GetApi) Call(ctx *gin.Context) (interface{}, iCoreError.IError) {
// 一行:缓存命中直接返回;未命中调用闭包取数并回填缓存
val, err := a.CacheFactory.Orchestrator().GetOrLoad(ctx, "account:1",
func(ctx context.Context) (interface{}, error) {
db := a.DbFactory.Build(ctx)
data := AccountModel{}
if err := db.Where("id = ?", 1).Take(&data); err != nil {
return nil, err
}
return data, nil
})
if err != nil {
return nil, errorSvc.NewError(response.Fail, err)
}
return val, nil
}内置能力:
- singleflight 防击穿:同一 key 的并发未命中只触发一次取数,其余请求等待共享结果,避免瞬时打爆数据源。
- cache-aside 回填:未命中取数成功后自动写回缓存(
GetOrLoad默认 5 分钟;GetOrLoadWithTTL可显式指定 TTL,0 表示永不过期)。 - 空值防穿透:取数闭包返回
(nil, nil)(数据不存在)时,框架缓存空值哨兵(短 TTL 1 分钟),避免不存在的 key 反复穿透到数据源。
编排层默认基于本地缓存(Go 值原生往返,适合结构化对象)。Redis 后端
Get返回字符串,仅适合字符串值场景;如需基于 Redis 编排,可service.NewOrchestrator(cacheFactory.Redis(), traceSvc)显式构造。
传入 traceSvc 后,每次缓存操作自动创建 Span:
- 本地缓存:记录 SET / GET / DEL / EXISTS
- Redis 缓存:记录 SET / GET / DEL / EXISTS / HSET / HGET / HDEL
Span 属性包含 cache.type、cache.operation、cache.key(Redis 为 db.system: redis)。
cache:
local:
enable: true
clear_interval: 3600 # 过期缓存清理间隔(秒)
redis:
addr: "127.0.0.1:6379" # Redis 地址
password: "" # Redis 密码
db: 0 # Redis DB 编号
pool_size: 100 # 连接池大小| 模块 | 说明 |
|---|---|
| db | 缓存读穿透的数据源 |
| trace | 缓存操作的 Trace Span |
| framework | IOC 注入与统一错误 |