cache

package
v0.16.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 28, 2026 License: MIT Imports: 8 Imported by: 0

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)
}
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

Examples

Constants

View Source
const KeepTTL time.Duration = -1

KeepTTL 表示条件更新时保留键现有的过期时间。

View Source
const NoExpiration time.Duration = 0

NoExpiration 表示键永不过期。

Variables

View Source
var ErrInvalidTTL = errors.New("cache: invalid ttl")

ErrInvalidTTL 当操作收到不支持的 TTL 值时返回。

View Source
var ErrLocked = errors.New("cache: lock already held")

ErrLocked 当无法立即获取锁时返回。

View Source
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 Closer

type Closer interface {
	// Close 释放缓存持有的所有资源。
	Close() error
}

Closer 为缓存实现提供资源清理功能。

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]) Delete

func (t *Typed[T]) Delete(ctx context.Context, key string) error

Delete 删除键

func (*Typed[T]) DeleteMany added in v0.15.1

func (t *Typed[T]) DeleteMany(ctx context.Context, keys []string) error

DeleteMany 批量删除多个键 如果底层 cache 实现了 BatchStore 接口,则使用批量操作 否则降级为循环调用 Delete

func (*Typed[T]) Exists

func (t *Typed[T]) Exists(ctx context.Context, key string) (bool, error)

Exists 检查键是否存在

func (*Typed[T]) Get

func (t *Typed[T]) Get(ctx context.Context, key string) (T, error)

Get 获取并反序列化值 如果键不存在,返回 ErrNotFound

func (*Typed[T]) GetMany added in v0.15.1

func (t *Typed[T]) GetMany(ctx context.Context, keys []string) (map[string]T, error)

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

func (*Typed[T]) Set

func (t *Typed[T]) Set(ctx context.Context, key string, value T, ttl time.Duration) error

Set 序列化并存储值

func (*Typed[T]) SetMany added in v0.15.1

func (t *Typed[T]) SetMany(ctx context.Context, items map[string]T, ttl time.Duration) error

SetMany 批量设置多个键值对 如果底层 cache 实现了 BatchStore 接口,则使用批量操作 否则降级为循环调用 Set

type TypedOption

type TypedOption func(*typedConfig)

TypedOption 是 Typed 的配置选项函数

func WithIgnoreSetErrors added in v0.15.1

func WithIgnoreSetErrors() TypedOption

WithIgnoreSetErrors 设置 GetOrLoad 在加载成功但缓存写入失败时仍返回加载值。

func WithSerializer

func WithSerializer(s Serializer) TypedOption

WithSerializer 设置自定义序列化器

Directories

Path Synopsis
adapter
memory
Package memory 提供基于内存的缓存实现。
Package memory 提供基于内存的缓存实现。
redis module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL