redis client自适应读写命令选择主库 OR 读库 #4

Merged
zhangdeman merged 2 commits from feature/upgrade into master 2024-10-08 16:44:38 +08:00
5 changed files with 121 additions and 28 deletions

View File

@ -11,7 +11,6 @@ import (
"context" "context"
"git.zhangdeman.cn/zhangdeman/redis/define" "git.zhangdeman.cn/zhangdeman/redis/define"
"github.com/redis/go-redis/v9"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -21,9 +20,9 @@ import (
// //
// Date : 15:09 2024/6/18 // Date : 15:09 2024/6/18
type IRedisClient interface { type IRedisClient interface {
GetRealClient(instanceFlag string) *redis.Client // 获取客户端连接 GetRealClient(instanceFlag string) *define.ClientInfo // 获取客户端连接
GetRealClientWithError(instanceFlag string) (*redis.Client, error) // 获取带error的客户端连接 GetRealClientWithError(instanceFlag string) (*define.ClientInfo, error) // 获取带error的客户端连接
AddClient(instanceFlag string, instanceConfig *define.Options) error // 添加新的客户端连接 AddClient(instanceFlag string, instanceConfig *define.Config) error // 添加新的客户端连接
RemoveClient(instanceFlag string) // 移除一个客户端连接 RemoveClient(instanceFlag string) // 移除一个客户端连接
SetLogger(loggerInstance *zap.Logger, extraLogFieldList []string) // 设置日志实例, 全局生效, 而非针对某一个实例 SetLogger(loggerInstance *zap.Logger, extraLogFieldList []string) // 设置日志实例, 全局生效, 而非针对某一个实例
Exec(ctx context.Context, instanceFlag string, command string, args ...any) *define.RedisResult // 执行任意命令 Exec(ctx context.Context, instanceFlag string, command string, args ...any) *define.RedisResult // 执行任意命令

103
client.go
View File

@ -9,6 +9,7 @@ package redis
import ( import (
"context" "context"
"git.zhangdeman.cn/zhangdeman/consts"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -28,14 +29,14 @@ var (
func init() { func init() {
Client = &OwnClient{ Client = &OwnClient{
lock: &sync.RWMutex{}, lock: &sync.RWMutex{},
instanceTable: make(map[string]*redisClient.Client), instanceTable: make(map[string]*define.ClientInfo),
whiteCommandTable: make(map[string]bool), whiteCommandTable: make(map[string]bool),
} }
} }
type OwnClient struct { type OwnClient struct {
lock *sync.RWMutex lock *sync.RWMutex
instanceTable map[string]*redisClient.Client instanceTable map[string]*define.ClientInfo
whiteCommandTable map[string]bool whiteCommandTable map[string]bool
logger *zap.Logger logger *zap.Logger
extraLogFieldList []string extraLogFieldList []string
@ -66,7 +67,7 @@ func (o *OwnClient) Exec(ctx context.Context, instanceFlag string, command strin
ctx = context.Background() ctx = context.Background()
} }
var ( var (
instance *redisClient.Client instance *define.ClientInfo
) )
cmdParamList := []any{ cmdParamList := []any{
@ -117,7 +118,21 @@ func (o *OwnClient) Exec(ctx context.Context, instanceFlag string, command strin
if nil == ctx { if nil == ctx {
ctx = context.Background() 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 { if res.Err = cmdRes.Err(); nil != res.Err {
return res 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() o.lock.RLock()
defer o.lock.RUnlock() defer o.lock.RUnlock()
return o.instanceTable[instanceFlag] 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() o.lock.RLock()
defer o.lock.RUnlock() defer o.lock.RUnlock()
instance, exist := o.instanceTable[instanceFlag] instance, exist := o.instanceTable[instanceFlag]
@ -154,8 +169,65 @@ func (o *OwnClient) GetRealClientWithError(instanceFlag string) (*redisClient.Cl
return instance, nil return instance, nil
} }
func (o *OwnClient) AddClient(instanceFlag string, instanceConfig *define.Options) error { func (o *OwnClient) AddClient(instanceFlag string, instanceConfig *define.Config) error {
instance := redisClient.NewClient(&redisClient.Options{ 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, DB: instanceConfig.DB,
Addr: instanceConfig.Addr, Addr: instanceConfig.Addr,
ClientName: instanceConfig.ClientName, ClientName: instanceConfig.ClientName,
@ -187,19 +259,4 @@ func (o *OwnClient) AddClient(instanceFlag string, instanceConfig *define.Option
// DisableIndentity: instanceConfig.DisableIdentity, // DisableIndentity: instanceConfig.DisableIdentity,
// IdentitySuffix: instanceConfig.IdentitySuffix, // 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
} }

View File

@ -7,6 +7,8 @@
// Date : 2024-06-18 16:12 // Date : 2024-06-18 16:12
package define package define
import redisClient "github.com/redis/go-redis/v9"
type Options struct { type Options struct {
DB int `json:"db" yaml:"db" ini:"db" toml:"db"` // 选择的数据库序号 DB int `json:"db" yaml:"db" ini:"db" toml:"db"` // 选择的数据库序号
Network string `json:"network" yaml:"network" ini:"network" toml:"network"` // 网络连接方式 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. 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
}

2
go.mod
View File

@ -12,7 +12,7 @@ require (
) )
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/easymap v0.0.0-20240311030808-e2a2e6a3c211 // indirect
git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 // indirect git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 // indirect
git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20240618035451-8d48a6bd39dd // indirect git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20240618035451-8d48a6bd39dd // indirect

2
go.sum
View File

@ -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-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 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-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 h1:I/wOsRpCSRkU9vo1u703slQsmK0wnNeZzsWQOGtIAG0=
git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20240311030808-e2a2e6a3c211/go.mod h1:SrtvrQRdzt+8KfYzvosH++gWxo2ShPTzR1m3VQ6uX7U= 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= git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 h1:gUDlQMuJ4xNfP2Abl1Msmpa3fASLWYkNlqDFF/6GN0Y=