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

Merged
zhangdeman merged 2 commits from feature/upgrade into master 2024-10-08 16:44:38 +08:00
3 changed files with 109 additions and 27 deletions
Showing only changes of commit 6f502a5bdd - Show all commits

View File

@ -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 // 执行任意命令

View File

@ -28,14 +28,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 +66,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 +117,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 +152,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 +168,57 @@ 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{}).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 +250,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
}

View File

@ -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
}