Documentation
¶
Overview ¶
Package cache 提供统一的缓存操作抽象层。 支持内存缓存和分布式缓存,提供类型安全的操作。
Package cache 提供统一的缓存能力接口,支持内存缓存、Redis 缓存和类型安全包装器。
接口按能力拆分,调用方可以按需断言:
- Store:基础 Get、Set、Delete、Exists。
- BatchStore:GetMany、SetMany、DeleteMany、ExistsMany。
- Expirer:TTL、Expire、Persist。
- ConditionalStore:SetNX、SetXX、Swap。
- Counter:整数和浮点原子计数。
- Scanner:游标式 key 扫描。
- Locker:锁能力。
- Closer:资源释放。
TTL 语义 ¶
NoExpiration 表示永不过期,KeepTTL 表示在支持的条件更新中保留原 TTL:
c.Set(ctx, "key", []byte("value"), cache.NoExpiration)
if expirer, ok := c.(cache.Expirer); ok {
_ = expirer.Expire(ctx, "key", time.Minute)
_ = expirer.Persist(ctx, "key")
}
TTL 在 key 不存在时返回 ErrNotFound;key 存在但永不过期时返回 NoExpiration, nil。
快速开始 ¶
import (
"context"
"time"
"github.com/f2xme/gox/cache"
"github.com/f2xme/gox/cache/adapter/memory"
)
func main() {
c, _ := memory.New()
defer c.(cache.Closer).Close()
ctx := context.Background()
_ = c.Set(ctx, "user:1", []byte("Alice"), 5*time.Minute)
value, err := c.Get(ctx, "user:1")
if err == cache.ErrNotFound {
// 键不存在
}
_ = value
_ = c.Delete(ctx, "user:1")
}
批量操作 ¶
if batch, ok := c.(cache.BatchStore); ok {
_ = batch.SetMany(ctx, map[string][]byte{
"user:1": []byte("Alice"),
"user:2": []byte("Bob"),
}, time.Minute)
users, _ := batch.GetMany(ctx, []string{"user:1", "user:2"})
exists, _ := batch.ExistsMany(ctx, []string{"user:1", "user:2"})
_ = users
_ = exists
}
条件写入 ¶
if conditional, ok := c.(cache.ConditionalStore); ok {
ok, _ := conditional.SetNX(ctx, "job:1", []byte("queued"), time.Minute)
_ = ok
old, _ := conditional.Swap(ctx, "job:1", []byte("done"), cache.KeepTTL)
_ = old
}
类型安全包装器 ¶
Typed 自动处理序列化、反序列化和 cache-aside 加载:
type User struct {
ID int
Name string
}
typed := cache.NewTyped[User](c)
_ = typed.Set(ctx, "user:1", User{ID: 1, Name: "Alice"}, time.Minute)
user, err := typed.GetOrLoad(ctx, "user:1", time.Minute, func(ctx context.Context) (User, error) {
return loadUser(ctx, 1)
})
_ = user
_ = err
默认序列化器是 JSON。可以用 WithSerializer 配置 Gob 或自定义序列化器。GetOrLoad 默认会返回缓存写入错误;如果应用更重视可用性,可以使用 WithIgnoreSetErrors。
适配器 ¶
memory 适合单进程本地缓存,支持 LRU/LFU 淘汰、过期清理、扫描、计数和进程内锁。
redis 适合分布式缓存,支持批量操作、条件写入、扫描、计数和基于 Redis 的锁。
错误 ¶
包定义了小型错误集合:
- ErrNotFound:键不存在。
- ErrLocked:锁已被占用。
- ErrInvalidTTL:TTL 参数不适用于当前操作。
旧版兼容接口和方法已移除,新代码应使用 Store、BatchStore、Expirer、ConditionalStore、Many、Swap 和 GetOrLoad。
Example (Batch) ¶
ExampleCache_batch 演示批量操作
package main
import (
"context"
"fmt"
"time"
"github.com/f2xme/gox/cache"
"github.com/f2xme/gox/cache/adapter/memory"
)
func main() {
c, _ := memory.New()
defer c.(cache.Closer).Close()
// 类型断言为 BatchStore
mc, ok := c.(cache.BatchStore)
if !ok {
fmt.Println("Cache does not support batch operations")
return
}
ctx := context.Background()
// 批量设置
items := map[string][]byte{
"key1": []byte("value1"),
"key2": []byte("value2"),
"key3": []byte("value3"),
}
err := mc.SetMany(ctx, items, 5*time.Minute)
if err != nil {
fmt.Println("Redis not available, skipping example")
return
}
// 批量获取
keys := []string{"key1", "key2", "key3"}
results, err := mc.GetMany(ctx, keys)
if err != nil {
fmt.Println("Redis not available, skipping example")
return
}
for _, key := range keys {
if value, ok := results[key]; ok {
fmt.Printf("%s: %s\n", key, value)
}
}
// 批量删除
_ = mc.DeleteMany(ctx, keys)
}
Output:
Example (Counter) ¶
package main
import (
"context"
"fmt"
"github.com/f2xme/gox/cache"
"github.com/f2xme/gox/cache/adapter/memory"
)
func main() {
// 创建缓存实例
c, _ := memory.New()
defer c.(cache.Closer).Close()
counter := c.(cache.Counter)
ctx := context.Background()
// 递增页面浏览量
views, _ := counter.Incr(ctx, "page:views", 1)
fmt.Println("Page views:", views)
// 再次递增
views, _ = counter.Incr(ctx, "page:views", 1)
fmt.Println("Page views:", views)
// 递减配额
quota, _ := counter.Incr(ctx, "api:quota", 100)
fmt.Println("Initial quota:", quota)
quota, _ = counter.Incr(ctx, "api:quota", -1)
fmt.Println("After one request:", quota)
// 浮点数计数器
balance, _ := counter.IncrFloat(ctx, "account:balance", 100.50)
fmt.Println("Initial balance:", balance)
balance, _ = counter.IncrFloat(ctx, "account:balance", -10.25)
fmt.Println("After withdrawal:", balance)
}
Output: Page views: 1 Page views: 2 Initial quota: 100 After one request: 99 Initial balance: 100.5 After withdrawal: 90.25
Example (Lock_mem) ¶
ExampleLocker_mem 演示内存缓存的锁
package main
import (
"context"
"fmt"
"time"
"github.com/f2xme/gox/cache"
"github.com/f2xme/gox/cache/adapter/memory"
)
func main() {
c, _ := memory.New()
defer c.(cache.Closer).Close()
locker, ok := c.(cache.Locker)
if !ok {
fmt.Println("Cache does not support locking")
return
}
ctx := context.Background()
// 尝试获取锁
unlock, err := locker.TryLock(ctx, "resource:1", 10*time.Second)
if err != nil {
fmt.Println("Failed to acquire lock")
return
}
defer unlock()
// 执行需要保护的操作
fmt.Println("Lock acquired, performing critical operation...")
}
Output: Lock acquired, performing critical operation...
Example (Serializer) ¶
ExampleCache_serializer 演示自定义序列化器
package main
import (
"context"
"fmt"
"github.com/f2xme/gox/cache"
"github.com/f2xme/gox/cache/adapter/memory"
)
func main() {
type Config struct {
Host string
Port int
}
c, _ := memory.New()
defer c.(cache.Closer).Close()
// 使用 Gob 序列化器(更快,但仅限 Go)
typed := cache.NewTyped[Config](c, cache.WithSerializer(cache.GobSerializer))
ctx := context.Background()
config := Config{Host: "localhost", Port: 8080}
_ = typed.Set(ctx, "config", config, 0)
retrieved, _ := typed.Get(ctx, "config")
fmt.Printf("%s:%d\n", retrieved.Host, retrieved.Port)
}
Output: localhost:8080
Index ¶
- Constants
- Variables
- type BatchStore
- type Closer
- type ConditionalStore
- type Counter
- type Expirer
- type LockInfo
- type LockMetadata
- type Locker
- type Scanner
- type Serializer
- type Store
- type Typed
- func (t *Typed[T]) Delete(ctx context.Context, key string) error
- func (t *Typed[T]) DeleteMany(ctx context.Context, keys []string) error
- func (t *Typed[T]) Exists(ctx context.Context, key string) (bool, error)
- func (t *Typed[T]) Get(ctx context.Context, key string) (T, error)
- func (t *Typed[T]) GetMany(ctx context.Context, keys []string) (map[string]T, error)
- func (t *Typed[T]) GetOrLoad(ctx context.Context, key string, ttl time.Duration, ...) (T, error)
- func (t *Typed[T]) Set(ctx context.Context, key string, value T, ttl time.Duration) error
- func (t *Typed[T]) SetMany(ctx context.Context, items map[string]T, ttl time.Duration) error
- type TypedOption
Examples ¶
Constants ¶
const KeepTTL time.Duration = -1
KeepTTL 表示条件更新时保留键现有的过期时间。
const NoExpiration time.Duration = 0
NoExpiration 表示键永不过期。
Variables ¶
var ErrInvalidTTL = errors.New("cache: invalid ttl")
ErrInvalidTTL 当操作收到不支持的 TTL 值时返回。
var ErrLocked = errors.New("cache: lock already held")
ErrLocked 当无法立即获取锁时返回。
var ErrNotFound = errors.New("cache: key not found")
ErrNotFound 当键在缓存中不存在时返回。
Functions ¶
This section is empty.
Types ¶
type BatchStore ¶ added in v0.15.1
type BatchStore interface {
Store
// GetMany 在单次操作中获取多个键。
// 不存在的键不会包含在返回的 map 中。
GetMany(ctx context.Context, keys []string) (map[string][]byte, error)
// SetMany 使用相同的 TTL 存储多个键值对。
// TTL 为 0 表示永不过期。
SetMany(ctx context.Context, items map[string][]byte, ttl time.Duration) error
// DeleteMany 在单次操作中删除多个键。
DeleteMany(ctx context.Context, keys []string) error
// ExistsMany 批量检查键是否存在。
// 返回 map 中每个键对应其存在状态。
ExistsMany(ctx context.Context, keys []string) (map[string]bool, error)
}
BatchStore 扩展 Store 接口,提供批量操作以提高性能。
type ConditionalStore ¶ added in v0.15.1
type ConditionalStore interface {
// SetNX 仅当键不存在时设置值。
// 返回 true 表示设置成功,false 表示键已存在。
SetNX(ctx context.Context, key string, value []byte, ttl time.Duration) (bool, error)
// SetXX 仅当键存在时更新值。
// ttl 为 KeepTTL 时保留原有过期时间。
// 返回 true 表示更新成功,false 表示键不存在。
SetXX(ctx context.Context, key string, value []byte, ttl time.Duration) (bool, error)
// Swap 原子性地获取旧值并设置新值。
// 如果键不存在返回 nil, ErrNotFound。
// ttl 为 KeepTTL 时保留原有过期时间。
Swap(ctx context.Context, key string, value []byte, ttl time.Duration) ([]byte, error)
}
ConditionalStore 提供条件写入和原子替换操作。
type Counter ¶ added in v0.6.0
type Counter interface {
// Incr 原子性地增加键的值,并返回增加后的值。
// 如果键不存在,则初始化为 0 后再增加。
// delta 可以是正数(递增)或负数(递减)。
Incr(ctx context.Context, key string, delta int64) (int64, error)
// IncrFloat 原子性地增加键的浮点值,并返回增加后的值。
// 如果键不存在,则初始化为 0.0 后再增加。
// delta 可以是正数(递增)或负数(递减)。
IncrFloat(ctx context.Context, key string, delta float64) (float64, error)
}
Counter 提供原子计数器操作。 适用于需要原子递增/递减的场景,如计数器、限流器等。
type Expirer ¶ added in v0.15.1
type Expirer interface {
// TTL 返回键的剩余过期时间。
// 如果键不存在返回 ErrNotFound。
// 如果键没有设置过期时间返回 NoExpiration, nil。
TTL(ctx context.Context, key string) (time.Duration, error)
// Expire 更新键的过期时间而不修改值。
// ttl 为 NoExpiration 时移除过期时间。
// 如果键不存在返回 ErrNotFound。
Expire(ctx context.Context, key string, ttl time.Duration) error
// Persist 移除键的过期时间。
// 如果键不存在返回 ErrNotFound。
Persist(ctx context.Context, key string) error
}
Expirer 提供过期时间控制。
type LockInfo ¶ added in v0.15.0
type LockInfo struct {
Owner string // 锁持有者的标识
AcquiredAt time.Time // 锁获取时间
TTL time.Duration // 锁的剩余有效期
Reentrant bool // 是否为可重入锁
Count int // 重入计数(仅可重入锁有效)
}
LockInfo 包含锁的元数据。
type LockMetadata ¶ added in v0.15.0
type LockMetadata interface {
// GetLockInfo 查询锁的当前状态。
// 如果锁不存在或已过期返回 ErrNotFound。
GetLockInfo(ctx context.Context, key string) (LockInfo, error)
}
LockMetadata 提供锁元数据查询功能。
type Locker ¶
type Locker interface {
// Lock 为指定的键获取锁,使用指定的 TTL。
// 阻塞直到获取锁或 context 被取消。
// 返回一个必须调用以释放锁的 unlock 函数。
Lock(ctx context.Context, key string, ttl time.Duration) (unlock func() error, err error)
// TryLock 尝试为指定的键获取锁,使用指定的 TTL。
// 如果锁已被持有则立即返回 ErrLocked。
// 返回一个必须调用以释放锁的 unlock 函数。
TryLock(ctx context.Context, key string, ttl time.Duration) (unlock func() error, err error)
// LockWithRenewal 获取带自动续期的锁。
// renewInterval 指定续期间隔,通常设置为 ttl 的 1/3 到 1/2。
// 返回的 unlock 函数会自动停止续期并释放锁。
LockWithRenewal(ctx context.Context, key string, ttl, renewInterval time.Duration) (unlock func() error, err error)
// TryLockWithRenewal 非阻塞版本的 LockWithRenewal。
// 如果锁已被持有则立即返回 ErrLocked。
TryLockWithRenewal(ctx context.Context, key string, ttl, renewInterval time.Duration) (unlock func() error, err error)
// LockReentrant 获取可重入锁。
// ownerID 是调用者的唯一标识(如 requestID, traceID 等)。
// 同一个 ownerID 可以多次获取同一把锁,每次获取会增加重入计数。
// unlock 时会减少计数,计数归零时才真正释放锁。
LockReentrant(ctx context.Context, key string, ownerID string, ttl time.Duration) (unlock func() error, err error)
// TryLockReentrant 非阻塞版本的 LockReentrant。
TryLockReentrant(ctx context.Context, key string, ownerID string, ttl time.Duration) (unlock func() error, err error)
}
Locker 提供分布式锁功能。
type Scanner ¶ added in v0.15.0
type Scanner interface {
// Scan 使用游标迭代匹配 pattern 的键。
// pattern 支持 glob 模式:* 匹配任意字符,? 匹配单个字符,[abc] 匹配字符集。
// cursor 为 0 表示开始新的迭代,返回的 cursor 为 0 表示迭代结束。
// count 是每次迭代的建议返回数量(实际可能更多或更少)。
Scan(ctx context.Context, pattern string, cursor uint64, count int64) (keys []string, nextCursor uint64, err error)
}
Scanner 提供键遍历功能。
type Serializer ¶
type Serializer interface {
// Marshal 将值编码为字节
Marshal(v any) ([]byte, error)
// Unmarshal 将字节解码为值
Unmarshal(data []byte, v any) error
}
Serializer 定义了序列化和反序列化的接口
var GobSerializer Serializer = &gobSerializer{}
GobSerializer 是 Go 专用的 Gob 序列化器(较快)
var JSONSerializer Serializer = &jsonSerializer{}
JSONSerializer 是跨语言兼容的 JSON 序列化器(较慢)
type Store ¶ added in v0.15.1
type Store interface {
// Get 获取指定键的值。
// 如果键不存在则返回 ErrNotFound。
Get(ctx context.Context, key string) ([]byte, error)
// Set 使用指定的键和 TTL 存储值。
// TTL 为 0 表示永不过期。
Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
// Delete 从缓存中删除键。
// 如果键不存在不会返回错误。
Delete(ctx context.Context, key string) error
// Exists 检查键是否存在于缓存中。
Exists(ctx context.Context, key string) (bool, error)
}
Store 定义基础缓存操作接口。 所有实现必须支持字节级操作并支持 context。
Example (Basic) ¶
ExampleStore_basic 演示基本的缓存操作
package main
import (
"context"
"fmt"
"time"
"github.com/f2xme/gox/cache"
"github.com/f2xme/gox/cache/adapter/memory"
)
func main() {
// 创建内存缓存
c, _ := memory.New()
defer c.(cache.Closer).Close()
ctx := context.Background()
// 设置值
_ = c.Set(ctx, "user:1", []byte("Alice"), 5*time.Minute)
// 获取值
value, err := c.Get(ctx, "user:1")
if err == nil {
fmt.Println(string(value))
}
// 检查键是否存在
exists, _ := c.Exists(ctx, "user:1")
fmt.Println(exists)
// 删除键
_ = c.Delete(ctx, "user:1")
}
Output: Alice true
type Typed ¶
type Typed[T any] struct { // contains filtered or unexported fields }
Typed 提供类型安全的缓存操作包装器
Example ¶
ExampleTyped 演示类型安全的缓存包装器
package main
import (
"context"
"fmt"
"time"
"github.com/f2xme/gox/cache"
"github.com/f2xme/gox/cache/adapter/memory"
)
func main() {
type User struct {
ID int
Name string
}
// 创建内存缓存
c, _ := memory.New()
defer c.(cache.Closer).Close()
// 创建类型安全的包装器
typed := cache.NewTyped[User](c)
ctx := context.Background()
// 存储结构体
user := User{ID: 1, Name: "Alice"}
_ = typed.Set(ctx, "user:1", user, 5*time.Minute)
// 获取结构体
retrieved, err := typed.Get(ctx, "user:1")
if err == nil {
fmt.Printf("%s (ID: %d)\n", retrieved.Name, retrieved.ID)
}
}
Output: Alice (ID: 1)
func NewTyped ¶
func NewTyped[T any](cache Store, opts ...TypedOption) *Typed[T]
NewTyped 创建一个新的类型安全缓存包装器 默认使用 JSONSerializer
func (*Typed[T]) DeleteMany ¶ added in v0.15.1
DeleteMany 批量删除多个键 如果底层 cache 实现了 BatchStore 接口,则使用批量操作 否则降级为循环调用 Delete
func (*Typed[T]) GetMany ¶ added in v0.15.1
GetMany 批量获取多个键的值 如果底层 cache 实现了 BatchStore 接口,则使用批量操作 否则降级为循环调用 Get
func (*Typed[T]) GetOrLoad ¶ added in v0.15.1
func (t *Typed[T]) GetOrLoad(ctx context.Context, key string, ttl time.Duration, fn func(context.Context) (T, error)) (T, error)
GetOrLoad 实现 cache-aside 模式 首先尝试获取值,如果不存在则调用 fn 计算值并存储 使用 singleflight 防止缓存击穿
Example ¶
ExampleTyped_GetOrLoad 演示 cache-aside 模式
package main
import (
"context"
"fmt"
"time"
"github.com/f2xme/gox/cache"
"github.com/f2xme/gox/cache/adapter/memory"
)
func main() {
type Product struct {
ID int
Name string
Price float64
}
c, _ := memory.New()
defer c.(cache.Closer).Close()
typed := cache.NewTyped[Product](c)
ctx := context.Background()
// 模拟数据库查询函数
loadFromDB := func(context.Context) (Product, error) {
fmt.Println("Loading from database...")
return Product{ID: 1, Name: "Laptop", Price: 999.99}, nil
}
// 第一次调用:缓存未命中,从数据库加载
product, _ := typed.GetOrLoad(ctx, "product:1", 5*time.Minute, loadFromDB)
fmt.Printf("%s: $%.2f\n", product.Name, product.Price)
// 第二次调用:缓存命中,不会调用 loadFromDB
product, _ = typed.GetOrLoad(ctx, "product:1", 5*time.Minute, loadFromDB)
fmt.Printf("%s: $%.2f\n", product.Name, product.Price)
}
Output: Loading from database... Laptop: $999.99 Laptop: $999.99
type TypedOption ¶
type TypedOption func(*typedConfig)
TypedOption 是 Typed 的配置选项函数
func WithIgnoreSetErrors ¶ added in v0.15.1
func WithIgnoreSetErrors() TypedOption
WithIgnoreSetErrors 设置 GetOrLoad 在加载成功但缓存写入失败时仍返回加载值。