More robust and straight forward pool abstraction

This commit is contained in:
Pavel Shevaev 2022-10-28 14:03:40 +03:00
parent 737a24befb
commit 33b922f181
2 changed files with 70 additions and 56 deletions

106
rdb.go
View File

@ -4,13 +4,13 @@ import (
"fmt" "fmt"
"math" "math"
"strconv" "strconv"
"sync"
"time" "time"
"git.bit5.ru/backend/colog" "git.bit5.ru/backend/colog"
"git.bit5.ru/backend/redigo/redis" "git.bit5.ru/backend/redigo/redis"
"git.bit5.ru/backend/redsync" "git.bit5.ru/backend/redsync"
"git.bit5.ru/backend/res_tracker" "git.bit5.ru/backend/res_tracker"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -18,25 +18,53 @@ const (
ExpireRDMutexSec = 30 ExpireRDMutexSec = 30
) )
var pools sync.Map type Settings struct {
type RdSettings struct {
Host, Prefix string Host, Prefix string
Port int //will be shown in CLIENT LIST
Db int ClientName string
LogLevel int Port int
Db int
LogLevel int
MaxIdle int
IdleTimeoutSec int
} }
func (s *RdSettings) ConnStr() string { func (s *Settings) ConnStr() string {
return s.Host + ":" + strconv.Itoa(s.Port) return s.Host + ":" + strconv.Itoa(s.Port)
} }
type Pool struct {
S Settings
RP *redis.Pool
}
func OpenPool(s Settings, logger *colog.CoLog) *Pool {
p := &Pool{S: s, RP: newRedisPool(s, logger)}
return p
}
func (p *Pool) Close() {
p.RP.Close()
}
func (p *Pool) Get() redis.Conn {
conn := p.RP.Get()
if res_tracker.IsOn() {
//let's wrap our connection with the special tracked wrapper
conn = &tracked{conn}
res_tracker.Track(conn)
}
return conn
}
// NOTE: redis connection logs all operations // NOTE: redis connection logs all operations
type rdb struct { type rdb struct {
orig redis.Conn orig redis.Conn
name string name string
logger *colog.CoLog logger *colog.CoLog
s RdSettings s Settings
} }
func (rd *rdb) Close() error { func (rd *rdb) Close() error {
@ -93,28 +121,22 @@ func (rd *rdb) Receive() (interface{}, error) {
return rd.orig.Receive() return rd.orig.Receive()
} }
func redisPool(logger *colog.CoLog, s RdSettings, clientName string) *redis.Pool { func newRedisPool(s Settings, logger *colog.CoLog) *redis.Pool {
//TODO: why not simply using s as a key for pools.Load(..)?
key := s.Host + ":" + strconv.Itoa(s.Port) + ":" + strconv.Itoa(s.Db) + ":" + s.Prefix maxIdle := s.MaxIdle
v, ok := pools.Load(key) if maxIdle == 0 {
if ok { maxIdle = 16
return v.(*redis.Pool)
} else {
rdpool := NewRedisPool(logger, s, clientName)
pools.Store(key, rdpool)
return rdpool
} }
}
func NewRedisPool(logger *colog.CoLog, s RdSettings, clientName string) *redis.Pool { idleTimeoutSec := s.IdleTimeoutSec
return NewRedisPoolEx(logger, s, 16, 240*time.Second, clientName) if idleTimeoutSec == 0 {
} idleTimeoutSec = 240
}
func NewRedisPoolEx(logger *colog.CoLog, s RdSettings, maxIdle int, idleTimeout time.Duration, clientName string) *redis.Pool {
return &redis.Pool{ return &redis.Pool{
Wait: false, Wait: false,
MaxIdle: maxIdle, MaxIdle: maxIdle,
IdleTimeout: idleTimeout, IdleTimeout: time.Second * time.Duration(idleTimeoutSec),
Dial: func() (redis.Conn, error) { Dial: func() (redis.Conn, error) {
orig, err := redis.Dial("tcp", s.ConnStr()) orig, err := redis.Dial("tcp", s.ConnStr())
if err != nil { if err != nil {
@ -129,8 +151,8 @@ func NewRedisPoolEx(logger *colog.CoLog, s RdSettings, maxIdle int, idleTimeout
// Сохраняем название клиента для диагностики соединений с помощью команды CLIENT LIST. // Сохраняем название клиента для диагностики соединений с помощью команды CLIENT LIST.
// https://redis.io/commands/client-list/ // https://redis.io/commands/client-list/
if len(clientName) > 0 { if len(s.ClientName) > 0 {
_, err = c.Do("CLIENT", "SETNAME", clientName) _, err = c.Do("CLIENT", "SETNAME", s.ClientName)
if err != nil { if err != nil {
return nil, errors.WithStack(err) return nil, errors.WithStack(err)
} }
@ -150,37 +172,27 @@ func NewRedisPoolEx(logger *colog.CoLog, s RdSettings, maxIdle int, idleTimeout
} }
} }
func GetRedisConn(logger *colog.CoLog, s RdSettings, clientName string) redis.Conn { func GetRedisMutex(pool *Pool, name string, ttlSec int) *redsync.Mutex {
conn := redisPool(logger, s, clientName).Get() mx, _ := redsync.NewMutexWithPool(name, []*redis.Pool{pool.RP})
if res_tracker.IsOn() {
//let's wrap our connection with the special tracked wrapper
conn = tracked{conn}
res_tracker.Track(conn)
}
return conn
}
func GetRedisMutex(logger *colog.CoLog, s RdSettings, name string, ttlSec int) *redsync.Mutex {
mx, _ := redsync.NewMutexWithPool(name, []*redis.Pool{redisPool(logger, s, "")})
mx.Expiry = time.Duration(ttlSec) * time.Second mx.Expiry = time.Duration(ttlSec) * time.Second
return mx return mx
} }
func GetRedisMutexAutoTTL(logger *colog.CoLog, s RdSettings, name string) *redsync.Mutex { func GetRedisMutexAutoTTL(pool *Pool, name string) *redsync.Mutex {
return GetRedisMutex(logger, s, name, ExpireRDMutexSec) return GetRedisMutex(pool, name, ExpireRDMutexSec)
} }
func RemoveRedisMutex(logger *colog.CoLog, s RdSettings, name string) error { func RemoveRedisMutex(pool *Pool, name string) error {
rd := redisPool(logger, s, "").Get() rd := pool.Get()
defer rd.Close()
_, err := rd.Do("DEL", name) _, err := rd.Do("DEL", name)
return err return err
} }
func ExistsRedisMutex(logger *colog.CoLog, s RdSettings, name string) (bool, error) { func ExistsRedisMutex(pool *Pool, name string) (bool, error) {
rd := redisPool(logger, s, "").Get() rd := pool.Get()
defer rd.Close()
exists, err := redis.Bool(rd.Do("EXISTS", name)) exists, err := redis.Bool(rd.Do("EXISTS", name))
if err != nil { if err != nil {

View File

@ -6,12 +6,11 @@ import (
"git.bit5.ru/backend/colog" "git.bit5.ru/backend/colog"
"git.bit5.ru/backend/rdb" "git.bit5.ru/backend/rdb"
"git.bit5.ru/backend/redigo/redis"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func getPool() *redis.Pool { func getPool() *rdb.Pool {
pool := rdb.NewRedisPool(getLogger(), rdb.RdSettings{Host: "localhost", Port: 6379, Db: 10}, "test") pool := rdb.OpenPool(rdb.Settings{Host: "localhost", Port: 6379, Db: 10, ClientName: "test"}, getLogger())
return pool return pool
} }
@ -24,7 +23,7 @@ func getLogger() *colog.CoLog {
} }
func TestBadConnection(t *testing.T) { func TestBadConnection(t *testing.T) {
pool := rdb.NewRedisPool(getLogger(), rdb.RdSettings{Host: "dummy", Port: 80}, "test") pool := rdb.OpenPool(rdb.Settings{Host: "dummy", Port: 80, ClientName: "test"}, getLogger())
conn := pool.Get() conn := pool.Get()
assert.NotNil(t, conn) assert.NotNil(t, conn)
_, err := conn.Do("PING") _, err := conn.Do("PING")
@ -33,6 +32,7 @@ func TestBadConnection(t *testing.T) {
func TestGetConn(t *testing.T) { func TestGetConn(t *testing.T) {
pool := getPool() pool := getPool()
defer pool.Close()
conn := pool.Get() conn := pool.Get()
defer conn.Close() defer conn.Close()
@ -42,9 +42,10 @@ func TestGetConn(t *testing.T) {
func TestCloseConn(t *testing.T) { func TestCloseConn(t *testing.T) {
pool := getPool() pool := getPool()
defer pool.Close()
conn := pool.Get() conn := pool.Get()
assert.EqualValues(t, 0, pool.IdleCount()) assert.EqualValues(t, 0, pool.RP.IdleCount())
_, err := conn.Do("PING") _, err := conn.Do("PING")
assert.Nil(t, err) assert.Nil(t, err)
@ -52,19 +53,20 @@ func TestCloseConn(t *testing.T) {
conn.Close() conn.Close()
_, err = conn.Do("PING") _, err = conn.Do("PING")
assert.NotNil(t, err) assert.NotNil(t, err)
assert.EqualValues(t, 1, pool.IdleCount()) assert.EqualValues(t, 1, pool.RP.IdleCount())
} }
func TestDoubleCloseConnIsOk(t *testing.T) { func TestDoubleCloseConnIsOk(t *testing.T) {
pool := getPool() pool := getPool()
defer pool.Close()
conn := pool.Get() conn := pool.Get()
_, err := conn.Do("PING") _, err := conn.Do("PING")
assert.Nil(t, err) assert.Nil(t, err)
assert.EqualValues(t, 0, pool.IdleCount()) assert.EqualValues(t, 0, pool.RP.IdleCount())
conn.Close() conn.Close()
assert.EqualValues(t, 1, pool.IdleCount()) assert.EqualValues(t, 1, pool.RP.IdleCount())
conn.Close() conn.Close()
assert.EqualValues(t, 1, pool.IdleCount()) assert.EqualValues(t, 1, pool.RP.IdleCount())
} }