// redka 内嵌 Redis 兼容存储实现(v0.2.x API) // 使用 github.com/nalgeon/redka — 基于 SQLite 的 Redis 替代方案 // Logan.Li // 零外部依赖,数据存在本地文件中,非常适合独立二进制部署 package redis import ( "context" "fmt" "os" "path/filepath" "time" "github.com/nalgeon/redka" _ "github.com/mattn/go-sqlite3" // redka.Open 需要 database/sql 驱动名 sqlite3 // Logan.Li ) // redkaCache 基于 Redka v0.2.x 的缓存实现 // Logan.Li type redkaCache struct { db *redka.DB } func newRedka(cfg Config) (Cache, error) { // redka.db 与独立二进制同级存放 // Logan.Li dataDir := cfg.DataDir if dataDir == "" { dataDir = "." } if err := os.MkdirAll(dataDir, 0o755); err != nil { return nil, fmt.Errorf("redka: 创建数据目录失败: %w", err) } dbPath := filepath.Join(dataDir, "redka.db") db, err := redka.Open(dbPath, nil) if err != nil { return nil, err } return &redkaCache{db: db}, nil } func (r *redkaCache) Capabilities() Capabilities { return Capabilities{SortedSet: false, PubSub: false} } func (r *redkaCache) Get(ctx context.Context, key string) (string, error) { // Redka v0.2.x: Str().Get() 返回 core.Value // Logan.Li val, err := r.db.Str().Get(key) if err == redka.ErrNotFound { return "", nil } if err != nil { return "", err } return val.String(), nil } func (r *redkaCache) Set(ctx context.Context, key string, value string, expiration time.Duration) error { // Redka v0.2.x: Str().Set() 返回 error // Logan.Li if err := r.db.Str().Set(key, value); err != nil { return err } if expiration > 0 { _, err := r.db.Key().Expire(key, expiration) return err } return nil } // SetNX check-then-set — Redka v0.2.0 无原子 SetNX,使用 Exists+Set 模拟。 // 本地/redka 模式为单副本运行,不存在并发竞争,非原子实现安全。 func (r *redkaCache) SetNX(ctx context.Context, key string, value string, expiration time.Duration) (bool, error) { ok, err := r.db.Key().Exists(key) if err != nil { return false, err } if ok { return false, nil } if err := r.db.Str().Set(key, value); err != nil { return false, err } if expiration > 0 { if _, err := r.db.Key().Expire(key, expiration); err != nil { return false, err } } return true, nil } func (r *redkaCache) Del(ctx context.Context, keys ...string) error { for _, key := range keys { if _, err := r.db.Key().Delete(key); err != nil { return err } } return nil } func (r *redkaCache) Exists(ctx context.Context, keys ...string) (int64, error) { var count int64 for _, key := range keys { ok, err := r.db.Key().Exists(key) if err != nil { return 0, err } if ok { count++ } } return count, nil } func (r *redkaCache) Expire(ctx context.Context, key string, expiration time.Duration) error { ok, err := r.db.Key().Expire(key, expiration) if err != nil { return err } if !ok { return redka.ErrNotFound } return nil } func (r *redkaCache) Ping(ctx context.Context) error { // Redka 始终可用(内嵌数据库)// Logan.Li return nil } func (r *redkaCache) Close() error { return r.db.Close() } var errSortedSetNotSupported = fmt.Errorf("sorted set operations are not supported by the redka backend; use go-redis (driver=redis) instead") func (r *redkaCache) ZAdd(ctx context.Context, key string, member SortedSetMember) error { return errSortedSetNotSupported } func (r *redkaCache) ZRangeByScore(ctx context.Context, key string, min, max string) ([]SortedSetMember, error) { return nil, errSortedSetNotSupported } func (r *redkaCache) ZRemRangeByScore(ctx context.Context, key string, min, max string) error { return errSortedSetNotSupported } var errPubSubNotSupported = fmt.Errorf("Pub/Sub is not supported by the redka backend; use go-redis (driver=redis)") func (r *redkaCache) Publish(ctx context.Context, channel string, message string) error { // local/redka 无 Pub/Sub,流水线日志走轮询 Get,此处 no-op // Logan.Li return nil }