This commit is contained in:
kzkzzzz
2025-03-22 01:24:57 +08:00
parent 197476e805
commit 7e0bf82418
22 changed files with 2291 additions and 919 deletions

View File

@@ -3,178 +3,245 @@ package myredis
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
jsoniter "github.com/json-iterator/go"
"git.makemake.in/kzkzzzz/mycommon/myconf"
"github.com/google/uuid"
"github.com/redis/go-redis/v9"
"log"
"sync"
"time"
)
const DefaultKey = "default"
const (
DefaultInstance = "redis"
)
type Client struct {
*redis.Client
redisOpt *redis.Options
disablePing bool
}
//type Conf struct {
// Addr string
// Password string
// Db int
// PoolSize int // 连接池数量 如果不够用会继续新建连接 可以用 MaxActiveConns 限制
// MinIdleConn int // 最小空闲连接数 预热可能用到
// MaxIdleConn int // 最大空闲连接数 0不限制
// MaxActiveConn int // 0 不限制, 如果不限制, 超出pool size可以继续新建立连接, 但使用完之后不会放回连接池
// PoolTimeout string // 等待连接池超时时间 Default is ReadTimeout + 1 second.
// DialTimeout string // 拨号超时时间
// ReadTimeout string // 读取超时
// WriteTimeout string // 写入超时
// MaxRetries int // 重试次数
// ConnMaxIdleTime string // 连接空闲时间
//}
var (
DefaultConfig = &Config{
Addr: "127.0.0.1:6379",
Password: "",
DB: 0,
PoolSize: 16,
MinIdleConn: 4,
MaxConnAge: "4h",
IdleTimeout: "15m",
}
instanceMap = make(map[string]*MyRedis)
defaultClient *redis.Client
instanceMap = &sync.Map{}
)
type (
MyRedis struct {
*redis.Client
}
Config struct {
Addr string
Password string
DB int
PoolSize int
MinIdleConn int
MaxConnAge string
IdleTimeout string
}
)
func DB(key ...string) *MyRedis {
var key0 string
if len(key) > 0 {
key0 = key[0]
func GetClient(name ...string) *Client {
var instanceName string
if len(name) > 0 {
instanceName = name[0]
} else {
key0 = DefaultKey
instanceName = DefaultInstance
}
instance, ok := instanceMap[key0]
v, ok := instanceMap.Load(instanceName)
if !ok {
panic(fmt.Errorf("redis %s not config", key0))
panic(fmt.Errorf("redis instance [%s] not init", instanceName))
}
return instance
return v.(*Client)
}
func InitDefault(config *Config) error {
return Init(DefaultKey, config)
}
func Init(key string, config *Config) error {
rd, err := New(config)
// InitClient 初始化全局默认client
func InitClient(config *myconf.Config, opts ...Opt) {
client, err := NewClient(DefaultInstance, config, opts...)
if err != nil {
return err
panic(err)
}
instanceMap[key] = rd
instanceMap.Store(DefaultInstance, client)
}
// InitClientInstance 初始化全局的client
func InitClientInstance(instanceName string, config *myconf.Config, opts ...Opt) {
client, err := NewClient(instanceName, config, opts...)
if err != nil {
panic(err)
}
instanceMap.Store(instanceName, client)
}
func NewClient(instanceName string, config *myconf.Config, opts ...Opt) (*Client, error) {
cf := &Conf{}
err := config.UnmarshalKey(instanceName, &cf)
if err != nil {
return nil, err
}
client, err := NewClientFromConf(cf, opts...)
if err != nil {
return nil, err
}
instanceMap.Store(instanceMap, client)
return client, nil
}
type Conf struct {
Addr string
Password string
Db int
PoolSize int // 连接池数量 如果不够用会继续新建连接 可以用 MaxActiveConns 限制
MinIdleConn int // 最小空闲连接数 预热可能用到
MaxIdleConn int // 最大空闲连接数 0不限制
MaxActiveConn int // 0 不限制, 如果不限制, 超出pool size可以继续新建立连接, 但使用完之后不会放回连接池
DialTimeout string // 拨号超时时间, 时间单位格式 10ms, 60s, 15m, 2h...
ReadTimeout string // 读取超时
WriteTimeout string // 写入超时
ConnMaxIdleTime string // 连接空闲时间
PoolTimeout string // 等待连接池超时时间 Default is ReadTimeout + 1 second.
MaxRetries int // 重试次数
}
func NewClientFromConf(cf *Conf, opts ...Opt) (*Client, error) {
c := &Client{
redisOpt: DefaultRedisOpt(),
}
for _, opt := range opts {
opt(c)
}
err := c.parseConfToRedisOpt(cf)
if err != nil {
return nil, err
}
client := redis.NewClient(c.redisOpt)
if c.disablePing == false {
_, err := client.Ping(context.Background()).Result()
if err != nil {
return nil, fmt.Errorf("redis ping err: %s", err)
}
}
c.Client = client
instanceMap.Store(uuid.New().String(), c)
log.Printf("connect redis success [addr:%s - db:%d]", cf.Addr, cf.Db)
return c, nil
}
func DefaultRedisOpt() *redis.Options {
return &redis.Options{
Addr: "",
Password: "",
DB: 0,
MaxRetries: 3, // 重试次数
DialTimeout: time.Millisecond * 500, // 拨号超时时间
ReadTimeout: time.Second, // 读取超时
WriteTimeout: time.Second * 3, // 写入超时
PoolSize: 1024, // 连接池数量 如果不够用会继续新建连接 可以用 MaxActiveConns 限制
PoolTimeout: time.Second + time.Second, // 等待连接池超时时间 Default is ReadTimeout + 1 second.
MinIdleConns: 0, // 最小空闲连接数 预热可能用到
MaxIdleConns: 0, // 最大空闲连接数 0不限制
MaxActiveConns: 0, // 0 不限制, 如果不限制, 超出pool size可以继续新建立连接, 但使用完之后不会放回连接池
ConnMaxIdleTime: time.Minute * 10, // 连接空闲时间
ContextTimeoutEnabled: true, // context控制超时用到
}
}
func (c *Client) parseTime(v string) (time.Duration, error) {
if v == "" {
return 0, nil
}
d, err := time.ParseDuration(v)
if err != nil {
return 0, fmt.Errorf("parse time %s err: %s", v, err)
}
return d, nil
}
func (c *Client) parseConfToRedisOpt(cf *Conf) error {
c.redisOpt.Addr = cf.Addr
c.redisOpt.Password = cf.Password
c.redisOpt.DB = cf.Db
if v := cf.PoolSize; v > 0 {
c.redisOpt.PoolSize = v
}
if v := cf.MinIdleConn; v > 0 {
c.redisOpt.MinIdleConns = v
}
if v := cf.MaxIdleConn; v > 0 {
c.redisOpt.MaxIdleConns = v
}
if v := cf.MaxActiveConn; v > 0 {
c.redisOpt.MaxActiveConns = v
}
if v := cf.MaxRetries; v > 0 {
c.redisOpt.MaxRetries = v
}
if v, err := c.parseTime(cf.DialTimeout); err != nil {
return err
} else if v > 0 {
c.redisOpt.DialTimeout = v
}
if v, err := c.parseTime(cf.ReadTimeout); err != nil {
return err
} else if v > 0 {
c.redisOpt.ReadTimeout = v
}
if v, err := c.parseTime(cf.WriteTimeout); err != nil {
return err
} else if v > 0 {
c.redisOpt.WriteTimeout = v
}
if v, err := c.parseTime(cf.ConnMaxIdleTime); err != nil {
return err
} else if v > 0 {
c.redisOpt.ConnMaxIdleTime = v
}
if v, err := c.parseTime(cf.PoolTimeout); err != nil {
return err
} else if v > 0 {
c.redisOpt.PoolTimeout = v
}
return nil
}
func New(config *Config) (*MyRedis, error) {
var (
maxConnAge, _ = time.ParseDuration(DefaultConfig.MaxConnAge)
idleTimeout, _ = time.ParseDuration(DefaultConfig.IdleTimeout)
)
const luaSetOnce = `if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end`
if config.PoolSize <= 0 {
config.MinIdleConn = DefaultConfig.PoolSize
// SetOnce 设置一次并设置过期时间, key不存在则设置成功返回1, key已存在返回0
func (c *Client) SetOnce(ctx context.Context, key, value string, t time.Duration) (int, error) {
if t < time.Second {
return 0, fmt.Errorf("time must >= 1s")
}
return c.Client.Eval(ctx, luaSetOnce, []string{key}, value, t).Int()
}
if config.MinIdleConn <= 0 {
config.MinIdleConn = DefaultConfig.MinIdleConn
}
if config.MaxConnAge != "" {
t, err := time.ParseDuration(config.MaxConnAge)
if err != nil {
return nil, fmt.Errorf("parse MaxConnAge err: %s\n", err)
}
maxConnAge = t
}
if config.IdleTimeout != "" {
t, err := time.ParseDuration(config.IdleTimeout)
if err != nil {
return nil, fmt.Errorf("parse IdleTimeout err: %s\n", err)
}
idleTimeout = t
}
client := redis.NewClient(&redis.Options{
Addr: config.Addr,
Password: config.Password,
DB: config.DB,
PoolSize: config.PoolSize,
MinIdleConns: config.MinIdleConn,
MaxConnAge: maxConnAge,
IdleTimeout: idleTimeout,
func CloseAllClient() {
instanceMap.Range(func(k, v any) bool {
v.(*Client).Close()
return true
})
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
rd := &MyRedis{}
rd.Client = client
ping := rd.Client.Ping(ctx)
if ping.Err() != nil {
return nil, fmt.Errorf("connet redis err: %s", ping.Err())
}
return rd, nil
}
// GetSimple 通用get
func (r *MyRedis) GetSimple(key string) (string, error) {
ctx := context.Background()
return r.Client.Get(ctx, key).Result()
}
// SetSimple 通用set
func (r *MyRedis) SetSimple(key string, value interface{}, t ...time.Duration) (string, error) {
ctx := context.Background()
var t2 time.Duration
if len(t) > 0 {
t2 = t[0]
}
return r.Client.Set(ctx, key, value, t2).Result()
}
// GetJson json序列化
func (r *MyRedis) GetJson(key string, result interface{}) error {
ctx := context.Background()
res, err := r.Client.Get(ctx, key).Bytes()
if err != nil {
return err
}
err = jsoniter.Unmarshal(res, &result)
if err != nil {
return fmt.Errorf("get key:%s 反序列化json失败(-2)", key)
}
return nil
}
// SetJson json序列化set
func (r *MyRedis) SetJson(key string, value interface{}, t ...time.Duration) (string, error) {
ctx := context.Background()
var t2 time.Duration
if len(t) > 0 {
t2 = t[0]
}
v, err := jsoniter.Marshal(value)
if err != nil {
return "", fmt.Errorf("set key:%s 序列化json失败", key)
}
return r.Client.Set(ctx, key, v, t2).Result()
}
func CloseDB(key string) {
DB(key).Client.Close()
}
func CloseAllDB() {
for _, v := range instanceMap {
v.Client.Close()
}
}