mirror of
https://github.com/go-gost/x.git
synced 2026-10-19 16:16:32 +00:00
x: add Redis backend for cache
Adds a RedisCache config block and redisCache implementation for the generic cache.Cache interface, enabling distributed HTTP response caching across gost instances. go-redis/redis/v8 was already a dependency (used by recorder and internal loader) — no new imports needed.
This commit is contained in:
Vendored
+119
@@ -0,0 +1,119 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/cache"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
// defaultKeyPrefix is prepended to cache keys when RedisOptions.KeyPrefix is empty.
|
||||
const defaultKeyPrefix = "gost:cache:"
|
||||
|
||||
// RedisOptions configures a Redis cache.
|
||||
type RedisOptions struct {
|
||||
// Addr is the Redis server address (host:port).
|
||||
Addr string
|
||||
// DB is the Redis database number.
|
||||
DB int
|
||||
// Username for Redis authentication. Optional.
|
||||
Username string
|
||||
// Password for Redis authentication. Optional.
|
||||
Password string
|
||||
// DefaultTTL is applied to entries stored without an explicit TTL.
|
||||
// A non-positive value means entries never expire by default.
|
||||
DefaultTTL time.Duration
|
||||
// KeyPrefix is prepended to all cache keys. Defaults to "gost:cache:".
|
||||
KeyPrefix string
|
||||
// Logger is used for debug logging. Optional.
|
||||
Logger logger.Logger
|
||||
}
|
||||
|
||||
// redisCache is a cache.Cache backed by a Redis server.
|
||||
//
|
||||
// Redis expires keys natively, so an expired entry is a true miss here — the
|
||||
// interface's serve-stale contract (returning expired entries from Get) does
|
||||
// not apply to this backend.
|
||||
type redisCache struct {
|
||||
client *redis.Client
|
||||
defaultTTL time.Duration
|
||||
keyPrefix string
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewRedisCache creates a Redis-backed cache. Close releases the underlying
|
||||
// client connections.
|
||||
func NewRedisCache(opts RedisOptions) cache.Cache {
|
||||
keyPrefix := opts.KeyPrefix
|
||||
if keyPrefix == "" {
|
||||
keyPrefix = defaultKeyPrefix
|
||||
}
|
||||
|
||||
return &redisCache{
|
||||
client: redis.NewClient(&redis.Options{
|
||||
Addr: opts.Addr,
|
||||
Username: opts.Username,
|
||||
Password: opts.Password,
|
||||
DB: opts.DB,
|
||||
}),
|
||||
defaultTTL: opts.DefaultTTL,
|
||||
keyPrefix: keyPrefix,
|
||||
logger: opts.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the entry for key, or cache.ErrNotFound if the key is absent
|
||||
// (including keys Redis has already expired).
|
||||
func (c *redisCache) Get(ctx context.Context, key string) (*cache.Entry, error) {
|
||||
k := c.keyPrefix + key
|
||||
|
||||
pipe := c.client.Pipeline()
|
||||
getCmd := pipe.Get(ctx, k)
|
||||
ttlCmd := pipe.PTTL(ctx, k)
|
||||
if _, err := pipe.Exec(ctx); err != nil {
|
||||
if err == redis.Nil {
|
||||
return nil, cache.ErrNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entry := &cache.Entry{
|
||||
Data: []byte(getCmd.Val()),
|
||||
}
|
||||
if ttl := ttlCmd.Val(); ttl > 0 {
|
||||
entry.Expiration = time.Now().Add(ttl)
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// Set stores data under key. A non-positive TTL (after applying the default)
|
||||
// stores the entry without expiration.
|
||||
func (c *redisCache) Set(ctx context.Context, key string, data []byte, opts ...cache.SetOption) error {
|
||||
var options cache.SetOptions
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
ttl := options.TTL
|
||||
if ttl <= 0 {
|
||||
ttl = c.defaultTTL
|
||||
}
|
||||
if ttl < 0 {
|
||||
ttl = 0 // redis: 0 = no expiration
|
||||
}
|
||||
|
||||
return c.client.Set(ctx, c.keyPrefix+key, data, ttl).Err()
|
||||
}
|
||||
|
||||
// Delete removes key from the cache. Deleting an absent key is a no-op.
|
||||
func (c *redisCache) Delete(ctx context.Context, key string) error {
|
||||
return c.client.Del(ctx, c.keyPrefix+key).Err()
|
||||
}
|
||||
|
||||
// Close releases the Redis client. Implements io.Closer so the registry
|
||||
// closes the client on hot reload.
|
||||
func (c *redisCache) Close() error {
|
||||
return c.client.Close()
|
||||
}
|
||||
+13
-2
@@ -324,11 +324,22 @@ type RewriterConfig struct {
|
||||
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||
}
|
||||
|
||||
// CacheConfig defines a named cache store. Only the Memory backend is
|
||||
// currently supported; other backends (Redis, file, plugin) are follow-ups.
|
||||
// CacheConfig defines a named cache store. Memory and Redis backends are
|
||||
// supported; other backends (file, plugin) are follow-ups.
|
||||
type CacheConfig struct {
|
||||
Name string `json:"name"`
|
||||
Memory *MemoryCache `yaml:",omitempty" json:"memory,omitempty"`
|
||||
Redis *RedisCache `yaml:",omitempty" json:"redis,omitempty"`
|
||||
}
|
||||
|
||||
// RedisCache configures the Redis cache backend.
|
||||
type RedisCache struct {
|
||||
Addr string `json:"addr"`
|
||||
DB int `yaml:",omitempty" json:"db,omitempty"`
|
||||
Username string `yaml:",omitempty" json:"username,omitempty"`
|
||||
Password string `yaml:",omitempty" json:"password,omitempty"`
|
||||
TTL time.Duration `yaml:",omitempty" json:"ttl,omitempty"`
|
||||
Key string `yaml:",omitempty" json:"key,omitempty"` // key prefix, default "gost:cache:"
|
||||
}
|
||||
|
||||
// MemoryCache configures the in-memory cache backend.
|
||||
|
||||
Vendored
+15
-3
@@ -9,9 +9,9 @@ import (
|
||||
"github.com/go-gost/x/config"
|
||||
)
|
||||
|
||||
// ParseCache converts a CacheConfig into a cache.Cache. Only the memory
|
||||
// backend is currently supported. Returns nil when cfg is nil or no backend
|
||||
// is configured.
|
||||
// ParseCache converts a CacheConfig into a cache.Cache. Memory and Redis
|
||||
// backends are supported. Returns nil when cfg is nil or no backend is
|
||||
// configured.
|
||||
func ParseCache(cfg *config.CacheConfig) cache.Cache {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
@@ -28,5 +28,17 @@ func ParseCache(cfg *config.CacheConfig) cache.Cache {
|
||||
})
|
||||
}
|
||||
|
||||
if cfg.Redis != nil {
|
||||
return xcache.NewRedisCache(xcache.RedisOptions{
|
||||
Addr: cfg.Redis.Addr,
|
||||
DB: cfg.Redis.DB,
|
||||
Username: cfg.Redis.Username,
|
||||
Password: cfg.Redis.Password,
|
||||
DefaultTTL: cfg.Redis.TTL,
|
||||
KeyPrefix: cfg.Redis.Key,
|
||||
Logger: logger.Default(),
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user