feat: Redis cluster support + version auto-detect + metrics API
This commit is contained in:
1 parent
8384006ae9
commit
3af65f2350
10 files changed
+485
-66
No files matched your search
@@ -13,8 +13,186 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
// Helper: get Redis client from instance
|
||||
func getRedisClient(instance *model.Instance, db int) (*redis.Client, error) {
|
||||
// Helper: get Redis client from instance (auto-detect standalone vs cluster)
|
||||
type redisClientWrapper struct {
|
||||
Client *redis.Client
|
||||
ClusterClient *redis.ClusterClient
|
||||
isCluster bool
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) Close() {
|
||||
if w.isCluster {
|
||||
w.ClusterClient.Close()
|
||||
} else {
|
||||
w.Client.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Ping, Info, DBSize, Scan, Type, TTL, Get, LRange, SMembers, ZRangeWithScores, HGetAll,
|
||||
// SlowLogGet, ClientList, Do, Set, RPush, SAdd, HSet, Expire, Del — unified interface via wrapper methods
|
||||
|
||||
func (w *redisClientWrapper) Ping(ctx context.Context) *redis.StatusCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.Ping(ctx)
|
||||
}
|
||||
return w.Client.Ping(ctx)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) Info(ctx context.Context, sections ...string) *redis.StringCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.Info(ctx, sections...)
|
||||
}
|
||||
return w.Client.Info(ctx, sections...)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) DBSize(ctx context.Context) *redis.IntCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.DBSize(ctx)
|
||||
}
|
||||
return w.Client.DBSize(ctx)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) Scan(ctx context.Context, cursor uint64, match string, count int64) *redis.ScanCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.Scan(ctx, cursor, match, count)
|
||||
}
|
||||
return w.Client.Scan(ctx, cursor, match, count)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) Type(ctx context.Context, key string) *redis.StatusCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.Type(ctx, key)
|
||||
}
|
||||
return w.Client.Type(ctx, key)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) TTL(ctx context.Context, key string) *redis.DurationCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.TTL(ctx, key)
|
||||
}
|
||||
return w.Client.TTL(ctx, key)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) Get(ctx context.Context, key string) *redis.StringCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.Get(ctx, key)
|
||||
}
|
||||
return w.Client.Get(ctx, key)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) LRange(ctx context.Context, key string, start, stop int64) *redis.StringSliceCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.LRange(ctx, key, start, stop)
|
||||
}
|
||||
return w.Client.LRange(ctx, key, start, stop)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) SMembers(ctx context.Context, key string) *redis.StringSliceCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.SMembers(ctx, key)
|
||||
}
|
||||
return w.Client.SMembers(ctx, key)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) ZRangeWithScores(ctx context.Context, key string, start, stop int64) *redis.ZSliceCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.ZRangeWithScores(ctx, key, start, stop)
|
||||
}
|
||||
return w.Client.ZRangeWithScores(ctx, key, start, stop)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) HGetAll(ctx context.Context, key string) *redis.MapStringStringCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.HGetAll(ctx, key)
|
||||
}
|
||||
return w.Client.HGetAll(ctx, key)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) SlowLogGet(ctx context.Context, count int64) *redis.SlowLogCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.SlowLogGet(ctx, count)
|
||||
}
|
||||
return w.Client.SlowLogGet(ctx, count)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) ClientList(ctx context.Context) *redis.StringCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.ClientList(ctx)
|
||||
}
|
||||
return w.Client.ClientList(ctx)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) Do(ctx context.Context, args ...interface{}) *redis.Cmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.Do(ctx, args...)
|
||||
}
|
||||
return w.Client.Do(ctx, args...)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.StatusCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.Set(ctx, key, value, expiration)
|
||||
}
|
||||
return w.Client.Set(ctx, key, value, expiration)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) RPush(ctx context.Context, key string, values ...interface{}) *redis.IntCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.RPush(ctx, key, values...)
|
||||
}
|
||||
return w.Client.RPush(ctx, key, values...)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) SAdd(ctx context.Context, key string, members ...interface{}) *redis.IntCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.SAdd(ctx, key, members...)
|
||||
}
|
||||
return w.Client.SAdd(ctx, key, members...)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) HSet(ctx context.Context, key string, values ...interface{}) *redis.IntCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.HSet(ctx, key, values...)
|
||||
}
|
||||
return w.Client.HSet(ctx, key, values...)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) Expire(ctx context.Context, key string, expiration time.Duration) *redis.BoolCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.Expire(ctx, key, expiration)
|
||||
}
|
||||
return w.Client.Expire(ctx, key, expiration)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) Del(ctx context.Context, keys ...string) *redis.IntCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.Del(ctx, keys...)
|
||||
}
|
||||
return w.Client.Del(ctx, keys...)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) LLen(ctx context.Context, key string) *redis.IntCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.LLen(ctx, key)
|
||||
}
|
||||
return w.Client.LLen(ctx, key)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) SCard(ctx context.Context, key string) *redis.IntCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.SCard(ctx, key)
|
||||
}
|
||||
return w.Client.SCard(ctx, key)
|
||||
}
|
||||
|
||||
func (w *redisClientWrapper) SScan(ctx context.Context, key string, cursor uint64, match string, count int64) *redis.ScanCmd {
|
||||
if w.isCluster {
|
||||
return w.ClusterClient.SScan(ctx, key, cursor, match, count)
|
||||
}
|
||||
return w.Client.SScan(ctx, key, cursor, match, count)
|
||||
}
|
||||
|
||||
func getRedisClient(instance *model.Instance, db int) (*redisClientWrapper, error) {
|
||||
password := instance.Password
|
||||
// Decrypt if encrypted
|
||||
if strings.HasPrefix(password, "enc:") {
|
||||
@@ -23,18 +201,36 @@ func getRedisClient(instance *model.Instance, db int) (*redis.Client, error) {
|
||||
password = decrypted
|
||||
}
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", instance.Host, instance.Port)
|
||||
|
||||
// Check cluster mode
|
||||
isCluster := instance.ClusterMode != nil && *instance.ClusterMode
|
||||
|
||||
if isCluster {
|
||||
clusterClient := redis.NewClusterClient(&redis.ClusterOptions{
|
||||
Addrs: []string{addr},
|
||||
Password: password,
|
||||
})
|
||||
ctx := context.Background()
|
||||
if err := clusterClient.Ping(ctx).Err(); err != nil {
|
||||
clusterClient.Close()
|
||||
return nil, fmt.Errorf("Redis Cluster connection failed: %v", err)
|
||||
}
|
||||
return &redisClientWrapper{ClusterClient: clusterClient, isCluster: true}, nil
|
||||
}
|
||||
|
||||
// Standalone mode
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: fmt.Sprintf("%s:%d", instance.Host, instance.Port),
|
||||
Addr: addr,
|
||||
Password: password,
|
||||
DB: db,
|
||||
})
|
||||
// Test connection
|
||||
ctx := context.Background()
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
client.Close()
|
||||
return nil, fmt.Errorf("Redis connection failed: %v", err)
|
||||
}
|
||||
return client, nil
|
||||
return &redisClientWrapper{Client: client, isCluster: false}, nil
|
||||
}
|
||||
|
||||
// GET /api/v1/redis/:id/info
|
||||
|
||||
Reference in new issue
Block a user