cachex

package
v0.2.5 Latest Latest
Warning

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

Go to latest
Published: Jul 6, 2026 License: MIT Imports: 6 Imported by: 0

README

cachex

cachex 是 Aisphere Kernel 的统一缓存模块。它基于 Redis(go-redis/v9),支持单点、集群、哨兵三种模式,提供缓存语义 + Redis 数据结构 + Pub/Sub 全套能力。

快速上手

import (
    "github.com/aisphereio/kernel/cachex"
    _ "github.com/aisphereio/kernel/cachex/redis"
)

cache, _ := cachex.New(cachex.Config{
    Driver: "redis",
    Addrs:  []string{"localhost:6379"},
})
defer cache.Close()

// 基础 CRUD
cache.Set(ctx, "user:1", &user, 5*time.Minute)
cache.Get(ctx, "user:1", &user)

// GetOrSet (缓存穿透防护)
cache.GetOrSet(ctx, "user:1", &user, 5*time.Minute, func(ctx) (any, error) {
    return db.FindUser(ctx, 1)
})

// 分布式锁
ok, _ := cache.SetIfNotExist(ctx, "lock:job:42", "owner", 30*time.Second)

// 原子计数器
n, _ := cache.Incr(ctx, "rate_limit:user:1")

// Pub/Sub
sub, _ := cache.Subscribe(ctx, "notifications")
cache.Publish(ctx, "notifications", "hello")

模式

模式 配置
单点 Addrs: ["localhost:6379"]
集群 Addrs: ["node1:6379","node2:6379","node3:6379"], Cluster: true
哨兵 Addrs: ["sentinel1:26379","sentinel2:26379"], MasterName: "mymaster"

API

类别 方法
基础 CRUD Get / Set / Del / Exists / Expire / TTL
批量 MGet / MSet
函数式 GetOrSet
分布式锁 SetIfNotExist
计数器 Incr / IncrBy / Decr
Pub/Sub Publish / Subscribe
Hash HSet / HGet / HGetAll / HDel
List LPush / RPush / LPop / RPop / LRange / LLen
Set SAdd / SRem / SMembers / SIsMember
生命周期 Ping / Close

测试

# 单元测试(miniredis,不需要 Docker)
go test ./cachex/... -short

# 集成测试(testcontainers,需要 Docker)
go test ./cachex/...

# 或用外部 Redis
KERNEL_CACHEX_REDIS_ADDR=localhost:6379 go test ./cachex/... -run TestIntegration

Documentation

Overview

Package cachex provides the unified cache API for Aisphere Kernel.

cachex is the ONLY cache abstraction that business code should depend on. It exposes a stable Cache interface backed by Redis (single-node or cluster), with JSON serialization, context propagation, and error normalization built in.

Quickstart

import (
    "github.com/aisphereio/kernel/cachex"
    _ "github.com/aisphereio/kernel/cachex/redis" // register "redis" driver
)

cache, err := cachex.New(cachex.Config{
    Driver: "redis",
    Addrs:  []string{"localhost:6379"},
})
if err != nil { return err }
defer cache.Close()

// Set with TTL
err = cache.Set(ctx, "user:123", user, 5*time.Minute)

// Get (auto-deserializes JSON into dest)
var user User
err = cache.Get(ctx, "user:123", &user)

// GetOrSet: cache-through pattern (prevents cache penetration)
err = cache.GetOrSet(ctx, "user:123", &user, 5*time.Minute, func(ctx context.Context) (*User, error) {
    return db.FindUser(ctx, 123)
})

// SetIfNotExist: distributed lock primitive
ok, err := cache.SetIfNotExist(ctx, "lock:job:42", "owner", 30*time.Second)

// Incr: atomic counter
count, err := cache.Incr(ctx, "rate_limit:user:123")

Drivers

import _ "github.com/aisphereio/kernel/cachex/redis" // registers "redis"

The redis driver uses github.com/redis/go-redis/v9 and supports both single-node and cluster modes (set Config.Cluster = true).

Forbidden patterns

Do not import `github.com/redis/go-redis/v9` in business code. Use the cachex.Cache interface. Do not use `encoding/json` directly for cache serialization — cachex handles it internally.

Index

Constants

View Source
const (
	CodeNotFound        = errorx.Code("CACHEX_NOT_FOUND")
	CodeInvalidConfig   = errorx.Code("CACHEX_INVALID_CONFIG")
	CodeUnknownDriver   = errorx.Code("CACHEX_UNKNOWN_DRIVER")
	CodeClosed          = errorx.Code("CACHEX_CLOSED")
	CodeTypeMismatch    = errorx.Code("CACHEX_TYPE_MISMATCH")
	CodeNilValue        = errorx.Code("CACHEX_NIL_VALUE")
	CodeOperationFailed = errorx.Code("CACHEX_OPERATION_FAILED")
	CodeTimeout         = errorx.Code("CACHEX_TIMEOUT")
)

Variables

View Source
var (
	// ErrNotFound is returned by Get when the key does not exist.
	// It is the cache equivalent of dbx.ErrNoRows.
	ErrNotFound = errors.New("cachex: key not found")

	// ErrNilConfig is returned by New when Config is missing required fields.
	ErrNilConfig = errors.New("cachex: config is missing required fields")

	// ErrUnknownDriver is returned when the driver name has not been registered.
	ErrUnknownDriver = errors.New("cachex: unknown driver (did you import cachex/redis?)")

	// ErrClosed is returned when a closed cache is used.
	ErrClosed = errors.New("cachex: cache is closed")

	// ErrTypeMismatch is returned when Get cannot deserialize into the target type.
	ErrTypeMismatch = errors.New("cachex: type mismatch during deserialization")

	// ErrNilValue is returned when attempting to cache a nil value via GetOrSet.
	ErrNilValue = errors.New("cachex: value function returned nil")
)

Public sentinel errors.

Functions

func IsDriverRegistered

func IsDriverRegistered(name string) bool

IsDriverRegistered returns true if the named driver has been registered.

func NormalizeError added in v0.0.5

func NormalizeError(err error) error

func RegisterDriver

func RegisterDriver(name string, fn DriverOpener)

RegisterDriver registers a driver opener under the given name. Called by cachex/redis in its init() function.

func RegisteredDrivers

func RegisteredDrivers() []string

RegisteredDrivers returns the names of all registered drivers.

Types

type Cache

type Cache interface {

	// Get retrieves the value for key and deserializes into dest.
	// Returns ErrNotFound if the key does not exist.
	Get(ctx context.Context, key string, dest any) error

	// Set stores value at key with the given TTL.
	// TTL == 0 means no expiration.
	Set(ctx context.Context, key string, value any, ttl time.Duration) error

	// Del removes one or more keys.
	Del(ctx context.Context, keys ...string) error

	// Exists checks if any of the keys exist. Returns count.
	Exists(ctx context.Context, keys ...string) (int64, error)

	// Expire sets a TTL on an existing key. No-op if key doesn't exist.
	Expire(ctx context.Context, key string, ttl time.Duration) error

	// TTL returns the remaining TTL of a key.
	// Returns -1 if key has no expiration.
	// Returns -2 if key does not exist.
	TTL(ctx context.Context, key string) (time.Duration, error)

	// MGet retrieves multiple keys. dest must be *[]T or *[]any.
	// Missing keys appear as nil in the result.
	MGet(ctx context.Context, keys []string, dest any) error

	// MSet sets multiple key-value pairs with the same TTL.
	MSet(ctx context.Context, pairs map[string]any, ttl time.Duration) error

	// GetOrSet retrieves the value for key. If the key is missing, it calls
	// fn to compute the value, caches it with the given TTL, and returns it.
	// This is the standard cache penetration prevention pattern.
	GetOrSet(ctx context.Context, key string, dest any, ttl time.Duration, fn func(ctx context.Context) (any, error)) error

	// SetIfNotExist sets key=value with TTL only if key does not exist.
	// Returns true if the key was set (i.e., acquired).
	SetIfNotExist(ctx context.Context, key string, value any, ttl time.Duration) (bool, error)

	// Incr atomically increments key by 1. Creates the key with value 1 if
	// it doesn't exist. Returns the new value.
	Incr(ctx context.Context, key string) (int64, error)

	// IncrBy atomically increments key by delta. Returns the new value.
	IncrBy(ctx context.Context, key string, delta int64) (int64, error)

	// Decr atomically decrements key by 1. Returns the new value.
	Decr(ctx context.Context, key string) (int64, error)

	// Publish sends a message to a channel. Returns the number of subscribers.
	Publish(ctx context.Context, channel string, message any) (int64, error)

	// Subscribe subscribes to channels and returns a Subscription.
	// Call Subscription.Close() to stop receiving.
	Subscribe(ctx context.Context, channels ...string) (*Subscription, error)

	// HSet sets a field in a hash.
	HSet(ctx context.Context, key, field string, value any) error

	// HGet gets a field from a hash and deserializes into dest.
	// Returns ErrNotFound if the field does not exist.
	HGet(ctx context.Context, key, field string, dest any) error

	// HGetAll returns all fields of a hash as a map.
	// dest must be *map[string]any or *map[string]T.
	HGetAll(ctx context.Context, key string, dest any) error

	// HDel removes fields from a hash.
	HDel(ctx context.Context, key string, fields ...string) error

	// LPush prepends values to a list.
	LPush(ctx context.Context, key string, values ...any) error

	// RPush appends values to a list.
	RPush(ctx context.Context, key string, values ...any) error

	// LPop removes and returns the first element.
	LPop(ctx context.Context, key string, dest any) error

	// RPop removes and returns the last element.
	RPop(ctx context.Context, key string, dest any) error

	// LRange returns a range of elements from a list.
	// dest must be *[]T or *[]any.
	LRange(ctx context.Context, key string, start, stop int64, dest any) error

	// LLen returns the length of a list.
	LLen(ctx context.Context, key string) (int64, error)

	// SAdd adds members to a set.
	SAdd(ctx context.Context, key string, members ...any) error

	// SRem removes members from a set.
	SRem(ctx context.Context, key string, members ...any) error

	// SMembers returns all members of a set.
	// dest must be *[]T or *[]any.
	SMembers(ctx context.Context, key string, dest any) error

	// SIsMember checks if a value is in a set.
	SIsMember(ctx context.Context, key string, member any) (bool, error)

	// Ping verifies the cache is reachable.
	Ping(ctx context.Context) error

	// Close closes the cache connection. Idempotent.
	Close() error

	// DriverName returns the registered driver name.
	DriverName() string
}

Cache is the runtime cache interface used by kernel modules and apps.

All methods accept context.Context. Methods that take a dest pointer (Get / GetOrSet) auto-serialize/deserialize via JSON.

func New

func New(cfg Config) (Cache, error)

New opens a cache connection using the supplied Config.

type Config

type Config struct {
	// Driver selects the registered driver: "redis".
	Driver string `json:"driver"`

	// Addrs is the list of Redis node addresses.
	// Single-node: ["localhost:6379"]
	// Cluster:     ["node1:6379", "node2:6379", "node3:6379"]
	Addrs []string `json:"addrs"`

	// Username for AUTH (Redis 6+ ACL).
	Username string `json:"username"`

	// Password for AUTH.
	Password string `json:"password"`

	// DB selects the Redis database (0-15 for single-node; ignored in cluster).
	DB int `json:"db"`

	// MasterName enables Sentinel mode (non-empty = sentinel).
	MasterName string `json:"master_name"`

	// Cluster enables cluster mode. If true, Addrs are seed nodes.
	Cluster bool `json:"cluster"`

	// PoolSize is the max number of socket connections.
	// Default is 10 * runtime.GOMAXPROCS.
	PoolSize int `json:"pool_size"`

	// MinIdleConns is the minimum number of idle connections.
	MinIdleConns int `json:"min_idle_conns"`

	// DialTimeout is the timeout for establishing connections.
	DialTimeout time.Duration `json:"dial_timeout_ns"`

	// ReadTimeout is the timeout for socket reads.
	ReadTimeout time.Duration `json:"read_timeout_ns"`

	// WriteTimeout is the timeout for socket writes.
	WriteTimeout time.Duration `json:"write_timeout_ns"`

	// KeyPrefix is prepended to all keys (namespace isolation).
	KeyPrefix string `json:"key_prefix"`

	// TLSEnabled enables TLS.
	TLSEnabled bool `json:"tls_enabled"`

	// TLSSkipVerify skips TLS certificate verification.
	TLSSkipVerify bool `json:"tls_skip_verify"`

	// Logger is the component logger. If nil, cachex uses logx.DefaultLogger().
	Logger logx.Logger `json:"-" yaml:"-"`

	// Metrics is the optional metrics manager for cache operations.
	Metrics metricsx.Manager `json:"-" yaml:"-"`

	// MetricsEnabled controls whether cache operations record metrics.
	MetricsEnabled bool `json:"metrics_enabled" yaml:"metrics_enabled"`
}

Config holds the configuration for a cache connection.

func (Config) Validate

func (c Config) Validate() error

Validate returns ErrNilConfig if required fields are missing.

type DriverOpener

type DriverOpener func(cfg Config) (Cache, error)

DriverOpener opens a Cache for the given Config.

type Message

type Message struct {
	Channel string
	Pattern string // non-empty if subscribed via pattern
	Payload string
}

Message is a single Pub/Sub message.

type Subscription

type Subscription struct {
	// Channel receives messages from all subscribed channels.
	Channel <-chan *Message

	// internal fields (set by driver implementation)
	UnsubscribeFn func(channels ...string) error
	CloseFn       func() error
}

Subscription represents an active Pub/Sub subscription.

func (*Subscription) Close

func (s *Subscription) Close() error

Close closes the subscription entirely.

func (*Subscription) Unsubscribe

func (s *Subscription) Unsubscribe(channels ...string) error

Unsubscribe stops receiving from the given channels.

Directories

Path Synopsis
Package redis registers the "redis" driver for cachex.
Package redis registers the "redis" driver for cachex.

Jump to

Keyboard shortcuts

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