-
Notifications
You must be signed in to change notification settings - Fork 249
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: common 添加 redis sdk 支持哨兵和集群模式 (#3546)
* feat: common 添加 redis sdk 支持哨兵和集群模式 * fix: 增加方法/结构体的注释
- Loading branch information
Showing
9 changed files
with
537 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.