diff --git a/abstract/IRedisClient.go b/abstract/IRedisClient.go index 1b3a8a8..92c5e8c 100644 --- a/abstract/IRedisClient.go +++ b/abstract/IRedisClient.go @@ -11,7 +11,6 @@ import ( "context" "git.zhangdeman.cn/zhangdeman/redis/define" - "github.com/redis/go-redis/v9" "go.uber.org/zap" ) @@ -21,9 +20,9 @@ import ( // // Date : 15:09 2024/6/18 type IRedisClient interface { - GetRealClient(instanceFlag string) *redis.Client // 获取客户端连接 - GetRealClientWithError(instanceFlag string) (*redis.Client, error) // 获取带error的客户端连接 - AddClient(instanceFlag string, instanceConfig *define.Options) error // 添加新的客户端连接 + GetRealClient(instanceFlag string) *define.ClientInfo // 获取客户端连接 + GetRealClientWithError(instanceFlag string) (*define.ClientInfo, error) // 获取带error的客户端连接 + AddClient(instanceFlag string, instanceConfig *define.Config) error // 添加新的客户端连接 RemoveClient(instanceFlag string) // 移除一个客户端连接 SetLogger(loggerInstance *zap.Logger, extraLogFieldList []string) // 设置日志实例, 全局生效, 而非针对某一个实例 Exec(ctx context.Context, instanceFlag string, command string, args ...any) *define.RedisResult // 执行任意命令 diff --git a/client.go b/client.go index e1258f8..24c7310 100644 --- a/client.go +++ b/client.go @@ -9,6 +9,7 @@ package redis import ( "context" + "git.zhangdeman.cn/zhangdeman/consts" "strings" "sync" "time" @@ -28,14 +29,14 @@ var ( func init() { Client = &OwnClient{ lock: &sync.RWMutex{}, - instanceTable: make(map[string]*redisClient.Client), + instanceTable: make(map[string]*define.ClientInfo), whiteCommandTable: make(map[string]bool), } } type OwnClient struct { lock *sync.RWMutex - instanceTable map[string]*redisClient.Client + instanceTable map[string]*define.ClientInfo whiteCommandTable map[string]bool logger *zap.Logger extraLogFieldList []string @@ -66,7 +67,7 @@ func (o *OwnClient) Exec(ctx context.Context, instanceFlag string, command strin ctx = context.Background() } var ( - instance *redisClient.Client + instance *define.ClientInfo ) cmdParamList := []any{ @@ -117,7 +118,21 @@ func (o *OwnClient) Exec(ctx context.Context, instanceFlag string, command strin if nil == ctx { ctx = context.Background() } - cmdRes := instance.Do(ctx, cmdParamList...) + if instance.ReadOnly && o.isWriteCommand(command) { + // 只读实例, 尝试执行写命令 + res.Err = errors.New(instanceFlag + " : instance is read only") + return res + } + if instance.ReadOnly && o.isWriteCommand(command) && instance.MasterClient() == nil { + // 写命令, 没有主库连接 + res.Err = errors.New(instanceFlag + " : instance master client is nil") + return res + } + redisRealClient := instance.MasterClient() + if !o.isWriteCommand(command) { + redisRealClient = instance.SlaveClient() + } + cmdRes := redisRealClient.Do(ctx, cmdParamList...) if res.Err = cmdRes.Err(); nil != res.Err { return res } @@ -138,13 +153,13 @@ func (o *OwnClient) SetCommandWhiteList(commandList []string) { } } -func (o *OwnClient) GetRealClient(instanceFlag string) *redisClient.Client { +func (o *OwnClient) GetRealClient(instanceFlag string) *define.ClientInfo { o.lock.RLock() defer o.lock.RUnlock() return o.instanceTable[instanceFlag] } -func (o *OwnClient) GetRealClientWithError(instanceFlag string) (*redisClient.Client, error) { +func (o *OwnClient) GetRealClientWithError(instanceFlag string) (*define.ClientInfo, error) { o.lock.RLock() defer o.lock.RUnlock() instance, exist := o.instanceTable[instanceFlag] @@ -154,8 +169,65 @@ func (o *OwnClient) GetRealClientWithError(instanceFlag string) (*redisClient.Cl return instance, nil } -func (o *OwnClient) AddClient(instanceFlag string, instanceConfig *define.Options) error { - instance := redisClient.NewClient(&redisClient.Options{ +func (o *OwnClient) AddClient(instanceFlag string, instanceConfig *define.Config) error { + if nil == instanceConfig.Master && !instanceConfig.ReadOnly { + // 不是只读, 则要求 主库配置 和 从库配置都要存在 + return errors.New(instanceFlag + " : master config is nil") + } + + clientInfo := &define.ClientInfo{ + ReadOnly: instanceConfig.ReadOnly, + Master: nil, + Slave: nil, + } + + if nil != instanceConfig.Master { + clientInfo.Master = o.newClient(instanceConfig.Master) + } + if nil != instanceConfig.Slave { + clientInfo.Master = o.newClient(instanceConfig.Slave) + } + o.lock.Lock() + defer o.lock.Unlock() + o.instanceTable[instanceFlag] = clientInfo + return nil +} + +func (o *OwnClient) RemoveClient(instanceFlag string) { + o.lock.Lock() + defer o.lock.Unlock() + delete(o.instanceTable, instanceFlag) +} + +func (o *OwnClient) SetLogger(loggerInstance *zap.Logger, extraLogFieldList []string) { + o.logger = loggerInstance + o.extraLogFieldList = extraLogFieldList +} + +// isWriteCommand 判断是否写命令 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:22 2024/10/8 +func (o *OwnClient) isWriteCommand(command string) bool { + return wrapperOperate.ArrayType([]string{ + consts.RedisCommandDel, + consts.RedisCommandSet, + consts.RedisCommandLpush, + consts.RedisCommandRpush, + consts.RedisCommandMSet, + consts.RedisCommandPublish, + consts.RedisCommandPsubScribe, + }).Has(strings.ToUpper(command)) >= 0 +} + +// newClient 获取客户端连接 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:12 2024/10/8 +func (o *OwnClient) newClient(instanceConfig *define.Options) *redisClient.Client { + return redisClient.NewClient(&redisClient.Options{ DB: instanceConfig.DB, Addr: instanceConfig.Addr, ClientName: instanceConfig.ClientName, @@ -187,19 +259,4 @@ func (o *OwnClient) AddClient(instanceFlag string, instanceConfig *define.Option // DisableIndentity: instanceConfig.DisableIdentity, // IdentitySuffix: instanceConfig.IdentitySuffix, }) - o.lock.Lock() - defer o.lock.Unlock() - o.instanceTable[instanceFlag] = instance - return nil -} - -func (o *OwnClient) RemoveClient(instanceFlag string) { - o.lock.Lock() - defer o.lock.Unlock() - delete(o.instanceTable, instanceFlag) -} - -func (o *OwnClient) SetLogger(loggerInstance *zap.Logger, extraLogFieldList []string) { - o.logger = loggerInstance - o.extraLogFieldList = extraLogFieldList } diff --git a/define/config.go b/define/config.go index e087659..11e24fc 100644 --- a/define/config.go +++ b/define/config.go @@ -7,6 +7,8 @@ // Date : 2024-06-18 16:12 package define +import redisClient "github.com/redis/go-redis/v9" + type Options struct { DB int `json:"db" yaml:"db" ini:"db" toml:"db"` // 选择的数据库序号 Network string `json:"network" yaml:"network" ini:"network" toml:"network"` // 网络连接方式 @@ -41,3 +43,36 @@ type Options struct { IdentitySuffix string `json:"identity_suffix" yaml:"identity_suffix" ini:"identity_suffix" toml:"identity_suffix"` // Add suffix to client name. Default is empty. } + +// Config 数据库连接配置 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 15:59 2024/10/8 +type Config struct { + ReadOnly bool `json:"readonly" yaml:"readonly" ini:"readonly" toml:"readonly"` + Master *Options `json:"master" yaml:"master" ini:"master" toml:"master"` // 主库配置 + Slave *Options `json:"slave" yaml:"slave" ini:"slave" toml:"slave"` // 主库配置 +} + +// ClientInfo 客户端连接信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:06 2024/10/8 +type ClientInfo struct { + ReadOnly bool // 是否只读 + Master *redisClient.Client // 主库连接 + Slave *redisClient.Client // 从库连接 +} + +func (cf *ClientInfo) MasterClient() *redisClient.Client { + return cf.Master +} + +func (cf *ClientInfo) SlaveClient() *redisClient.Client { + if nil != cf.Slave { + return cf.Slave + } + return cf.Master +} diff --git a/go.mod b/go.mod index 3fcba66..9e92595 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( ) require ( - git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240924065029-c865046cd9e7 // indirect + git.zhangdeman.cn/zhangdeman/consts v0.0.0-20241008084126-0b1c661317ee // indirect git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20240311030808-e2a2e6a3c211 // indirect git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 // indirect git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20240618035451-8d48a6bd39dd // indirect diff --git a/go.sum b/go.sum index 4a98133..1a44b7b 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240612081722-31c64d4d4ce7 h1:QR8vMX git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240612081722-31c64d4d4ce7/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240924065029-c865046cd9e7 h1:tyCPCMK+68PZ0axZylQHitMVp1d5mzNr9/YqMHXqo+A= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240924065029-c865046cd9e7/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= +git.zhangdeman.cn/zhangdeman/consts v0.0.0-20241008084126-0b1c661317ee h1:4nuaCr5GQcx4z9/xWeEnjmLVV6J0j+QT68+AUKI9dFc= +git.zhangdeman.cn/zhangdeman/consts v0.0.0-20241008084126-0b1c661317ee/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20240311030808-e2a2e6a3c211 h1:I/wOsRpCSRkU9vo1u703slQsmK0wnNeZzsWQOGtIAG0= git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20240311030808-e2a2e6a3c211/go.mod h1:SrtvrQRdzt+8KfYzvosH++gWxo2ShPTzR1m3VQ6uX7U= git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 h1:gUDlQMuJ4xNfP2Abl1Msmpa3fASLWYkNlqDFF/6GN0Y=