redis client自适应读写命令选择主库 OR 读库 #4
@ -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
103
client.go
@ -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
|
|
||||||
}
|
}
|
||||||
|
@ -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
2
go.mod
@ -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
2
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-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=
|
||||||
|
Loading…
Reference in New Issue
Block a user