From 1b27f6ae36076248c8498faaf76b995ddee370f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Wed, 19 Jun 2024 10:24:51 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E5=A2=9E=E5=8A=A0Redis=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=20+=20=E6=8E=A5=E5=8F=A3=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- abstract/IRedisClient.go | 26 +++ client.go | 442 +++------------------------------------ define/config.go | 53 +++++ go.mod | 1 + go.sum | 2 + logger.go | 39 ---- 6 files changed, 116 insertions(+), 447 deletions(-) create mode 100644 abstract/IRedisClient.go create mode 100644 define/config.go delete mode 100644 logger.go diff --git a/abstract/IRedisClient.go b/abstract/IRedisClient.go new file mode 100644 index 0000000..55eafad --- /dev/null +++ b/abstract/IRedisClient.go @@ -0,0 +1,26 @@ +// Package abstract ... +// +// Description : abstract ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2024-06-18 15:09 +package abstract + +import ( + "github.com/redis/go-redis/v9" + "go.uber.org/zap" +) + +// IRedisClient redis客户端定义 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 15:09 2024/6/18 +type IRedisClient interface { + GetRealClient(instanceFlag string) *redis.ClusterClient // 获取客户端连接 + GetRealClientWithError(instanceFlag string) (*redis.ClusterClient, error) // 获取带error的客户端连接 + AddClient(instanceFlag string, instanceConfig *redis.ClusterOptions) error // 添加新的客户端连接 + RemoveClient(instanceFlag string) // 移除一个客户端连接 + SetLogger(loggerInstance *zap.Logger) // 设置日志实例 +} diff --git a/client.go b/client.go index dfffec7..712bf27 100644 --- a/client.go +++ b/client.go @@ -8,432 +8,58 @@ package redis import ( - "encoding/json" - "fmt" - "git.zhangdeman.cn/zhangdeman/consts" - "git.zhangdeman.cn/zhangdeman/serialize" - "path/filepath" - "strings" - "sync" - "time" - - "git.zhangdeman.cn/zhangdeman/logger" - redisInstance "github.com/go-redis/redis/v8" + "git.zhangdeman.cn/zhangdeman/redis/abstract" + "github.com/pkg/errors" + redisClient "github.com/redis/go-redis/v9" "go.uber.org/zap" + "sync" ) -var ( - // Client 连接实例 - Client ClientInterface -) +var Client abstract.IRedisClient -// InitWithCfgFile 使用配置文件初始化 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 21:24 2022/6/22 -func InitWithCfgFile(cfgFilePath string) error { - c := &OwnClient{ - lock: &sync.RWMutex{}, - instanceTable: make(map[string]*RealClient), - confTable: make(map[string]*FullConfig), - parseErrorFunc: defaultParseError, +func init() { + Client = &OwnClient{ + lock: &sync.RWMutex{}, + instanceTable: make(map[string]*redisClient.ClusterClient), } - if err := c.loadConfig(cfgFilePath); nil != err { - return err - } - if err := c.init(); nil != err { - return err - } - Client = c - return nil } -// InitWithCfgDir 使用配置目录初始化 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 21:31 2022/6/22 -func InitWithCfgDir(cfgDir string) error { - c := &OwnClient{ - lock: &sync.RWMutex{}, - instanceTable: make(map[string]*RealClient), - confTable: make(map[string]*FullConfig), - parseErrorFunc: defaultParseError, - } - if err := c.batchLoadConfig(cfgDir); nil != err { - return err - } - if err := c.init(); nil != err { - return err - } - Client = c - return nil -} - -// InitWithCfgFileList 使用批量的配置文件初始化 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 21:33 2022/6/22 -func InitWithCfgFileList(cfgFileList []string) error { - c := &OwnClient{ - lock: &sync.RWMutex{}, - instanceTable: make(map[string]*RealClient), - confTable: make(map[string]*FullConfig), - parseErrorFunc: defaultParseError, - } - for _, cfgFilePath := range cfgFileList { - if err := c.loadConfig(cfgFilePath); nil != err { - return err - } - } - if err := c.init(); nil != err { - return err - } - Client = c - return nil -} - -// defaultParseError ... -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 10:59 下午 2021/2/27 -func defaultParseError(err error) error { - if nil == err { - return nil - } - errMsg := err.Error() - if errMsg == "nil" || errMsg == "" { - return nil - } - strArr := strings.Split(errMsg, ":") - if len(strArr) != 2 { - return err - } - msg := strings.ToLower(strings.TrimSpace(strArr[1])) - if msg == "nil" || msg == "" { - return nil - } - return err -} - -// RealClient 包装好的 redis client -type RealClient struct { - Flag string // redis 标识 - Master *redisInstance.Client // redis 实例 - Slave *redisInstance.Client // redis 实例 - Logger *zap.Logger // 日志实例 -} - -// NewClient 获取redis client实例 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 5:05 下午 2021/2/27 -func NewClient(config map[string]*FullConfig, parseErrorFunc func(err error) error) (ClientInterface, error) { - c := &OwnClient{ - lock: &sync.RWMutex{}, - instanceTable: make(map[string]*RealClient), - loggerTable: make(map[string]*zap.Logger), - confTable: config, - parseErrorFunc: parseErrorFunc, - } - if nil == c.parseErrorFunc { - c.parseErrorFunc = defaultParseError - } - return c, c.init() -} - -// OwnClient 包装的redis client -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 4:52 下午 2021/2/27 type OwnClient struct { - lock *sync.RWMutex // 锁 - loggerTable map[string]*zap.Logger // 日志实例 - instanceTable map[string]*RealClient // redis 实例 - confTable map[string]*FullConfig // redis 配置 - parseErrorFunc func(err error) error // 解析err的function,解析执行结果是否为失败,有的场景,执行成功,返回 redis:nil / redis: + lock *sync.RWMutex + instanceTable map[string]*redisClient.ClusterClient + logger *zap.Logger } -// AddClientWithCfgFile 使用具体配置文件初始化 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 20:57 2022/6/15 -func (c *OwnClient) AddClientWithCfgFile(cfgPath string) error { - var ( - err error - ) - if err = c.loadConfig(cfgPath); nil != err { - return err - } - return c.init() +func (o *OwnClient) GetRealClient(instanceFlag string) *redisClient.ClusterClient { + o.lock.RLock() + defer o.lock.RUnlock() + return o.instanceTable[instanceFlag] } -// AddClientWithCfgDir 使用配置目录进行初始化 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 20:58 2022/6/15 -func (c *OwnClient) AddClientWithCfgDir(cfgDir string) error { - var ( - err error - ) - if err = c.batchLoadConfig(cfgDir); nil != err { - return err - } - return c.init() -} - -// AddClientWithCfg 通过配置增加实例 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 11:45 2023/8/16 -func (c *OwnClient) AddClientWithCfg(flag string, cfg *FullConfig) error { - c.lock.Lock() - c.confTable[flag] = cfg - c.lock.Unlock() - return c.init() -} - -// loadConfig 载入配置文件 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 14:31 2022/6/15 -func (c *OwnClient) loadConfig(cfgPath string) error { - var ( - err error - cfg FullConfig - ) - - filePathArr := strings.Split(cfgPath, string(filepath.Separator)) - if len(filePathArr) == 0 { - return CfgFilePathError() - } - fileName := strings.ToLower(filePathArr[len(filePathArr)-1]) - fileArr := strings.Split(fileName, ".") - if len(filePathArr) < 2 { - return CfgFileFormatErr("未知") - } - flag := strings.Trim( - strings.Trim( - strings.Trim(fileName, ".json"), - ".yaml"), ".yml") - switch strings.ToLower(fileArr[len(fileArr)-1]) { - case consts.FileTypeJson: - if err = serialize.File.ReadJSONContent(cfgPath, &cfg); nil != err { - return err - } - flag = strings.Join(fileArr[0:len(fileArr)-1], ".") - case consts.FileTypeYml: - fallthrough - case consts.FileTypeYaml: - if err = serialize.File.ReadYmlContent(cfgPath, &cfg); nil != err { - return err - } - flag = strings.Join(fileArr[0:len(fileArr)-1], ".") - default: - // 不支持的格式,跳过 - } - - c.lock.Lock() - c.confTable[flag] = &cfg - c.lock.Unlock() - return nil -} - -// batchLoadConfig 批量载入配置 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 16:44 2022/6/15 -func (c *OwnClient) batchLoadConfig(cfgDir string) error { - filepathNames, _ := filepath.Glob(filepath.Join(cfgDir, "*")) - for i := range filepathNames { - if err := c.loadConfig(filepathNames[i]); nil != err { - return err - } - } - return nil -} - -// init 初始化redis连接 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 5:31 下午 2021/2/27 -func (c *OwnClient) init() error { - var err error - for flag, conf := range c.confTable { - if _, exist := c.instanceTable[flag]; exist { - // 实例已经初始化 - continue - } - c.instanceTable[flag] = &RealClient{ - Flag: flag, - Master: redisInstance.NewClient(Config2Options(conf.Master)), - Slave: redisInstance.NewClient(Config2Options(conf.Slave)), - } - if c.instanceTable[flag].Logger, err = logger.GetLogInstanceFromInputConfig(conf.Logger); nil != err { - return InitLoggerErr(flag, err) - } - } - return nil -} - -// initLogger 初始化日志 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 7:07 下午 2021/2/27 -func (c *OwnClient) initLogger(flag string, conf *logger.InputLogConfig) error { - var err error - c.loggerTable[flag], err = logger.GetLogInstanceFromInputConfig(conf) - return LoggerInitFail(flag, err) -} - -// GetRedisClientWithError 获取redis实例 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 5:16 下午 2021/2/27 -func (c *OwnClient) GetRedisClientWithError(flag string) (*RealClient, error) { - c.lock.RLock() - defer c.lock.RUnlock() - redisClient, exist := c.instanceTable[flag] +func (o *OwnClient) GetRealClientWithError(instanceFlag string) (*redisClient.ClusterClient, error) { + o.lock.RLock() + defer o.lock.RUnlock() + instance, exist := o.instanceTable[instanceFlag] if !exist { - return nil, FlagNotFound(flag) + return nil, errors.New(instanceFlag + " : redis instance is not found") } - return redisClient, nil + return instance, nil } -// GetRedisClient ... -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 11:52 2023/8/16 -func (c *OwnClient) GetRedisClient(flag string) *RealClient { - redisClient, _ := c.GetRedisClientWithError(flag) - return redisClient +func (o *OwnClient) AddClient(instanceFlag string, instanceConfig *redisClient.ClusterOptions) error { + instance := redisClient.NewClusterClient(instanceConfig) + o.lock.Lock() + defer o.lock.Unlock() + o.instanceTable[instanceFlag] = instance + return nil } -// log 记录redis请求日志 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 8:52 下午 2021/2/27 -func (c *OwnClient) log(ctx *Context, realClient *RealClient, isMaster bool, cmdResult redisInstance.Cmder, startTime int64, finishTime int64) { - if nil == realClient || nil == realClient.Logger { - return - } - realClient.Logger.Info( - "执行redis命令日志记录", - zap.Any(ctx.RequestIDField, ctx.RequestID), // 上下文串联的requestID - zap.String("instance_flag", ctx.Flag), // redis 实例的标识 - zap.Any("exec_command_name", cmdResult.Name()), // 执行的命令 - zap.Any("exec_command_arg", cmdResult.Args()), // 执行的命令参数 - zap.Bool("use_master", isMaster), // 是否使用主节点 - zap.Float64("exec_used_time", float64(finishTime-startTime)/1e6), // 耗时,单位: ms - zap.Error(cmdResult.Err()), // 异常信息 - zap.String("command_result", cmdResult.String()), // 命令执行后的结果 - ) +func (o *OwnClient) RemoveClient(instanceFlag string) { + o.lock.Lock() + defer o.lock.Unlock() + delete(o.instanceTable, instanceFlag) } -// CommandProxy 执行命令的代理 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 9:41 下午 2021/2/27 -func (c *OwnClient) CommandProxy(ctx *Context, flag string, cmd string, param ...interface{}) (string, error) { - var ( - realClient *RealClient - err error - ) - if len(cmd) == 0 { - return "", EmptyCmd() - } - - if nil == ctx { - ctx = NewContext(flag) - } - if realClient, err = c.GetRedisClientWithError(ctx.Flag); nil != err { - return "", err - } - flagArr := strings.Split(flag, "#") - if len(flagArr) == 1 { - flagArr = append(flagArr, "r") - } - isMater := flagArr[1] == "w" - redisClient := realClient.Slave - if isMater { - redisClient = realClient.Master - } - redisCmd := append([]interface{}{cmd}, param...) - startTime := time.Now().Unix() - cmdResult := redisClient.Do(ctx.Ctx, redisCmd...) - go c.log(ctx, realClient, isMater, cmdResult, startTime, time.Now().UnixNano()) - return fmt.Sprintf("%v", cmdResult.Val()), c.parseErrorFunc(cmdResult.Err()) -} - -// CommandProxyWithReceiver 执行命令,并解析结果 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 10:00 下午 2021/2/27 -func (c *OwnClient) CommandProxyWithReceiver(ctx *Context, flag string, receiver interface{}, cmd string, param ...interface{}) error { - if nil == receiver { - return ReceiverISNIL() - } - var ( - err error - result string - ) - - if result, err = c.CommandProxy(ctx, flag, cmd, param...); nil != err { - return err - } - - return ResultConvertFail(json.Unmarshal([]byte(result), receiver)) -} - -// RemoveClient 移除client -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 11:49 2023/8/16 -func (c *OwnClient) RemoveClient(flag string) { - c.lock.Lock() - defer c.lock.Unlock() - delete(c.confTable, flag) - if _, exist := c.instanceTable[flag]; exist { - if nil != c.instanceTable[flag].Master { - _ = c.instanceTable[flag].Master.Close() - } - if nil != c.instanceTable[flag].Slave { - _ = c.instanceTable[flag].Slave.Close() - } - } - delete(c.instanceTable, flag) -} - -// ClientInterface 定义redis client的接口实现,方便单元测试数据mock -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 10:49 下午 2021/2/27 -type ClientInterface interface { - GetRedisClient(flag string) *RealClient - GetRedisClientWithError(flag string) (*RealClient, error) - CommandProxy(ctx *Context, flag string, cmd string, param ...interface{}) (string, error) - CommandProxyWithReceiver(ctx *Context, flag string, receiver interface{}, cmd string, param ...interface{}) error - AddClientWithCfgFile(cfgPath string) error - AddClientWithCfgDir(cfgDir string) error - AddClientWithCfg(flag string, cfg *FullConfig) error - RemoveClient(flag string) +func (o *OwnClient) SetLogger(loggerInstance *zap.Logger) { + o.logger = loggerInstance } diff --git a/define/config.go b/define/config.go new file mode 100644 index 0000000..95e60fe --- /dev/null +++ b/define/config.go @@ -0,0 +1,53 @@ +// Package define ... +// +// Description : define ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2024-06-18 16:12 +package define + +type ClusterOptions struct { + // A seed list of host:port addresses of cluster nodes. + Addrs []string `json:"addrs" yaml:"addrs" ini:"addrs" toml:"addrs"` // 集群IP列表 + // ClientName will execute the `CLIENT SETNAME ClientName` command for each conn. + ClientName string `json:"client_name" yaml:"client_name" ini:"client_name" toml:"client_name"` // 集群名称 + // The maximum number of retries before giving up. Command is retried + // on network errors and MOVED/ASK redirects. + // Default is 3 retries. + MaxRedirects int `json:"max_redirects" yaml:"max_redirects" ini:"max_redirects" toml:"max_redirects"` // 最大重试次数 + // Enables read-only commands on slave nodes. + ReadOnly bool `json:"read_only" yaml:"read_only" ini:"read_only" toml:"read_only"` // 只读 + // Allows routing read-only commands to the closest master or slave node. + // It automatically enables ReadOnly. + RouteByLatency bool `json:"route_by_latency" yaml:"route_by_latency" ini:"route_by_latency" toml:"route_by_latency"` + // Allows routing read-only commands to the random master or slave node. + // It automatically enables ReadOnly. + RouteRandomly bool `json:"route_randomly" yaml:"route_randomly" ini:"route_randomly" toml:"route_randomly"` + + Protocol int + Username string `json:"username" yaml:"username" ini:"username" toml:"username"` + Password string `json:"password" yaml:"password" ini:"password" toml:"password"` + + MaxRetries int `json:"max_retries" yaml:"max_retries" ini:"max_retries" toml:"max_retries"` + MinRetryBackoff int `json:"min_retry_backoff" yaml:"min_retry_backoff" ini:"min_retry_backoff" toml:"min_retry_backoff"` // 最小重试间隔,单位 : ms + MaxRetryBackoff int `json:"max_retry_backoff" yaml:"max_retry_backoff" ini:"max_retry_backoff" toml:"max_retry_backoff"` // 最大重试时间间隔, 单位 : ms + + DialTimeout int `json:"dial_timeout" yaml:"dial_timeout" ini:"dial_timeout" toml:"dial_timeout"` // 连接超时时间 + ReadTimeout int `json:"read_timeout" yaml:"read_timeout" ini:"read_timeout" toml:"read_timeout"` // 读取超时时间 + WriteTimeout int `json:"write_timeout" yaml:"write_timeout" ini:"write_timeout" toml:"write_timeout"` // 写入超时时间 + ContextTimeoutEnabled bool `json:"context_timeout_enabled" yaml:"context_timeout_enabled" ini:"context_timeout_enabled" toml:"context_timeout_enabled"` + + PoolFIFO bool `json:"pool_fifo" yaml:"pool_fifo" ini:"pool_fifo" toml:"pool_fifo"` + PoolSize int `json:"pool_size" yaml:"pool_size" ini:"pool_size" toml:"pool_size"` // applies per cluster node and not for the whole cluster + PoolTimeout int `json:"pool_timeout" yaml:"pool_timeout" ini:"pool_timeout" toml:"pool_timeout"` // 单位 : ms + MinIdleConn int `json:"min_idle_conn" yaml:"min_idle_conn" ini:"min_idle_conn" toml:"min_idle_conn"` // 最小空闲连接数 + MaxIdleConn int `json:"max_idle_conn" yaml:"max_idle_conn" ini:"max_idle_conn" toml:"max_idle_conn"` // 最大空闲连接数 + MaxActiveConn int `json:"max_active_conn" yaml:"max_active_conn" ini:"max_active_conn" toml:"max_active_conn"` // applies per cluster node and not for the whole cluster + ConnMaxIdleTime int `json:"conn_max_idle_time" yaml:"conn_max_idle_time" ini:"conn_max_idle_time" toml:"conn_max_idle_time"` // 连接最大空闲时长, 单位 : s + ConnMaxLifetime int `json:"conn_max_lifetime" yaml:"conn_max_lifetime" ini:"conn_max_lifetime" toml:"conn_max_lifetime"` // 连接最大存活时长, 单位ms + + DisableIdentity bool `json:"disable_identity" yaml:"disable_identity" ini:"disable_identity" toml:"disable_identity"` // Disable set-lib on connect. Default is false. + + IdentitySuffix string `json:"identity_suffix" yaml:"identity_suffix" ini:"identity_suffix" toml:"identity_suffix"` // Add suffix to client name. Default is empty. +} diff --git a/go.mod b/go.mod index 3ef09c9..7281130 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mozillazg/go-pinyin v0.20.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/redis/go-redis/v9 v9.5.3 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect diff --git a/go.sum b/go.sum index b9b8f6a..811c124 100644 --- a/go.sum +++ b/go.sum @@ -161,6 +161,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU= +github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/logger.go b/logger.go deleted file mode 100644 index 2c4e161..0000000 --- a/logger.go +++ /dev/null @@ -1,39 +0,0 @@ -// Package redis... -// -// Description : redis... -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 2021-02-27 5:26 下午 -package redis - -import ( - "git.zhangdeman.cn/zhangdeman/logger" - "go.uber.org/zap/zapcore" -) - -// LoggerConfig 日志配置 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 5:26 下午 2021/2/27 -type LoggerConfig struct { - LoggerPath string - LoggerFile string - LoggerLevel zapcore.Level - ConsoleOutput bool - Encoder zapcore.Encoder - SplitConfig *logger.RotateLogConfig -} - -// LogFieldConfig 日志字段配置 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 9:20 下午 2021/2/27 -type LogFieldConfig struct { - Message string - UsedTimeField string - CommandField string - FlagField string -} From 11ac38fe6933cba6a76802b570135d26e0cd5268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Wed, 19 Jun 2024 16:13:06 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E5=8D=87=E7=BA=A7redis=20client=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- abstract/IRedisClient.go | 14 ++++-- client.go | 104 +++++++++++++++++++++++++++++++++++++-- define/result.go | 24 +++++++++ 3 files changed, 132 insertions(+), 10 deletions(-) create mode 100644 define/result.go diff --git a/abstract/IRedisClient.go b/abstract/IRedisClient.go index 55eafad..b10f2b3 100644 --- a/abstract/IRedisClient.go +++ b/abstract/IRedisClient.go @@ -8,6 +8,8 @@ package abstract import ( + "context" + "git.zhangdeman.cn/zhangdeman/redis/define" "github.com/redis/go-redis/v9" "go.uber.org/zap" ) @@ -18,9 +20,11 @@ import ( // // Date : 15:09 2024/6/18 type IRedisClient interface { - GetRealClient(instanceFlag string) *redis.ClusterClient // 获取客户端连接 - GetRealClientWithError(instanceFlag string) (*redis.ClusterClient, error) // 获取带error的客户端连接 - AddClient(instanceFlag string, instanceConfig *redis.ClusterOptions) error // 添加新的客户端连接 - RemoveClient(instanceFlag string) // 移除一个客户端连接 - SetLogger(loggerInstance *zap.Logger) // 设置日志实例 + GetRealClient(instanceFlag string) *redis.ClusterClient // 获取客户端连接 + GetRealClientWithError(instanceFlag string) (*redis.ClusterClient, error) // 获取带error的客户端连接 + AddClient(instanceFlag string, instanceConfig *redis.ClusterOptions) error // 添加新的客户端连接 + RemoveClient(instanceFlag string) // 移除一个客户端连接 + SetLogger(loggerInstance *zap.Logger) // 设置日志实例, 全局生效, 而非针对某一个实例 + Exec(ctx context.Context, instanceFlag string, command string, args ...any) *define.RedisResult // 执行任意命令 + SetCommandWhiteList(command []string) // 设置命令的白名单,全局生效, 而非单独针对某一个实例 } diff --git a/client.go b/client.go index 712bf27..de19571 100644 --- a/client.go +++ b/client.go @@ -8,26 +8,120 @@ package redis import ( + "context" "git.zhangdeman.cn/zhangdeman/redis/abstract" + "git.zhangdeman.cn/zhangdeman/redis/define" + "git.zhangdeman.cn/zhangdeman/wrapper" "github.com/pkg/errors" redisClient "github.com/redis/go-redis/v9" "go.uber.org/zap" + "strings" "sync" + "time" ) var Client abstract.IRedisClient func init() { Client = &OwnClient{ - lock: &sync.RWMutex{}, - instanceTable: make(map[string]*redisClient.ClusterClient), + lock: &sync.RWMutex{}, + instanceTable: make(map[string]*redisClient.ClusterClient), + whiteCommandTable: make(map[string]bool), } } type OwnClient struct { - lock *sync.RWMutex - instanceTable map[string]*redisClient.ClusterClient - logger *zap.Logger + lock *sync.RWMutex + instanceTable map[string]*redisClient.ClusterClient + whiteCommandTable map[string]bool + logger *zap.Logger +} + +func (o *OwnClient) isAllowCommand(command string) bool { + if len(o.whiteCommandTable) == 0 { + // 未配置, 视为全部允许执行 + return true + } + if o.whiteCommandTable["*"] { + // 配置了 * 视为全部允许执行 + return true + } + command = strings.ToLower(strings.TrimSpace(command)) + o.lock.RLock() + defer o.lock.RUnlock() + return o.whiteCommandTable[command] +} + +// Exec 执行命令 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 11:05 2024/6/19 +func (o *OwnClient) Exec(ctx context.Context, instanceFlag string, command string, args ...any) *define.RedisResult { + var ( + instance *redisClient.ClusterClient + ) + + cmdParamList := []any{ + command, + } + argStrList := make([]string, 0) + for _, itemArg := range args { + argStrList = append(argStrList, wrapper.AnyDataType(itemArg).ToString().Value()) + cmdParamList = append(cmdParamList, itemArg) + } + res := &define.RedisResult{ + StartTime: time.Now().UnixMilli(), + FinishTime: 0, + UsedTime: 0, + Result: "", + Command: command, + ArgList: argStrList, + Err: nil, + } + defer func() { + res.FinishTime = time.Now().UnixMilli() + res.UsedTime = res.FinishTime - res.StartTime + if nil == o.logger { + // 未注入日志实例 + return + } + o.logger.Info( + "Redis命令执行记录", + zap.Int64("start_time", res.StartTime), + zap.Int64("finish_time", res.FinishTime), + zap.Int64("used_time", res.UsedTime), + zap.String("command", res.Command), + zap.String("arg_list", strings.Join(res.ArgList, " ")), + zap.String("execute_result", res.Result), + zap.Error(res.Err), + ) + }() + if instance, res.Err = o.GetRealClientWithError(instanceFlag); nil != res.Err { + return res + } + if nil == ctx { + ctx = context.Background() + } + cmdRes := instance.Do(ctx, cmdParamList...) + if res.Err = cmdRes.Err(); nil != res.Err { + return res + } + res.Result = cmdRes.String() + return res +} + +// SetCommandWhiteList 设置命令白名单, 空 或者 包含 * 则认为所有命令均允许执行 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 11:02 2024/6/19 +func (o *OwnClient) SetCommandWhiteList(commandList []string) { + o.lock.Lock() + defer o.lock.Unlock() + for _, itemCommand := range commandList { + o.whiteCommandTable[strings.ToLower(strings.TrimSpace(itemCommand))] = true + } } func (o *OwnClient) GetRealClient(instanceFlag string) *redisClient.ClusterClient { diff --git a/define/result.go b/define/result.go new file mode 100644 index 0000000..e4599b7 --- /dev/null +++ b/define/result.go @@ -0,0 +1,24 @@ +// Package define ... +// +// Description : define ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2024-06-19 10:49 +package define + +// RedisResult redis名玲玲执行结果 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 10:50 2024/6/19 +type RedisResult struct { + InstanceFlag string `json:"instance_flag"` // 实例标识 + StartTime int64 `json:"start_time"` // 开始执行时间, 单位 : ms + FinishTime int64 `json:"finish_time"` // 完成执行时间, 单位 : ms + UsedTime int64 `json:"used_time"` // 执行耗时, 单位 : ms + Result string `json:"result"` // 执行结果 + Command string `json:"command"` // 执行的命令 + ArgList []string `json:"arg_list"` // 参数列表 + Err error `json:"-"` // 失败信息 +} From 1a4ce91fb5ee3901f97ee643bb654611dcc699a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Wed, 19 Jun 2024 16:39:16 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E5=8C=85=E8=A3=85redis=E5=B8=B8=E7=94=A8?= =?UTF-8?q?=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wrapper.go | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 wrapper.go diff --git a/wrapper.go b/wrapper.go new file mode 100644 index 0000000..e3792ef --- /dev/null +++ b/wrapper.go @@ -0,0 +1,125 @@ +// Package redis ... +// +// Description : redis ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2024-06-19 16:15 +package redis + +import ( + "context" + "git.zhangdeman.cn/zhangdeman/redis/define" + "strings" + "time" +) + +var ( + Wrapper = &wrapper{} +) + +// wrapper 常用命令的包装 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:16 2024/6/19 +type wrapper struct { +} + +// Get Get命令 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:17 2024/6/19 +func (w *wrapper) Get(ctx context.Context, instanceFlag string, key string) *define.RedisResult { + return Client.Exec(ctx, instanceFlag, "GET", key) +} + +// Del 删除命令 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:19 2024/6/19 +func (w *wrapper) Del(ctx context.Context, instanceFlag string, keyList ...string) *define.RedisResult { + return Client.Exec(ctx, instanceFlag, "DEL", strings.Join(keyList, " ")) +} + +// SetEx 设置数据并且带有效期, 有效期单位 : s +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:22 2024/6/19 +func (w *wrapper) SetEx(ctx context.Context, instanceFlag string, key string, value string, ttl int64, withLock bool) *define.RedisResult { + now := time.Now().Unix() + if ttl > now { + // 传入过期时间大于当前时间, 说明是指定具体时间过期, 做一下处理 + ttl = ttl - now + } + if withLock { + return Client.Exec(ctx, instanceFlag, "SET", key, value, "EX", ttl, "NX") + } + return Client.Exec(ctx, instanceFlag, "SET", key, value, "EX", ttl) +} + +// LPop ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:29 2024/6/19 +func (w *wrapper) LPop(ctx context.Context, instanceFlag string, key string) *define.RedisResult { + return Client.Exec(ctx, instanceFlag, "LPOP", key) +} + +// RPop ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:30 2024/6/19 +func (w *wrapper) RPop(ctx context.Context, instanceFlag string, key string) *define.RedisResult { + return Client.Exec(ctx, instanceFlag, "RPOP", key) +} + +// LPush ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:31 2024/6/19 +func (w *wrapper) LPush(ctx context.Context, instanceFlag string, key string, value string) *define.RedisResult { + return Client.Exec(ctx, instanceFlag, "LPUSH", key, value) +} + +// RPush ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:31 2024/6/19 +func (w *wrapper) RPush(ctx context.Context, instanceFlag string, key string, value string) *define.RedisResult { + return Client.Exec(ctx, instanceFlag, "RPUSH", key, value) +} + +// HGet ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:33 2024/6/19 +func (w *wrapper) HGet(ctx context.Context, instanceFlag string, key string, field string) *define.RedisResult { + return Client.Exec(ctx, instanceFlag, "HGET", key, field) +} + +// HSet ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:33 2024/6/19 +func (w *wrapper) HSet(ctx context.Context, instanceFlag string, key string, field string, value string) *define.RedisResult { + return Client.Exec(ctx, instanceFlag, "HSET", key, field, value) +} + +// HDel ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:37 2024/6/19 +func (w *wrapper) HDel(ctx context.Context, instanceFlag string, key string, fieldList ...string) *define.RedisResult { + return Client.Exec(ctx, instanceFlag, "HSET", key, strings.Join(fieldList, " ")) +}