tracker/storage/redis/redis.go
2022-01-18 22:00:57 -05:00

136 lines
3 KiB
Go

package redis
import (
"errors"
"net/url"
"strconv"
"strings"
"time"
"github.com/go-redsync/redsync/v4"
"github.com/go-redsync/redsync/v4/redis/redigo"
redigolib "github.com/gomodule/redigo/redis"
)
// redisBackend represents a redis handler.
type redisBackend struct {
pool *redigolib.Pool
redsync *redsync.Redsync
}
// newRedisBackend creates a redisBackend instance.
func newRedisBackend(cfg *Config, u *redisURL, socketPath string) *redisBackend {
rc := &redisConnector{
URL: u,
SocketPath: socketPath,
ReadTimeout: cfg.RedisReadTimeout,
WriteTimeout: cfg.RedisWriteTimeout,
ConnectTimeout: cfg.RedisConnectTimeout,
}
pool := rc.NewPool()
redsync := redsync.New(redigo.NewPool(pool))
return &redisBackend{
pool: pool,
redsync: redsync,
}
}
// open returns or creates instance of Redis connection.
func (rb *redisBackend) open() redigolib.Conn {
return rb.pool.Get()
}
type redisConnector struct {
URL *redisURL
SocketPath string
ReadTimeout time.Duration
WriteTimeout time.Duration
ConnectTimeout time.Duration
}
// NewPool returns a new pool of Redis connections
func (rc *redisConnector) NewPool() *redigolib.Pool {
return &redigolib.Pool{
MaxIdle: 3,
IdleTimeout: 240 * time.Second,
Dial: func() (redigolib.Conn, error) {
c, err := rc.open()
if err != nil {
return nil, err
}
if rc.URL.DB != 0 {
_, err = c.Do("SELECT", rc.URL.DB)
if err != nil {
return nil, err
}
}
return c, err
},
// PINGs connections that have been idle more than 10 seconds
TestOnBorrow: func(c redigolib.Conn, t time.Time) error {
if time.Since(t) < 10*time.Second {
return nil
}
_, err := c.Do("PING")
return err
},
}
}
// Open a new Redis connection
func (rc *redisConnector) open() (redigolib.Conn, error) {
opts := []redigolib.DialOption{
redigolib.DialDatabase(rc.URL.DB),
redigolib.DialReadTimeout(rc.ReadTimeout),
redigolib.DialWriteTimeout(rc.WriteTimeout),
redigolib.DialConnectTimeout(rc.ConnectTimeout),
}
if rc.URL.Password != "" {
opts = append(opts, redigolib.DialPassword(rc.URL.Password))
}
if rc.SocketPath != "" {
return redigolib.Dial("unix", rc.SocketPath, opts...)
}
return redigolib.Dial("tcp", rc.URL.Host, opts...)
}
// A redisURL represents a parsed redisURL
// The general form represented is:
//
// redis://[password@]host][/][db]
type redisURL struct {
Host string
Password string
DB int
}
// parseRedisURL parse rawurl into redisURL
func parseRedisURL(target string) (*redisURL, error) {
var u *url.URL
u, err := url.Parse(target)
if err != nil {
return nil, err
}
if u.Scheme != "redis" {
return nil, errors.New("no redis scheme found")
}
db := 0 // default redis db
parts := strings.Split(u.Path, "/")
if len(parts) != 1 {
db, err = strconv.Atoi(parts[1])
if err != nil {
return nil, err
}
}
return &redisURL{
Host: u.Host,
Password: u.User.String(),
DB: db,
}, nil
}