github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/config/server_config.go (about)

     1  // Copyright 2020 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package config
    15  
    16  import (
    17  	"encoding/json"
    18  	"fmt"
    19  	"math"
    20  	"net"
    21  	"regexp"
    22  	"strings"
    23  	"sync/atomic"
    24  	"time"
    25  
    26  	"github.com/pingcap/errors"
    27  	"github.com/pingcap/log"
    28  	cerror "github.com/pingcap/tiflow/pkg/errors"
    29  	"github.com/pingcap/tiflow/pkg/security"
    30  	"go.uber.org/zap"
    31  )
    32  
    33  const (
    34  	// clusterIDMaxLen is the max length of cdc server cluster id
    35  	clusterIDMaxLen = 128
    36  	// DefaultSortDir is the default value of sort-dir, it will be a subordinate directory of data-dir.
    37  	DefaultSortDir = "/tmp/sorter"
    38  
    39  	// DefaultRedoDir is a subordinate directory path of data-dir.
    40  	DefaultRedoDir = "/tmp/redo"
    41  
    42  	// DebugConfigurationItem is the name of debug configurations
    43  	DebugConfigurationItem = "debug"
    44  
    45  	// DefaultChangefeedMemoryQuota is the default memory quota for each changefeed.
    46  	DefaultChangefeedMemoryQuota = 1024 * 1024 * 1024 // 1GB.
    47  
    48  	// DisableMemoryLimit is the default max memory percentage for TiCDC server.
    49  	// 0 means no memory limit.
    50  	DisableMemoryLimit = 0
    51  
    52  	// EnablePDForwarding is the value of whether to enable PD client forwarding function.
    53  	// The PD client will forward the requests throughthe follower
    54  	// If there is a network partition problem between TiCDC and PD leader.
    55  	EnablePDForwarding = true
    56  )
    57  
    58  var (
    59  	clusterIDRe = regexp.MustCompile(`^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$`)
    60  
    61  	// ReservedClusterIDs contains a list of reserved cluster id,
    62  	// these words are the part of old cdc etcd key prefix
    63  	// like: /tidb/cdc/owner
    64  	ReservedClusterIDs = []string{
    65  		"owner", "capture", "task",
    66  		"changefeed", "job", "meta",
    67  	}
    68  )
    69  
    70  func init() {
    71  	StoreGlobalServerConfig(GetDefaultServerConfig())
    72  }
    73  
    74  // LogFileConfig represents log file config for server
    75  type LogFileConfig struct {
    76  	MaxSize    int `toml:"max-size" json:"max-size"`
    77  	MaxDays    int `toml:"max-days" json:"max-days"`
    78  	MaxBackups int `toml:"max-backups" json:"max-backups"`
    79  }
    80  
    81  // LogConfig represents log config for server
    82  type LogConfig struct {
    83  	File              *LogFileConfig `toml:"file" json:"file"`
    84  	InternalErrOutput string         `toml:"error-output" json:"error-output"`
    85  }
    86  
    87  var defaultServerConfig = &ServerConfig{
    88  	Addr:          "127.0.0.1:8300",
    89  	AdvertiseAddr: "",
    90  	LogFile:       "",
    91  	LogLevel:      "info",
    92  	Log: &LogConfig{
    93  		File: &LogFileConfig{
    94  			MaxSize:    300,
    95  			MaxDays:    0,
    96  			MaxBackups: 0,
    97  		},
    98  		InternalErrOutput: "stderr",
    99  	},
   100  	DataDir: "",
   101  	GcTTL:   24 * 60 * 60, // 24H
   102  	TZ:      "System",
   103  	// The default election-timeout in PD is 3s and minimum session TTL is 5s,
   104  	// which is calculated by `math.Ceil(3 * election-timeout / 2)`, we choose
   105  	// default capture session ttl to 10s to increase robust to PD jitter,
   106  	// however it will decrease RTO when single TiCDC node error happens.
   107  	CaptureSessionTTL:      10,
   108  	OwnerFlushInterval:     TomlDuration(50 * time.Millisecond),
   109  	ProcessorFlushInterval: TomlDuration(50 * time.Millisecond),
   110  	Sorter: &SorterConfig{
   111  		SortDir:       DefaultSortDir,
   112  		CacheSizeInMB: 128, // By default, use 128M memory as sorter cache.
   113  	},
   114  	Security: &security.Credential{},
   115  	KVClient: &KVClientConfig{
   116  		EnableMultiplexing:   true,
   117  		WorkerConcurrent:     8,
   118  		GrpcStreamConcurrent: 1,
   119  		AdvanceIntervalInMs:  300,
   120  		FrontierConcurrent:   8,
   121  		WorkerPoolSize:       0, // 0 will use NumCPU() * 2
   122  		RegionScanLimit:      40,
   123  		// The default TiKV region election timeout is [10s, 20s],
   124  		// Use 1 minute to cover region leader missing.
   125  		RegionRetryDuration: TomlDuration(time.Minute),
   126  	},
   127  	Debug: &DebugConfig{
   128  		DB: &DBConfig{
   129  			Count: 8,
   130  			// Following configs are optimized for write/read throughput.
   131  			// Users should not change them.
   132  			MaxOpenFiles:        10000,
   133  			BlockSize:           65536,
   134  			WriterBufferSize:    8388608,
   135  			Compression:         "snappy",
   136  			WriteL0PauseTrigger: math.MaxInt32,
   137  			CompactionL0Trigger: 16, // Based on a performance test on 4K tables.
   138  		},
   139  		Messages: defaultMessageConfig.Clone(),
   140  
   141  		Scheduler: NewDefaultSchedulerConfig(),
   142  		CDCV2:     &CDCV2{Enable: false},
   143  		Puller: &PullerConfig{
   144  			EnableResolvedTsStuckDetection: false,
   145  			ResolvedTsStuckInterval:        TomlDuration(5 * time.Minute),
   146  			LogRegionDetails:               false,
   147  		},
   148  	},
   149  	ClusterID:              "default",
   150  	GcTunerMemoryThreshold: DisableMemoryLimit,
   151  }
   152  
   153  // ServerConfig represents a config for server
   154  type ServerConfig struct {
   155  	Addr          string `toml:"addr" json:"addr"`
   156  	AdvertiseAddr string `toml:"advertise-addr" json:"advertise-addr"`
   157  
   158  	LogFile  string     `toml:"log-file" json:"log-file"`
   159  	LogLevel string     `toml:"log-level" json:"log-level"`
   160  	Log      *LogConfig `toml:"log" json:"log"`
   161  
   162  	DataDir string `toml:"data-dir" json:"data-dir"`
   163  
   164  	GcTTL int64  `toml:"gc-ttl" json:"gc-ttl"`
   165  	TZ    string `toml:"tz" json:"tz"`
   166  
   167  	CaptureSessionTTL int `toml:"capture-session-ttl" json:"capture-session-ttl"`
   168  
   169  	OwnerFlushInterval     TomlDuration `toml:"owner-flush-interval" json:"owner-flush-interval"`
   170  	ProcessorFlushInterval TomlDuration `toml:"processor-flush-interval" json:"processor-flush-interval"`
   171  
   172  	Sorter                 *SorterConfig        `toml:"sorter" json:"sorter"`
   173  	Security               *security.Credential `toml:"security" json:"security"`
   174  	KVClient               *KVClientConfig      `toml:"kv-client" json:"kv-client"`
   175  	Debug                  *DebugConfig         `toml:"debug" json:"debug"`
   176  	ClusterID              string               `toml:"cluster-id" json:"cluster-id"`
   177  	GcTunerMemoryThreshold uint64               `toml:"gc-tuner-memory-threshold" json:"gc-tuner-memory-threshold"`
   178  
   179  	// Deprecated: we don't use this field anymore.
   180  	PerTableMemoryQuota uint64 `toml:"per-table-memory-quota" json:"per-table-memory-quota"`
   181  	// Deprecated: we don't use this field anymore.
   182  	MaxMemoryPercentage int `toml:"max-memory-percentage" json:"max-memory-percentage"`
   183  }
   184  
   185  // Marshal returns the json marshal format of a ServerConfig
   186  func (c *ServerConfig) Marshal() (string, error) {
   187  	cfg, err := json.Marshal(c)
   188  	if err != nil {
   189  		return "", cerror.WrapError(cerror.ErrEncodeFailed, errors.Annotatef(err, "Unmarshal data: %v", c))
   190  	}
   191  	return string(cfg), nil
   192  }
   193  
   194  // Unmarshal unmarshals into *ServerConfig from json marshal byte slice
   195  func (c *ServerConfig) Unmarshal(data []byte) error {
   196  	err := json.Unmarshal(data, c)
   197  	if err != nil {
   198  		return cerror.WrapError(cerror.ErrDecodeFailed, err)
   199  	}
   200  	return nil
   201  }
   202  
   203  // String implements the Stringer interface
   204  func (c *ServerConfig) String() string {
   205  	s, _ := c.Marshal()
   206  	return s
   207  }
   208  
   209  // Clone clones a replication
   210  func (c *ServerConfig) Clone() *ServerConfig {
   211  	str, err := c.Marshal()
   212  	if err != nil {
   213  		log.Panic("failed to marshal replica config",
   214  			zap.Error(cerror.WrapError(cerror.ErrDecodeFailed, err)))
   215  	}
   216  	clone := new(ServerConfig)
   217  	err = clone.Unmarshal([]byte(str))
   218  	if err != nil {
   219  		log.Panic("failed to unmarshal replica config",
   220  			zap.Error(cerror.WrapError(cerror.ErrDecodeFailed, err)))
   221  	}
   222  	return clone
   223  }
   224  
   225  // ValidateAndAdjust validates and adjusts the server configuration
   226  func (c *ServerConfig) ValidateAndAdjust() error {
   227  	if !isValidClusterID(c.ClusterID) {
   228  		return cerror.ErrInvalidServerOption.GenWithStack(fmt.Sprintf("bad cluster-id"+
   229  			"please match the pattern \"^[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*$\", and not the list of"+
   230  			" following reserved world: %s"+
   231  			"eg, \"simple-cluster-id\"", strings.Join(ReservedClusterIDs, ",")))
   232  	}
   233  	if c.Addr == "" {
   234  		return cerror.ErrInvalidServerOption.GenWithStack("empty address")
   235  	}
   236  	if c.AdvertiseAddr == "" {
   237  		c.AdvertiseAddr = c.Addr
   238  	}
   239  	// Advertise address must be specified.
   240  	if idx := strings.LastIndex(c.AdvertiseAddr, ":"); idx >= 0 {
   241  		ip := net.ParseIP(c.AdvertiseAddr[:idx])
   242  		// Skip nil as it could be a domain name.
   243  		if ip != nil && ip.IsUnspecified() {
   244  			return cerror.ErrInvalidServerOption.GenWithStack("advertise address must be specified as a valid IP")
   245  		}
   246  	} else {
   247  		return cerror.ErrInvalidServerOption.GenWithStack("advertise address or address does not contain a port")
   248  	}
   249  	if c.GcTTL == 0 {
   250  		return cerror.ErrInvalidServerOption.GenWithStack("empty GC TTL is not allowed")
   251  	}
   252  	// 5s is minimum lease ttl in etcd(PD)
   253  	if c.CaptureSessionTTL < 5 {
   254  		log.Warn("capture session ttl too small, set to default value 10s")
   255  		c.CaptureSessionTTL = 10
   256  	}
   257  
   258  	if c.Security != nil {
   259  		if c.Security.ClientUserRequired {
   260  			if len(c.Security.ClientAllowedUser) == 0 {
   261  				log.Error("client-allowed-user should not be empty when client-user-required is true")
   262  				return cerror.ErrInvalidServerOption.GenWithStack("client-allowed-user should not be empty when client-user-required is true")
   263  			}
   264  			if !c.Security.IsTLSEnabled() {
   265  				log.Warn("client-allowed-user is true, but tls is not enabled." +
   266  					"It's highly recommended to enable TLS to secure the communication")
   267  			}
   268  		}
   269  		if c.Security.IsTLSEnabled() {
   270  			var err error
   271  			_, err = c.Security.ToTLSConfig()
   272  			if err != nil {
   273  				return errors.Annotate(err, "invalidate TLS config")
   274  			}
   275  			_, err = c.Security.ToGRPCDialOption()
   276  			if err != nil {
   277  				return errors.Annotate(err, "invalidate TLS config")
   278  			}
   279  		}
   280  	}
   281  
   282  	defaultCfg := GetDefaultServerConfig()
   283  	if c.Sorter == nil {
   284  		c.Sorter = defaultCfg.Sorter
   285  	}
   286  	c.Sorter.SortDir = DefaultSortDir
   287  	err := c.Sorter.ValidateAndAdjust()
   288  	if err != nil {
   289  		return err
   290  	}
   291  
   292  	if c.KVClient == nil {
   293  		c.KVClient = defaultCfg.KVClient
   294  	}
   295  	if err = c.KVClient.ValidateAndAdjust(); err != nil {
   296  		return errors.Trace(err)
   297  	}
   298  
   299  	if c.Debug == nil {
   300  		c.Debug = defaultCfg.Debug
   301  	}
   302  	if err = c.Debug.ValidateAndAdjust(); err != nil {
   303  		return errors.Trace(err)
   304  	}
   305  	return nil
   306  }
   307  
   308  // GetDefaultServerConfig returns the default server config
   309  func GetDefaultServerConfig() *ServerConfig {
   310  	return defaultServerConfig.Clone()
   311  }
   312  
   313  var globalServerConfig atomic.Value
   314  
   315  // GetGlobalServerConfig returns the global configuration for this server.
   316  // It should store configuration from command line and configuration file.
   317  // Other parts of the system can read the global configuration use this function.
   318  func GetGlobalServerConfig() *ServerConfig {
   319  	return globalServerConfig.Load().(*ServerConfig)
   320  }
   321  
   322  // StoreGlobalServerConfig stores a new config to the globalServerConfig. It mostly uses in the test to avoid some data races.
   323  func StoreGlobalServerConfig(config *ServerConfig) {
   324  	globalServerConfig.Store(config)
   325  }
   326  
   327  // TomlDuration is a duration with a custom json decoder and toml decoder
   328  type TomlDuration time.Duration
   329  
   330  // UnmarshalText is the toml decoder
   331  func (d *TomlDuration) UnmarshalText(text []byte) error {
   332  	stdDuration, err := time.ParseDuration(string(text))
   333  	if err != nil {
   334  		return err
   335  	}
   336  	*d = TomlDuration(stdDuration)
   337  	return nil
   338  }
   339  
   340  // UnmarshalJSON is the json decoder
   341  func (d *TomlDuration) UnmarshalJSON(b []byte) error {
   342  	var stdDuration time.Duration
   343  	if err := json.Unmarshal(b, &stdDuration); err != nil {
   344  		return err
   345  	}
   346  	*d = TomlDuration(stdDuration)
   347  	return nil
   348  }
   349  
   350  // isValidClusterID returns true if the cluster ID matches
   351  // the pattern "^[a-zA-Z0-9]+(\-[a-zA-Z0-9]+)*$", length no more than `clusterIDMaxLen`,
   352  // eg, "simple-cluster-id".
   353  func isValidClusterID(clusterID string) bool {
   354  	valid := clusterID != "" && len(clusterID) <= clusterIDMaxLen &&
   355  		clusterIDRe.MatchString(clusterID)
   356  	if !valid {
   357  		return false
   358  	}
   359  	for _, reserved := range ReservedClusterIDs {
   360  		if reserved == clusterID {
   361  			return false
   362  		}
   363  	}
   364  	return true
   365  }