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 }