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()
|
||||
}
|
||||
Reference in New Issue
Block a user