github.com/polarismesh/polaris@v1.17.8/common/redispool/config.go (about)

     1  /**
     2   * Tencent is pleased to support the open source community by making Polaris available.
     3   *
     4   * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
     5   *
     6   * Licensed under the BSD 3-Clause License (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at
     9   *
    10   * https://opensource.org/licenses/BSD-3-Clause
    11   *
    12   * Unless required by applicable law or agreed to in writing, software distributed
    13   * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    14   * CONDITIONS OF ANY KIND, either express or implied. See the License for the
    15   * specific language governing permissions and limitations under the License.
    16   */
    17  
    18  package redispool
    19  
    20  import (
    21  	"crypto/tls"
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"time"
    26  
    27  	"github.com/go-redis/redis/v8"
    28  	"github.com/mitchellh/mapstructure"
    29  )
    30  
    31  const (
    32  	// redisStandalone 单机模式
    33  	redisStandalone = "standalone"
    34  	// redisSentinel 哨兵模式
    35  	redisSentinel = "sentinel"
    36  	// redisCluster 集群模式
    37  	redisCluster = "cluster"
    38  )
    39  
    40  // Config redis pool configuration
    41  type Config struct {
    42  	// DeployMode is the run mode of the redis pool, support `standalone`、`cluster`、`sentinel`、or `ckv`
    43  	DeployMode string `json:"deployMode"`
    44  	// StandaloneConfig standalone-deploy-mode config
    45  	StandaloneConfig
    46  	// StandaloneConfig sentinel-deploy-mode config
    47  	SentinelConfig
    48  	// ClusterConfig cluster-deploy-mode config
    49  	ClusterConfig
    50  }
    51  
    52  // UnmarshalJSON unmarshal config from json
    53  func (c *Config) UnmarshalJSON(data []byte) error {
    54  	var configmap map[string]interface{}
    55  	if err := json.Unmarshal(data, &configmap); err != nil {
    56  		return err
    57  	}
    58  	// 需要以用户的配置为优先
    59  	raw := DefaultConfig().StandaloneConfig
    60  	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
    61  		DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
    62  		ZeroFields: false,
    63  		Result:     &raw,
    64  		TagName:    "json",
    65  	})
    66  	if err != nil {
    67  		return err
    68  	}
    69  	if err = decoder.Decode(configmap); err != nil {
    70  		return err
    71  	}
    72  	c.DeployMode, _ = configmap["deployMode"].(string)
    73  	c.StandaloneConfig = raw
    74  	switch c.DeployMode {
    75  	case redisCluster:
    76  		var clusterConfig ClusterConfig
    77  		if err = json.Unmarshal(data, &clusterConfig); err != nil {
    78  			return fmt.Errorf("unmarshal redis cluster config error: %w", err)
    79  		}
    80  		c.ClusterConfig = clusterConfig
    81  	case redisSentinel:
    82  		var sentinelConfig SentinelConfig
    83  		if err = json.Unmarshal(data, &sentinelConfig); err != nil {
    84  			return fmt.Errorf("unmarshal redis sentinel config error: %w", err)
    85  		}
    86  		c.SentinelConfig = sentinelConfig
    87  	case redisStandalone:
    88  	default:
    89  	}
    90  	return nil
    91  }
    92  
    93  // StandaloneOptions standalone-mode options
    94  func (c *Config) StandaloneOptions() *redis.Options {
    95  	redisOption := &redis.Options{
    96  		Addr:         c.KvAddr,
    97  		Username:     c.KvUser,
    98  		Password:     c.KvPasswd,
    99  		MaxRetries:   c.MaxRetries,
   100  		DialTimeout:  c.ConnectTimeout,
   101  		PoolSize:     c.PoolSize,
   102  		MinIdleConns: c.MinIdleConns,
   103  		IdleTimeout:  c.IdleTimeout,
   104  		DB:           c.DB,
   105  		ReadTimeout:  c.ReadTimeout,
   106  		WriteTimeout: c.WriteTimeout,
   107  		PoolTimeout:  c.PoolTimeout,
   108  		MaxConnAge:   c.MaxConnAge,
   109  	}
   110  
   111  	if redisOption.ReadTimeout == 0 {
   112  		redisOption.ReadTimeout = c.MsgTimeout
   113  	}
   114  
   115  	if redisOption.WriteTimeout == 0 {
   116  		redisOption.WriteTimeout = c.MsgTimeout
   117  	}
   118  
   119  	if c.MaxConnAge == 0 {
   120  		redisOption.MaxConnAge = 1800 * time.Second
   121  	}
   122  
   123  	if c.WithTLS {
   124  		redisOption.TLSConfig = c.tlsConfig
   125  		if redisOption.TLSConfig == nil {
   126  			redisOption.TLSConfig = &tls.Config{
   127  				MinVersion: tls.VersionTLS12,
   128  			}
   129  		}
   130  	}
   131  	return redisOption
   132  }
   133  
   134  // ClusterOptions cluster-mode client options
   135  func (c *Config) ClusterOptions() *redis.ClusterOptions {
   136  	standalone := c.StandaloneOptions()
   137  	return &redis.ClusterOptions{
   138  		Addrs: c.ClusterConfig.Addrs,
   139  
   140  		RouteByLatency: c.ClusterConfig.RouteByLatency,
   141  		RouteRandomly:  c.ClusterConfig.RouteRandomly,
   142  
   143  		Username:     standalone.Username,
   144  		Password:     standalone.Password,
   145  		MaxRetries:   standalone.MaxRetries,
   146  		DialTimeout:  standalone.DialTimeout,
   147  		PoolSize:     standalone.PoolSize,
   148  		MinIdleConns: standalone.MinIdleConns,
   149  		IdleTimeout:  standalone.IdleTimeout,
   150  		ReadTimeout:  standalone.ReadTimeout,
   151  		WriteTimeout: standalone.WriteTimeout,
   152  		PoolTimeout:  standalone.PoolTimeout,
   153  		MaxConnAge:   standalone.MaxConnAge,
   154  		TLSConfig:    standalone.TLSConfig,
   155  	}
   156  }
   157  
   158  // FailOverOptions sentinel-model options
   159  func (c *Config) FailOverOptions() *redis.FailoverOptions {
   160  	standalone := c.StandaloneOptions()
   161  	return &redis.FailoverOptions{
   162  		SentinelAddrs: c.SentinelConfig.Addrs,
   163  		MasterName:    c.SentinelConfig.MasterName,
   164  
   165  		SentinelUsername: c.SentinelConfig.SentinelUsername,
   166  		SentinelPassword: c.SentinelConfig.SentinelPassword,
   167  
   168  		Username:     standalone.Username,
   169  		Password:     standalone.Password,
   170  		MaxRetries:   standalone.MaxRetries,
   171  		DialTimeout:  standalone.DialTimeout,
   172  		PoolSize:     standalone.PoolSize,
   173  		MinIdleConns: standalone.MinIdleConns,
   174  		IdleTimeout:  standalone.IdleTimeout,
   175  		ReadTimeout:  standalone.ReadTimeout,
   176  		WriteTimeout: standalone.WriteTimeout,
   177  		PoolTimeout:  standalone.PoolTimeout,
   178  		MaxConnAge:   standalone.MaxConnAge,
   179  		TLSConfig:    standalone.TLSConfig,
   180  	}
   181  }
   182  
   183  // StandaloneConfig redis pool basic-configuration, also used as sentinel/cluster common config.
   184  type StandaloneConfig struct {
   185  	// KvAddr is the address of the redis server
   186  	KvAddr string `json:"kvAddr"`
   187  
   188  	// Use the specified Username to authenticate the current connection
   189  	// with one of the connections defined in the ACL list when connecting
   190  	// to a Redis 6.0 instance, or greater, that is using the Redis ACL system.
   191  	KvUser string `json:"kvUser"`
   192  
   193  	// KvPasswd for go-redis password or username (redis 6.0 version)
   194  	// Optional password. Must match the password specified in the
   195  	// requirepass server configuration option (if connecting to a Redis 5.0 instance, or lower),
   196  	// or the User Password when connecting to a Redis 6.0 instance, or greater,
   197  	// that is using the Redis ACL system.
   198  	KvPasswd string `json:"kvPasswd"`
   199  
   200  	// Minimum number of idle connections which is useful when establishing
   201  	// new connection is slow.
   202  	MinIdleConns int `json:"minIdleConns"`
   203  
   204  	// Amount of time after which client closes idle connections.
   205  	// Should be less than server's timeout.
   206  	// Default is 5 minutes. -1 disables idle timeout check.
   207  	IdleTimeout time.Duration `json:"idleTimeout"`
   208  
   209  	// ConnectTimeout for go-redis is Dial timeout for establishing new connections.
   210  	// Default is 5 seconds.
   211  	ConnectTimeout time.Duration `json:"connectTimeout"`
   212  
   213  	MsgTimeout    time.Duration `json:"msgTimeout"`
   214  	Concurrency   int           `json:"concurrency"`
   215  	Compatible    bool          `json:"compatible"`
   216  	MaxRetry      int           `json:"maxRetry"`
   217  	MinBatchCount int           `json:"minBatchCount"`
   218  	WaitTime      time.Duration `json:"waitTime"`
   219  
   220  	// MaxRetries is Maximum number of retries before giving up.
   221  	// Default is 3 retries; -1 (not 0) disables retries.
   222  	MaxRetries int `json:"maxRetries"`
   223  
   224  	// DB is Database to be selected after connecting to the server.
   225  	DB int `json:"DB"`
   226  
   227  	// ReadTimeout for socket reads. If reached, commands will fail
   228  	// with a timeout instead of blocking. Use value -1 for no timeout and 0 for default.
   229  	// Default is 3 seconds.
   230  	ReadTimeout time.Duration `json:"readTimeout"`
   231  
   232  	// WriteTimeout for socket writes. If reached, commands will fail
   233  	// with a timeout instead of blocking.
   234  	// Default is ReadTimeout.
   235  	WriteTimeout time.Duration `json:"writeTimeout"`
   236  
   237  	// Maximum number of socket connections.
   238  	// Default is 10 connections per every available CPU as reported by runtime.GOMAXPROCS.
   239  	PoolSize int `json:"poolSize" mapstructure:"poolSize"`
   240  
   241  	// Amount of time client waits for connection if all connections
   242  	// are busy before returning an error.
   243  	// Default is ReadTimeout + 1 second.
   244  	PoolTimeout time.Duration `json:"poolTimeout"`
   245  
   246  	// Connection age at which client retires (closes) the connection.
   247  	// Default is to not close aged connections.
   248  	MaxConnAge time.Duration `json:"maxConnAge"`
   249  
   250  	// WithTLS whether open TLSConfig
   251  	// if WithTLS is true, you should call WithEnableWithTLS,and then TLSConfig is not should be nil
   252  	// In this case you should call WithTLSConfig func to set tlsConfig
   253  	WithTLS bool `json:"withTLS"`
   254  
   255  	// TLS Config to use. When set TLS will be negotiated.
   256  	tlsConfig *tls.Config
   257  }
   258  
   259  // SentinelConfig sentinel pool configuration.
   260  // See github.com/go-redis/redis/v8/redis.FailoverOptions
   261  type SentinelConfig struct {
   262  	// MasterName is the name of the master instance
   263  	MasterName string `json:"masterName"`
   264  
   265  	// A seed list of host:port addresses of sentinel servers.
   266  	// Use shor name, to keep in line with ClusterConfig.Addrs
   267  	Addrs []string `json:"addrs"`
   268  
   269  	// Username ACL User and Password
   270  	SentinelUsername string `json:"sentinelUsername"`
   271  	// Password ACL User and Password
   272  	SentinelPassword string `json:"sentinelPassword"`
   273  
   274  	// Route all commands to slave read-only nodes.
   275  	SlaveOnly bool
   276  
   277  	// Use slaves disconnected with master when cannot get connected slaves
   278  	// Now, this option only works in RandomSlaveAddr function.
   279  	UseDisconnectedSlaves bool
   280  }
   281  
   282  // ClusterConfig redis cluster pool configuration
   283  // See github.com/go-redis/redis/v8/redis.ClusterOptions
   284  type ClusterConfig struct {
   285  	// A seed list of host:port addresses of cluster nodes.
   286  	Addrs []string
   287  
   288  	// Enables read-only commands on slave nodes.
   289  	ReadOnly bool
   290  
   291  	// Allows routing read-only commands to the closest master or slave node.
   292  	// It automatically enables ReadOnly.
   293  	RouteByLatency bool
   294  
   295  	// Allows routing read-only commands to the random master or slave node.
   296  	// It automatically enables ReadOnly.
   297  	RouteRandomly bool
   298  }
   299  
   300  // DefaultConfig redis pool configuration with default values
   301  func DefaultConfig() *Config {
   302  	return &Config{
   303  		StandaloneConfig: StandaloneConfig{
   304  			PoolSize:       200,
   305  			MinIdleConns:   30,
   306  			IdleTimeout:    120 * time.Second,
   307  			ConnectTimeout: 300 * time.Millisecond,
   308  			MsgTimeout:     300 * time.Millisecond,
   309  			Concurrency:    200,
   310  			Compatible:     false,
   311  			MaxRetry:       2,
   312  			MinBatchCount:  10,
   313  			WaitTime:       50 * time.Millisecond,
   314  			DB:             0,
   315  			PoolTimeout:    3 * time.Second,
   316  			MaxConnAge:     1800 * time.Second,
   317  		},
   318  	}
   319  }
   320  
   321  // Validate validate config params
   322  func (c *Config) Validate() error {
   323  	if len(c.KvAddr) == 0 {
   324  		return errors.New("kvAddr is empty")
   325  	}
   326  	// password is required only when ACL's user is given
   327  	if len(c.KvUser) > 0 && len(c.KvPasswd) == 0 {
   328  		return errors.New("kvPasswd is empty")
   329  	}
   330  	if c.MinIdleConns <= 0 {
   331  		return errors.New("minIdleConns is empty")
   332  	}
   333  	if c.PoolSize <= 0 {
   334  		return errors.New("poolSize is empty")
   335  	}
   336  	if c.IdleTimeout == 0 {
   337  		return errors.New("idleTimeout is empty")
   338  	}
   339  	if c.ConnectTimeout == 0 {
   340  		return errors.New("connectTimeout is empty")
   341  	}
   342  	if c.MsgTimeout == 0 {
   343  		return errors.New("msgTimeout is empty")
   344  	}
   345  	if c.Concurrency <= 0 {
   346  		return errors.New("concurrency is empty")
   347  	}
   348  	if c.MaxRetry < 0 {
   349  		return errors.New("maxRetry is empty")
   350  	}
   351  
   352  	if c.DeployMode == redisSentinel {
   353  		if len(c.SentinelConfig.Addrs) == 0 {
   354  			return errors.New("sentinel address list is empty")
   355  		}
   356  		if c.SentinelConfig.SentinelUsername != "" && c.SentinelConfig.SentinelPassword == "" {
   357  			return errors.New("sentinel acl username or password is empty")
   358  		}
   359  	}
   360  
   361  	if c.DeployMode == redisCluster && len(c.ClusterConfig.Addrs) == 0 {
   362  		return errors.New("cluster address list is empty")
   363  	}
   364  	return nil
   365  }