...

Source file src/github.com/letsencrypt/boulder/redis/config.go

Documentation: github.com/letsencrypt/boulder/redis

     1  package redis
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/letsencrypt/boulder/cmd"
     7  	"github.com/letsencrypt/boulder/config"
     8  	blog "github.com/letsencrypt/boulder/log"
     9  	"github.com/prometheus/client_golang/prometheus"
    10  	"github.com/redis/go-redis/v9"
    11  )
    12  
    13  // Config contains the configuration needed to act as a Redis client.
    14  type Config struct {
    15  	// TLS contains the configuration to speak TLS with Redis.
    16  	TLS cmd.TLSConfig
    17  
    18  	// Username used to authenticate to each Redis instance.
    19  	Username string `validate:"required"`
    20  
    21  	// PasswordFile is the path to a file holding the password used to
    22  	// authenticate to each Redis instance.
    23  	cmd.PasswordConfig
    24  
    25  	// ShardAddrs is a map of shard names to IP address:port pairs. The go-redis
    26  	// `Ring` client will shard reads and writes across the provided Redis
    27  	// Servers based on a consistent hashing algorithm.
    28  	ShardAddrs map[string]string `validate:"omitempty,required_without=Lookups,min=1,dive,hostname_port"`
    29  
    30  	// Lookups each entry contains a service and domain name that will be used
    31  	// to construct a SRV DNS query to lookup Redis backends. For example: if
    32  	// the resource record is 'foo.service.consul', then the 'Service' is 'foo'
    33  	// and the 'Domain' is 'service.consul'. The expected dNSName to be
    34  	// authenticated in the server certificate would be 'foo.service.consul'.
    35  	Lookups []cmd.ServiceDomain `validate:"omitempty,required_without=ShardAddrs,min=1,dive"`
    36  
    37  	// LookupFrequency is the frequency of periodic SRV lookups. Defaults to 30
    38  	// seconds.
    39  	LookupFrequency config.Duration `validate:"-"`
    40  
    41  	// LookupDNSAuthority can only be specified with Lookups. It's a single
    42  	// <hostname|IPv4|[IPv6]>:<port> of the DNS server to be used for resolution
    43  	// of Redis backends. If the address contains a hostname it will be resolved
    44  	// using system DNS. If the address contains a port, the client will use it
    45  	// directly, otherwise port 53 is used. If this field is left unspecified
    46  	// the system DNS will be used for resolution.
    47  	LookupDNSAuthority string `validate:"excluded_without=Lookups,omitempty,ip|hostname|hostname_port"`
    48  
    49  	// Enables read-only commands on replicas.
    50  	ReadOnly bool
    51  	// Allows routing read-only commands to the closest primary or replica.
    52  	// It automatically enables ReadOnly.
    53  	RouteByLatency bool
    54  	// Allows routing read-only commands to a random primary or replica.
    55  	// It automatically enables ReadOnly.
    56  	RouteRandomly bool
    57  
    58  	// PoolFIFO uses FIFO mode for each node connection pool GET/PUT (default LIFO).
    59  	PoolFIFO bool
    60  
    61  	// Maximum number of retries before giving up.
    62  	// Default is to not retry failed commands.
    63  	MaxRetries int `validate:"min=0"`
    64  	// Minimum backoff between each retry.
    65  	// Default is 8 milliseconds; -1 disables backoff.
    66  	MinRetryBackoff config.Duration `validate:"-"`
    67  	// Maximum backoff between each retry.
    68  	// Default is 512 milliseconds; -1 disables backoff.
    69  	MaxRetryBackoff config.Duration `validate:"-"`
    70  
    71  	// Dial timeout for establishing new connections.
    72  	// Default is 5 seconds.
    73  	DialTimeout config.Duration `validate:"-"`
    74  	// Timeout for socket reads. If reached, commands will fail
    75  	// with a timeout instead of blocking. Use value -1 for no timeout and 0 for default.
    76  	// Default is 3 seconds.
    77  	ReadTimeout config.Duration `validate:"-"`
    78  	// Timeout for socket writes. If reached, commands will fail
    79  	// with a timeout instead of blocking.
    80  	// Default is ReadTimeout.
    81  	WriteTimeout config.Duration `validate:"-"`
    82  
    83  	// Maximum number of socket connections.
    84  	// Default is 5 connections per every CPU as reported by runtime.NumCPU.
    85  	// If this is set to an explicit value, that's not multiplied by NumCPU.
    86  	// PoolSize applies per cluster node and not for the whole cluster.
    87  	// https://pkg.go.dev/github.com/go-redis/redis#ClusterOptions
    88  	PoolSize int `validate:"min=0"`
    89  	// Minimum number of idle connections which is useful when establishing
    90  	// new connection is slow.
    91  	MinIdleConns int `validate:"min=0"`
    92  	// Connection age at which client retires (closes) the connection.
    93  	// Default is to not close aged connections.
    94  	MaxConnAge config.Duration `validate:"-"`
    95  	// Amount of time client waits for connection if all connections
    96  	// are busy before returning an error.
    97  	// Default is ReadTimeout + 1 second.
    98  	PoolTimeout config.Duration `validate:"-"`
    99  	// Amount of time after which client closes idle connections.
   100  	// Should be less than server's timeout.
   101  	// Default is 5 minutes. -1 disables idle timeout check.
   102  	IdleTimeout config.Duration `validate:"-"`
   103  	// Frequency of idle checks made by idle connections reaper.
   104  	// Default is 1 minute. -1 disables idle connections reaper,
   105  	// but idle connections are still discarded by the client
   106  	// if IdleTimeout is set.
   107  	// Deprecated: This field has been deprecated and will be removed.
   108  	IdleCheckFrequency config.Duration `validate:"-"`
   109  }
   110  
   111  // Ring is a wrapper around the go-redis/v9 Ring client that adds support for
   112  // (optional) periodic SRV lookups.
   113  type Ring struct {
   114  	*redis.Ring
   115  	lookup *lookup
   116  }
   117  
   118  // NewRingFromConfig returns a new *redis.Ring client. If periodic SRV lookups
   119  // are supplied, a goroutine will be started to periodically perform lookups.
   120  // Callers should defer a call to StopLookups() to ensure that this goroutine is
   121  // gracefully shutdown.
   122  func NewRingFromConfig(c Config, stats prometheus.Registerer, log blog.Logger) (*Ring, error) {
   123  	password, err := c.Pass()
   124  	if err != nil {
   125  		return nil, fmt.Errorf("loading password: %w", err)
   126  	}
   127  
   128  	tlsConfig, err := c.TLS.Load(stats)
   129  	if err != nil {
   130  		return nil, fmt.Errorf("loading TLS config: %w", err)
   131  	}
   132  
   133  	inner := redis.NewRing(&redis.RingOptions{
   134  		Addrs:     c.ShardAddrs,
   135  		Username:  c.Username,
   136  		Password:  password,
   137  		TLSConfig: tlsConfig,
   138  
   139  		MaxRetries:      c.MaxRetries,
   140  		MinRetryBackoff: c.MinRetryBackoff.Duration,
   141  		MaxRetryBackoff: c.MaxRetryBackoff.Duration,
   142  		DialTimeout:     c.DialTimeout.Duration,
   143  		ReadTimeout:     c.ReadTimeout.Duration,
   144  		WriteTimeout:    c.WriteTimeout.Duration,
   145  
   146  		PoolSize:        c.PoolSize,
   147  		MinIdleConns:    c.MinIdleConns,
   148  		ConnMaxLifetime: c.MaxConnAge.Duration,
   149  		PoolTimeout:     c.PoolTimeout.Duration,
   150  		ConnMaxIdleTime: c.IdleTimeout.Duration,
   151  	})
   152  	if len(c.ShardAddrs) > 0 {
   153  		// Client was statically configured with a list of shards.
   154  		MustRegisterClientMetricsCollector(inner, stats, c.ShardAddrs, c.Username)
   155  	}
   156  
   157  	var lookup *lookup
   158  	if len(c.Lookups) != 0 {
   159  		lookup, err = newLookup(c.Lookups, c.LookupDNSAuthority, c.LookupFrequency.Duration, inner, log, stats)
   160  		if err != nil {
   161  			return nil, err
   162  		}
   163  		lookup.start()
   164  	}
   165  
   166  	return &Ring{
   167  		Ring:   inner,
   168  		lookup: lookup,
   169  	}, nil
   170  }
   171  
   172  // StopLookups stops the goroutine responsible for keeping the shards of the
   173  // inner *redis.Ring up-to-date. It is a no-op if the Ring was not constructed
   174  // with periodic lookups or if the lookups have already been stopped.
   175  func (r *Ring) StopLookups() {
   176  	if r == nil || r.lookup == nil {
   177  		// No-op.
   178  		return
   179  	}
   180  	r.lookup.stop()
   181  }
   182  

View as plain text