func (r *Client) RestoreReplace(ctx context.Context, key string, ttl time.Duration, value string) *redis.StatusCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.RestoreReplace(ctx, r.k(key), ttl, value)
	}
	return r.client.RestoreReplace(ctx, r.k(key), ttl, value)
}
RestoreReplace -> Restore
package redis
import (
	"context"
	"fmt"
	"sync"
	"time"
	"github.com/abulo/ratel/v2/logger"
	"github.com/go-redis/redis/v8"
	"github.com/pkg/errors"
)
type Config struct {
	Type      bool      
	Hosts     []string  
	Password  string    
	Database  int       
	PoolSize  int       
	KeyPrefix string
}
func New(config *Config) *Client {
	opts := Options{}
	if config.Type {
		opts.Type = ClientCluster
	} else {
		opts.Type = ClientNormal
	}
	opts.Hosts = config.Hosts
	opts.KeyPrefix = config.KeyPrefix
	if config.PoolSize > 0 {
		opts.PoolSize = config.PoolSize
	} else {
		opts.PoolSize = 64
	}
	if config.Database > 0 {
		opts.Database = config.Database
	} else {
		opts.Database = 0
	}
	if config.Password != "" {
		opts.Password = config.Password
	}
	client := NewClient(opts)
	ctx := context.TODO()
	if err := client.Ping(ctx).Err(); err != nil {
		logger.Logger.Panic(err.Error())
	}
	return client
}
const RedisNil = redis.Nil
type Client struct {
	opts          Options
	client        *redis.Client
	clusterClient *redis.ClusterClient
	fmtString     string
}
func NewClient(opts Options) *Client {
	r := &Client{opts: opts}
	switch opts.Type {
	case ClientCluster:
		tc := redis.NewClusterClient(opts.GetClusterConfig())
		if trace {
			ctx := context.TODO()
			tc.ForEachShard(ctx, func(ctx context.Context, shard *redis.Client) error {
				shard.AddHook(OpenTelemetryHook{})
				return nil
			})
		}
		r.clusterClient = tc
	case ClientNormal:
		fallthrough
	default:
		tc := redis.NewClient(opts.GetNormalConfig())
		if trace {
			tc.AddHook(OpenTelemetryHook{})
		}
		r.client = tc
	}
	r.fmtString = opts.KeyPrefix + "%s"
	return r
}
func (r *Client) IsCluster() bool {
	if r.opts.Type == ClientCluster {
		return true
	}
	return false
}
func (r *Client) Prefix(key string) string {
	return fmt.Sprintf(r.fmtString, key)
}
func (r *Client) k(key string) string {
	return fmt.Sprintf(r.fmtString, key)
}
func (r *Client) ks(key ...string) []string {
	keys := make([]string, len(key))
	for i, k := range key {
		keys[i] = r.k(k)
	}
	return keys
}
func (r *Client) GetClient() redis.Cmdable {
	if r.IsCluster() {
		return r.clusterClient
	}
	return r.client
}
func (r *Client) MGetByPipeline(ctx context.Context, keys ...string) ([]string, error) {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	var res []string
	if r.IsCluster() {
		start := time.Now()
		pipeLineLen := 100
		pipeCount := len(keys)/pipeLineLen + 1
		pipes := make([]redis.Pipeliner, pipeCount)
		for i := 0; i < pipeCount; i++ {
			pipes[i] = r.client.Pipeline()
		}
		for i, k := range keys {
			p := pipes[i%pipeCount]
			p.Get(ctx, r.k(k))
		}
		logger.Logger.Debug("process cost: ", time.Since(start))
		start = time.Now()
		var wg sync.WaitGroup
		var lock sync.Mutex
		errors := make(chan error, pipeCount)
		for _, p := range pipes {
			p := p
			wg.Add(1)
			go func() {
				defer wg.Done()
				cmders, err := p.Exec(ctx)
				if err != nil {
					select {
					case errors <- err:
					default:
					}
					return
				}
				lock.Lock()
				defer lock.Unlock()
				for _, cmder := range cmders {
					result, _ := cmder.(*redis.StringCmd).Result()
					res = append(res, result)
				}
			}()
		}
		wg.Wait()
		logger.Logger.Debug("exec cost: ", time.Since(start))
		if len(errors) > 0 {
			return nil, <-errors
		}
		return res, nil
	}
	vals, err := r.client.MGet(ctx, keys...).Result()
	if redis.Nil != err && nil != err {
		return nil, err
	}
	for _, item := range vals {
		res = append(res, fmt.Sprintf("%s", item))
	}
	return res, err
}
func (r *Client) Pipeline() redis.Pipeliner {
	if r.IsCluster() {
		return r.clusterClient.Pipeline()
	}
	return r.client.Pipeline()
}
func (r *Client) Pipelined(ctx context.Context, fn func(redis.Pipeliner) error) ([]redis.Cmder, error) {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		r.clusterClient.Pipelined(ctx, fn)
	}
	return r.client.Pipelined(ctx, fn)
}
func (r *Client) TxPipeline() redis.Pipeliner {
	if r.IsCluster() {
		return r.clusterClient.TxPipeline()
	}
	return r.client.TxPipeline()
}
func (r *Client) TxPipelined(ctx context.Context, fn func(redis.Pipeliner) error) ([]redis.Cmder, error) {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.TxPipelined(ctx, fn)
	}
	return r.client.TxPipelined(ctx, fn)
}
func (r *Client) Command(ctx context.Context) *redis.CommandsInfoCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.Command(ctx)
	}
	return r.client.Command(ctx)
}
func (r *Client) ClientGetName(ctx context.Context) *redis.StringCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.ClientGetName(ctx)
	}
	return r.client.ClientGetName(ctx)
}
func (r *Client) Echo(ctx context.Context, message interface{}) *redis.StringCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.Echo(ctx, message)
	}
	return r.client.Echo(ctx, message)
}
func (r *Client) Ping(ctx context.Context) *redis.StatusCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.Ping(ctx)
	}
	return r.client.Ping(ctx)
}
func (r *Client) Quit(ctx context.Context) *redis.StatusCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.Quit(ctx)
	}
	return r.client.Quit(ctx)
}
func (r *Client) Del(ctx context.Context, keys ...string) *redis.IntCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.Del(ctx, r.ks(keys...)...)
	}
	return r.client.Del(ctx, r.ks(keys...)...)
}
func (r *Client) Unlink(ctx context.Context, keys ...string) *redis.IntCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.Unlink(ctx, r.ks(keys...)...)
	}
	return r.client.Unlink(ctx, r.ks(keys...)...)
}
func (r *Client) Dump(ctx context.Context, key string) *redis.StringCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.Dump(ctx, r.k(key))
	}
	return r.client.Dump(ctx, r.k(key))
}
func (r *Client) Exists(ctx context.Context, key ...string) *redis.IntCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.Exists(ctx, r.ks(key...)...)
	}
	return r.client.Exists(ctx, r.ks(key...)...)
}
func (r *Client) Expire(ctx context.Context, key string, expiration time.Duration) *redis.BoolCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.Expire(ctx, r.k(key), expiration)
	}
	return r.client.Expire(ctx, r.k(key), expiration)
}
func (r *Client) ExpireAt(ctx context.Context, key string, tm time.Time) *redis.BoolCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.ExpireAt(ctx, r.k(key), tm)
	}
	return r.client.ExpireAt(ctx, r.k(key), tm)
}
func (r *Client) Keys(ctx context.Context, pattern string) *redis.StringSliceCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.Keys(ctx, r.k(pattern))
	}
	return r.client.Keys(ctx, r.k(pattern))
}
func (r *Client) Migrate(ctx context.Context, host, port, key string, db int, timeout time.Duration) *redis.StatusCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.Migrate(ctx, host, port, r.k(key), db, timeout)
	}
	return r.client.Migrate(ctx, host, port, r.k(key), db, timeout)
}
func (r *Client) Move(ctx context.Context, key string, db int) *redis.BoolCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.Move(ctx, r.k(key), db)
	}
	return r.client.Move(ctx, r.k(key), db)
}
func (r *Client) ObjectRefCount(ctx context.Context, key string) *redis.IntCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.ObjectRefCount(ctx, r.k(key))
	}
	return r.client.ObjectRefCount(ctx, r.k(key))
}
func (r *Client) ObjectEncoding(ctx context.Context, key string) *redis.StringCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.ObjectEncoding(ctx, r.k(key))
	}
	return r.client.ObjectEncoding(ctx, r.k(key))
}
func (r *Client) ObjectIdleTime(ctx context.Context, key string) *redis.DurationCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.ObjectIdleTime(ctx, r.k(key))
	}
	return r.client.ObjectIdleTime(ctx, r.k(key))
}
func (r *Client) Persist(ctx context.Context, key string) *redis.BoolCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.Persist(ctx, r.k(key))
	}
	return r.client.Persist(ctx, r.k(key))
}
func (r *Client) PExpire(ctx context.Context, key string, expiration time.Duration) *redis.BoolCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.PExpire(ctx, r.k(key), expiration)
	}
	return r.client.PExpire(ctx, r.k(key), expiration)
}
func (r *Client) PExpireAt(ctx context.Context, key string, tm time.Time) *redis.BoolCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.PExpireAt(ctx, r.k(key), tm)
	}
	return r.client.PExpireAt(ctx, r.k(key), tm)
}
func (r *Client) PTTL(ctx context.Context, key string) *redis.DurationCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.PTTL(ctx, r.k(key))
	}
	return r.client.PTTL(ctx, r.k(key))
}
func (r *Client) RandomKey(ctx context.Context) *redis.StringCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.RandomKey(ctx)
	}
	return r.client.RandomKey(ctx)
}
func (r *Client) Rename(ctx context.Context, key, newkey string) *redis.StatusCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.Rename(ctx, r.k(key), r.k(newkey))
	}
	return r.client.Rename(ctx, r.k(key), r.k(newkey))
}
func (r *Client) RenameNX(ctx context.Context, key, newkey string) *redis.BoolCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.RenameNX(ctx, r.k(key), r.k(newkey))
	}
	return r.client.RenameNX(ctx, r.k(key), r.k(newkey))
}
func (r *Client) Restore(ctx context.Context, key string, ttl time.Duration, value string) *redis.StatusCmd {
	if ctx == nil || ctx.Err() != nil {
		ctx = context.TODO()
	}
	if r.IsCluster() {
		return r.clusterClient.Restore(ctx, r.k(key), ttl, value)
	}
	return r.client.Restore(ctx, r.k(key), ttl, value)
}