Skip to content

Commit

Permalink
feat: common 添加 redis sdk 支持哨兵和集群模式 (#3546)
Browse files Browse the repository at this point in the history
* feat: common 添加 redis sdk 支持哨兵和集群模式

* fix: 增加方法/结构体的注释
  • Loading branch information
yuyudeqiu authored Oct 15, 2024
1 parent 5cbfebd commit 8149fa7
Show file tree
Hide file tree
Showing 9 changed files with 537 additions and 0 deletions.
57 changes: 57 additions & 0 deletions bcs-common/common/redisclient/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package redisclient

import (
"context"
"fmt"
"time"

"github.com/alicebob/miniredis"
"github.com/go-redis/redis/v8"
)

type RedisMode string

const (
SingleMode RedisMode = "single" // Single mode
SentinelMode RedisMode = "sentinel" // Sentinel mode
ClusterMode RedisMode = "cluster" // Cluster mode
)

type Client interface {
// GetCli return the underlying Redis client
GetCli() redis.UniversalClient
// Ping checks the Redis server connection
Ping(ctx context.Context) (string, error)
Get(ctx context.Context, key string) (string, error)
Exists(ctx context.Context, key ...string) (int64, error)
Set(ctx context.Context, key string, value interface{}, duration time.Duration) (string, error)
SetEX(ctx context.Context, key string, value interface{}, expiration time.Duration) (string, error)
SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) (bool, error)
Del(ctx context.Context, key string) (int64, error)
Expire(ctx context.Context, key string, duration time.Duration) (bool, error)
}

// NewClient creates a Redis client based on the configuration for different deployment modes
func NewClient(config Config) (Client, error) {
switch config.Mode {
case SingleMode:
return NewSingleClient(config)
case SentinelMode:
return NewSentinelClient(config)
case ClusterMode:
return NewClusterClient(config)
}
return nil, fmt.Errorf("invalid config mode: %s", config.Mode)
}

// NewTestClient creates a Redis client for unit testing
func NewTestClient() (Client, error) {
mr, err := miniredis.Run()
if err != nil {
return nil, err
}
client := redis.NewClient(&redis.Options{
Addr: mr.Addr(),
})
return &SingleClient{cli: client}, nil
}
64 changes: 64 additions & 0 deletions bcs-common/common/redisclient/cluster.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package redisclient

import (
"context"
"time"

"github.com/go-redis/redis/v8"
)

// ClusterClient Redis client for cluster mode
type ClusterClient struct {
cli *redis.ClusterClient
}

// NewClusterClient init ClusterClient from config
func NewClusterClient(config Config) (*ClusterClient, error) {
cli := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: config.Addrs,
Password: config.Password,
DialTimeout: config.DialTimeout * time.Second,
ReadTimeout: config.ReadTimeout * time.Second,
WriteTimeout: config.WriteTimeout * time.Second,
PoolSize: config.PoolSize,
MinIdleConns: config.MinIdleConns,
IdleTimeout: config.IdleTimeout * time.Second,
})
return &ClusterClient{cli: cli}, nil
}

func (c *ClusterClient) GetCli() redis.UniversalClient {
return c.cli
}

func (c *ClusterClient) Ping(ctx context.Context) (string, error) {
return c.cli.Ping(ctx).Result()
}

func (c *ClusterClient) Get(ctx context.Context, key string) (string, error) {
return c.cli.Get(ctx, key).Result()
}

func (c *ClusterClient) Exists(ctx context.Context, key ...string) (int64, error) {
return c.cli.Exists(ctx, key...).Result()
}

func (c *ClusterClient) Set(ctx context.Context, key string, value interface{}, duration time.Duration) (string, error) {
return c.cli.Set(ctx, key, value, duration).Result()
}

func (c *ClusterClient) SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) (bool, error) {
return c.cli.SetNX(ctx, key, value, expiration).Result()
}

func (c *ClusterClient) SetEX(ctx context.Context, key string, value interface{}, expiration time.Duration) (string, error) {
return c.cli.SetEX(ctx, key, value, expiration).Result()
}

func (c *ClusterClient) Del(ctx context.Context, key string) (int64, error) {
return c.cli.Del(ctx, key).Result()
}

func (c *ClusterClient) Expire(ctx context.Context, key string, duration time.Duration) (bool, error) {
return c.cli.Expire(ctx, key, duration).Result()
}
58 changes: 58 additions & 0 deletions bcs-common/common/redisclient/cluster_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package redisclient

import (
"context"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

// setupClusterClient function for initializing Redis ClusterClient
func setupClusterClient(t *testing.T) *ClusterClient {
config := Config{
Mode: ClusterMode,
Addrs: []string{"127.0.0.1:7021", "127.0.0.1:7022", "127.0.0.1:7023"},
}
client, err := NewClusterClient(config)
assert.NoError(t, err)
assert.NotNil(t, client)
return client
}

// TestClusterPing tests ClusterClient connectivity
func TestClusterPing(t *testing.T) {
client := setupClusterClient(t)
result, err := client.GetCli().Ping(context.TODO()).Result()
assert.NoError(t, err)
assert.Equal(t, "PONG", result)
}

// TestClusterClient tests ClusterClient basic functionality
func TestClusterClient(t *testing.T) {
client := setupClusterClient(t)
ctx := context.Background()

// Test Set operation
_, err := client.Set(ctx, "key1", "value1", 10*time.Second)
assert.NoError(t, err)

// Test Get operation
val, err := client.Get(ctx, "key1")
assert.NoError(t, err)
assert.Equal(t, "value1", val)

// Test Exists operation
exists, err := client.Exists(ctx, "key1")
assert.NoError(t, err)
assert.Equal(t, int64(1), exists)

// Test Del operation
_, err = client.Del(ctx, "key1")
assert.NoError(t, err)

// Test if the key has been deleted
exists, err = client.Exists(ctx, "key1")
assert.NoError(t, err)
assert.Equal(t, int64(0), exists)
}
20 changes: 20 additions & 0 deletions bcs-common/common/redisclient/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package redisclient

import "time"

// Config contains the configuration required to initialize a Redis client
type Config struct {
Addrs []string // List of nodes (addresses)
MasterName string // Master node name in Sentinel mode
Password string // Password
DB int // Database index in single node mode
Mode RedisMode // Redis mode: single, sentinel, or cluster

// Options configs
DialTimeout time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration
PoolSize int
MinIdleConns int
IdleTimeout time.Duration
}
70 changes: 70 additions & 0 deletions bcs-common/common/redisclient/sentinel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package redisclient

import (
"context"
"errors"
"time"

"github.com/go-redis/redis/v8"
)

// SentinelClient Redis client for sentinel mode
type SentinelClient struct {
cli *redis.Client
}

// NewSentinelClient init SentinelClient from config
func NewSentinelClient(config Config) (*SentinelClient, error) {
if config.Mode != SentinelMode {
return nil, errors.New("redis mode not supported")
}
cli := redis.NewFailoverClient(&redis.FailoverOptions{
MasterName: config.MasterName,
SentinelAddrs: config.Addrs,
Password: config.Password,
DB: config.DB,
DialTimeout: config.DialTimeout,
ReadTimeout: config.ReadTimeout,
WriteTimeout: config.WriteTimeout,
PoolSize: config.PoolSize,
MinIdleConns: config.MinIdleConns,
IdleTimeout: config.IdleTimeout,
})
return &SentinelClient{cli: cli}, nil
}

func (c *SentinelClient) GetCli() redis.UniversalClient {
return c.cli
}

func (c *SentinelClient) Ping(ctx context.Context) (string, error) {
return c.cli.Ping(ctx).Result()
}

func (c *SentinelClient) Set(ctx context.Context, key string, value interface{}, duration time.Duration) (string, error) {
return c.cli.Set(ctx, key, value, duration).Result()
}

func (c *SentinelClient) SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) (bool, error) {
return c.cli.SetNX(ctx, key, value, expiration).Result()
}

func (c *SentinelClient) Get(ctx context.Context, key string) (string, error) {
return c.cli.Get(ctx, key).Result()
}

func (c *SentinelClient) Exists(ctx context.Context, key ...string) (int64, error) {
return c.cli.Exists(ctx, key...).Result()
}

func (c *SentinelClient) SetEX(ctx context.Context, key string, value interface{}, expiration time.Duration) (string, error) {
return c.cli.SetEX(ctx, key, value, expiration).Result()
}

func (c *SentinelClient) Del(ctx context.Context, key string) (int64, error) {
return c.cli.Del(ctx, key).Result()
}

func (c *SentinelClient) Expire(ctx context.Context, key string, duration time.Duration) (bool, error) {
return c.cli.Expire(ctx, key, duration).Result()
}
61 changes: 61 additions & 0 deletions bcs-common/common/redisclient/sentinel_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package redisclient

import (
"context"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

// setupClusterClient function for initializing Redis SentinelClient
func setupSentinel(t *testing.T) *SentinelClient {
config := Config{
Mode: SentinelMode,
Addrs: []string{"127.0.0.1:5001"}, // Sentinel addresses
MasterName: "mymaster", // Master name
DB: 0,
Password: "",
}
client, err := NewSentinelClient(config)
assert.NoError(t, err)
assert.NotNil(t, client)
return client
}

// TestSentinelClientPing tests SentinelClient connectivity
func TestSentinelClientPing(t *testing.T) {
client := setupSentinel(t)
result, err := client.GetCli().Ping(context.TODO()).Result()
assert.NoError(t, err)
assert.Equal(t, "PONG", result)
}

// TestSentinelClient tests SentinelClient basic functionality
func TestSentinelClient(t *testing.T) {
client := setupSentinel(t)
ctx := context.Background()

// Test Set operation
_, err := client.Set(ctx, "key1", "value1", 10*time.Second)
assert.NoError(t, err)

// Test Get operation
val, err := client.Get(ctx, "key1")
assert.NoError(t, err)
assert.Equal(t, "value1", val)

// Test Exists operation
exists, err := client.Exists(ctx, "key1")
assert.NoError(t, err)
assert.Equal(t, int64(1), exists)

// Test Del operation
_, err = client.Del(ctx, "key1")
assert.NoError(t, err)

// Test if the key has been deleted
exists, err = client.Exists(ctx, "key1")
assert.NoError(t, err)
assert.Equal(t, int64(0), exists)
}
Loading

0 comments on commit 8149fa7

Please sign in to comment.