redis/options.go

302 lines
7.8 KiB
Go
Raw Normal View History

package redis
import (
2019-06-04 14:05:29 +03:00
"context"
"crypto/tls"
"errors"
2017-01-26 17:34:09 +03:00
"fmt"
"net"
"net/url"
"runtime"
"strconv"
"strings"
"time"
2020-03-11 17:29:16 +03:00
"github.com/go-redis/redis/v8/internal/pool"
)
2018-10-14 10:53:48 +03:00
// Limiter is the interface of a rate limiter or a circuit breaker.
type Limiter interface {
2019-06-04 13:30:47 +03:00
// Allow returns nil if operation is allowed or an error otherwise.
// If operation is allowed client must ReportResult of the operation
// whether it is a success or a failure.
2018-10-14 10:53:48 +03:00
Allow() error
2020-02-02 12:09:27 +03:00
// ReportResult reports the result of the previously allowed operation.
// nil indicates a success, non-nil error usually indicates a failure.
2018-10-14 10:53:48 +03:00
ReportResult(result error)
}
// Options keeps the settings to setup redis connection.
type Options struct {
// The network type, either tcp or unix.
// Default is tcp.
Network string
// host:port address.
Addr string
// Dialer creates new network connection and has priority over
// Network and Addr options.
2019-06-04 14:05:29 +03:00
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
2017-05-25 14:16:39 +03:00
// Hook that is called when new connection is established.
2020-06-10 10:36:22 +03:00
OnConnect func(ctx context.Context, cn *Conn) error
2017-05-25 14:16:39 +03:00
2020-06-12 09:28:14 +03:00
// Use the specified Username to authenticate the current connection
// with one of the connections defined in the ACL list when connecting
// to a Redis 6.0 instance, or greater, that is using the Redis ACL system.
2020-05-21 08:59:20 +03:00
Username string
2016-08-09 16:32:08 +03:00
// Optional password. Must match the password specified in the
2020-05-21 08:59:20 +03:00
// requirepass server configuration option (if connecting to a Redis 5.0 instance, or lower),
2020-06-12 09:28:14 +03:00
// or the User Password when connecting to a Redis 6.0 instance, or greater,
// that is using the Redis ACL system.
Password string
2020-06-12 09:28:14 +03:00
2016-08-09 16:32:08 +03:00
// Database to be selected after connecting to the server.
DB int
2016-08-09 16:32:08 +03:00
// Maximum number of retries before giving up.
2020-12-29 01:47:46 +03:00
// Default is 3 retries; -1 (not 0) disables retries.
MaxRetries int
2017-07-09 13:10:07 +03:00
// Minimum backoff between each retry.
// Default is 8 milliseconds; -1 disables backoff.
MinRetryBackoff time.Duration
2017-06-01 17:49:27 +03:00
// Maximum backoff between each retry.
2017-07-09 13:10:07 +03:00
// Default is 512 milliseconds; -1 disables backoff.
2017-05-25 08:08:44 +03:00
MaxRetryBackoff time.Duration
2016-08-09 16:32:08 +03:00
// Dial timeout for establishing new connections.
2016-03-14 17:51:46 +03:00
// Default is 5 seconds.
DialTimeout time.Duration
2016-08-09 16:32:08 +03:00
// Timeout for socket reads. If reached, commands will fail
// with a timeout instead of blocking. Use value -1 for no timeout and 0 for default.
// Default is 3 seconds.
ReadTimeout time.Duration
2016-08-09 16:32:08 +03:00
// Timeout for socket writes. If reached, commands will fail
// with a timeout instead of blocking.
2017-04-17 14:29:41 +03:00
// Default is ReadTimeout.
WriteTimeout time.Duration
2016-08-09 16:32:08 +03:00
// Maximum number of socket connections.
// Default is 10 connections per every CPU as reported by runtime.NumCPU.
PoolSize int
2018-05-28 17:27:24 +03:00
// Minimum number of idle connections which is useful when establishing
// new connection is slow.
MinIdleConns int
2018-08-12 10:08:21 +03:00
// Connection age at which client retires (closes) the connection.
// Default is to not close aged connections.
MaxConnAge time.Duration
2016-08-09 16:32:08 +03:00
// Amount of time client waits for connection if all connections
// are busy before returning an error.
// Default is ReadTimeout + 1 second.
PoolTimeout time.Duration
2016-08-09 16:32:08 +03:00
// Amount of time after which client closes idle connections.
// Should be less than server's timeout.
2018-06-11 13:26:24 +03:00
// Default is 5 minutes. -1 disables idle timeout check.
IdleTimeout time.Duration
2018-06-11 13:26:24 +03:00
// Frequency of idle checks made by idle connections reaper.
// Default is 1 minute. -1 disables idle connections reaper,
2018-05-28 17:27:24 +03:00
// but idle connections are still discarded by the client
// if IdleTimeout is set.
2016-03-17 19:00:47 +03:00
IdleCheckFrequency time.Duration
2016-08-09 16:32:08 +03:00
// Enables read only queries on slave nodes.
readOnly bool
2016-11-15 21:27:20 +03:00
// TLS Config to use. When set TLS will be negotiated.
TLSConfig *tls.Config
2020-02-02 12:09:27 +03:00
// Limiter interface used to implemented circuit breaker or rate limiter.
Limiter Limiter
}
2016-06-05 14:10:30 +03:00
func (opt *Options) init() {
2019-01-13 11:36:38 +03:00
if opt.Addr == "" {
opt.Addr = "localhost:6379"
}
if opt.Network == "" {
if strings.HasPrefix(opt.Addr, "/") {
opt.Network = "unix"
} else {
opt.Network = "tcp"
}
}
if opt.DialTimeout == 0 {
opt.DialTimeout = 5 * time.Second
}
2016-06-05 14:10:30 +03:00
if opt.Dialer == nil {
2019-06-04 14:05:29 +03:00
opt.Dialer = func(ctx context.Context, network, addr string) (net.Conn, error) {
2018-07-24 09:57:02 +03:00
netDialer := &net.Dialer{
Timeout: opt.DialTimeout,
KeepAlive: 5 * time.Minute,
}
if opt.TLSConfig == nil {
return netDialer.DialContext(ctx, network, addr)
2016-11-15 21:27:20 +03:00
}
2019-08-23 14:46:40 +03:00
return tls.DialWithDialer(netDialer, network, addr, opt.TLSConfig)
2016-06-05 14:10:30 +03:00
}
}
if opt.PoolSize == 0 {
opt.PoolSize = 10 * runtime.NumCPU()
}
2017-05-25 08:08:44 +03:00
switch opt.ReadTimeout {
case -1:
opt.ReadTimeout = 0
2017-05-25 08:08:44 +03:00
case 0:
opt.ReadTimeout = 3 * time.Second
}
2017-05-25 08:08:44 +03:00
switch opt.WriteTimeout {
case -1:
opt.WriteTimeout = 0
2017-05-25 08:08:44 +03:00
case 0:
opt.WriteTimeout = opt.ReadTimeout
}
if opt.PoolTimeout == 0 {
opt.PoolTimeout = opt.ReadTimeout + time.Second
}
if opt.IdleTimeout == 0 {
opt.IdleTimeout = 5 * time.Minute
}
2016-03-17 19:00:47 +03:00
if opt.IdleCheckFrequency == 0 {
2016-06-05 14:10:30 +03:00
opt.IdleCheckFrequency = time.Minute
2016-03-17 19:00:47 +03:00
}
2017-07-09 13:10:07 +03:00
if opt.MaxRetries == -1 {
opt.MaxRetries = 0
2020-09-11 11:24:38 +03:00
} else if opt.MaxRetries == 0 {
opt.MaxRetries = 3
}
2017-07-09 13:10:07 +03:00
switch opt.MinRetryBackoff {
case -1:
opt.MinRetryBackoff = 0
case 0:
opt.MinRetryBackoff = 8 * time.Millisecond
}
2017-05-25 08:08:44 +03:00
switch opt.MaxRetryBackoff {
case -1:
opt.MaxRetryBackoff = 0
case 0:
opt.MaxRetryBackoff = 512 * time.Millisecond
}
2016-03-17 19:00:47 +03:00
}
2020-02-02 15:59:27 +03:00
func (opt *Options) clone() *Options {
clone := *opt
return &clone
}
2017-06-09 13:55:45 +03:00
// ParseURL parses an URL into Options that can be used to connect to Redis.
// Scheme is required.
// There are two connection types: by tcp socket and by unix socket.
// Tcp connection:
// redis://<user>:<password>@<host>:<port>/<db_number>
// Unix connection:
// unix://<user>:<password>@</path/to/redis.sock>?db=<db_number>
func ParseURL(redisURL string) (*Options, error) {
u, err := url.Parse(redisURL)
if err != nil {
return nil, err
}
switch u.Scheme {
case "redis", "rediss":
return setupTCPConn(u)
case "unix":
return setupUnixConn(u)
default:
2020-09-28 15:29:35 +03:00
return nil, fmt.Errorf("redis: invalid URL scheme: %s", u.Scheme)
}
}
func setupTCPConn(u *url.URL) (*Options, error) {
o := &Options{Network: "tcp"}
o.Username, o.Password = getUserPassword(u)
if len(u.Query()) > 0 {
2020-09-28 15:29:35 +03:00
return nil, errors.New("redis: no options supported")
}
h, p, err := net.SplitHostPort(u.Host)
if err != nil {
h = u.Host
}
if h == "" {
h = "localhost"
}
if p == "" {
p = "6379"
}
o.Addr = net.JoinHostPort(h, p)
f := strings.FieldsFunc(u.Path, func(r rune) bool {
return r == '/'
})
switch len(f) {
case 0:
o.DB = 0
case 1:
if o.DB, err = strconv.Atoi(f[0]); err != nil {
2020-09-28 15:29:35 +03:00
return nil, fmt.Errorf("redis: invalid database number: %q", f[0])
}
default:
2020-09-28 15:29:35 +03:00
return nil, fmt.Errorf("redis: invalid URL path: %s", u.Path)
}
if u.Scheme == "rediss" {
o.TLSConfig = &tls.Config{ServerName: h}
}
return o, nil
}
func setupUnixConn(u *url.URL) (*Options, error) {
o := &Options{
Network: "unix",
}
if strings.TrimSpace(u.Path) == "" { // path is required with unix connection
2020-09-28 15:29:35 +03:00
return nil, errors.New("redis: empty unix socket path")
}
o.Addr = u.Path
o.Username, o.Password = getUserPassword(u)
dbStr := u.Query().Get("db")
if dbStr == "" {
return o, nil // if database is not set, connect to 0 db.
}
2020-09-28 15:29:35 +03:00
db, err := strconv.Atoi(dbStr)
if err != nil {
2020-11-17 17:08:15 +03:00
return nil, fmt.Errorf("redis: invalid database number: %w", err)
}
o.DB = db
return o, nil
}
func getUserPassword(u *url.URL) (string, string) {
var user, password string
if u.User != nil {
user = u.User.Username()
if p, ok := u.User.Password(); ok {
password = p
}
}
return user, password
}
func newConnPool(opt *Options) *pool.ConnPool {
return pool.NewConnPool(&pool.Options{
2019-08-18 17:03:32 +03:00
Dialer: func(ctx context.Context) (net.Conn, error) {
2021-03-19 14:47:44 +03:00
return opt.Dialer(ctx, opt.Network, opt.Addr)
2019-05-18 14:00:07 +03:00
},
PoolSize: opt.PoolSize,
2018-05-28 17:27:24 +03:00
MinIdleConns: opt.MinIdleConns,
2018-08-12 10:08:21 +03:00
MaxConnAge: opt.MaxConnAge,
PoolTimeout: opt.PoolTimeout,
IdleTimeout: opt.IdleTimeout,
IdleCheckFrequency: opt.IdleCheckFrequency,
})
}