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"`
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CacheConfig defines a named cache store. Only the Memory backend is
|
// CacheConfig defines a named cache store. Memory and Redis backends are
|
||||||
// currently supported; other backends (Redis, file, plugin) are follow-ups.
|
// supported; other backends (file, plugin) are follow-ups.
|
||||||
type CacheConfig struct {
|
type CacheConfig struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Memory *MemoryCache `yaml:",omitempty" json:"memory,omitempty"`
|
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.
|
// MemoryCache configures the in-memory cache backend.
|
||||||
|
|||||||
Vendored
+15
-3
@@ -9,9 +9,9 @@ import (
|
|||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseCache converts a CacheConfig into a cache.Cache. Only the memory
|
// ParseCache converts a CacheConfig into a cache.Cache. Memory and Redis
|
||||||
// backend is currently supported. Returns nil when cfg is nil or no backend
|
// backends are supported. Returns nil when cfg is nil or no backend is
|
||||||
// is configured.
|
// configured.
|
||||||
func ParseCache(cfg *config.CacheConfig) cache.Cache {
|
func ParseCache(cfg *config.CacheConfig) cache.Cache {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user