一、newConfig()
newConfig() 是 ETCD 启动过程中的一个重要函数,它用于初始化和配置 ETCD 的各种配置项。该函数构造了一个 config 类型的实例,并为其配置了多个启动参数、标志和默认值。接下来,我们将逐行解析该函数的具体实现。
1. 创建 config 实例
        
            
            
              go
              
              
            
          
          cfg := &config{
    ec:      *embed.NewConfig(),
    ignored: ignored,
}- cfg := &config{...}:创建一个新的- config结构体实例,并为其各个字段赋予默认值。
- ec: *embed.NewConfig():初始化- embed.Config,这是 ETCD 服务的配置,包含了网络监听、存储路径等基础配置。
- ignored: ignored:- ignored是一个包含需要忽略的标志名称的字符串切片。它用于存储那些不会被解析和使用的标志。
2. 初始化 configFlags 结构体
        
            
            
              go
              
              
            
          
          cfg.cf = configFlags{
    flagSet: flag.NewFlagSet("etcd", flag.ContinueOnError),
    clusterState: flags.NewSelectiveStringValue(
        embed.ClusterStateFlagNew,
        embed.ClusterStateFlagExisting,
    ),
    fallback: flags.NewSelectiveStringValue(
        fallbackFlagExit,
        fallbackFlagProxy,
    ),
    v2deprecation: flags.NewSelectiveStringsValue(
        string(cconfig.V2Depr1WriteOnly),
        string(cconfig.V2Depr1WriteOnlyDrop),
        string(cconfig.V2Depr2Gone)),
}- 
flagSet: flag.NewFlagSet("etcd", flag.ContinueOnError):创建一个新的FlagSet实例,该实例用于解析命令行参数。"etcd"是标志集的名称,flag.ContinueOnError表示在遇到错误时继续执行(不退出)。
- 
clusterState: flags.NewSelectiveStringValue(...):clusterState是一个标志,表示集群的状态。它使用NewSelectiveStringValue初始化,支持选择值"new"(新集群)或"existing"(已有集群)。这个标志告诉 ETCD 是启动一个新集群还是加入现有集群。
- 
fallback: flags.NewSelectiveStringValue(...):fallback是一个标志,控制发现失败时的处理方式。它的可选值为"exit"或"proxy",决定在发现失败时退出或继续作为代理。
- 
v2deprecation: flags.NewSelectiveStringsValue(...):v2deprecation是一个包含多个选择值的标志,用于控制 ETCD v2 API 的弃用阶段。它使用NewSelectiveStringsValue初始化,支持不同的弃用阶段(如"write-only"、"write-only-drop"和"gone")。
3. 定义 Usage 函数
        
            
            
              go
              
              
            
          
          fs := cfg.cf.flagSet
fs.Usage = func() {
    fmt.Fprintln(os.Stderr, usageline)
}- fs := cfg.cf.flagSet:获取- flagSet,该实例用于解析命令行标志。
- fs.Usage = func():定义- Usage函数,当用户输入- -h或无效的标志时,- Usage会输出帮助信息。此处的- usageline是预定义的帮助文本。
4. 添加标志和参数
            
            
              go
              
              
            
          
          cfg.ec.AddFlags(fs)- cfg.ec.AddFlags(fs):将- embed.Config的标志添加到- flagSet中。- AddFlags函数会将 ETCD 服务相关的配置项(如端口、数据目录等)添加到- flagSet,使得它们可以通过命令行传递。
5. 配置命令行标志
            
            
              go
              
              
            
          
          fs.StringVar(&cfg.configFile, "config-file", "", "Path to the server configuration file. Note that if a configuration file is provided, other command line flags and environment variables will be ignored.")- fs.StringVar(&cfg.configFile, "config-file", ""):添加- --config-file标志,指定配置文件路径。该文件的内容会覆盖命令行参数和环境变量的设置。
            
            
              go
              
              
            
          
          fs.Var(cfg.cf.fallback, "discovery-fallback", fmt.Sprintf("Valid values include %q", cfg.cf.fallback.Valids()))
fs.Var(cfg.cf.clusterState, "initial-cluster-state", "Initial cluster state ('new' when bootstrapping a new cluster or 'existing' when adding new members to an existing cluster). After successful initialization (bootstrapping or adding), flag is ignored on restarts.")
fs.Var(cfg.cf.v2deprecation, "v2-deprecation", fmt.Sprintf("v2store deprecation stage: %q. ", cfg.cf.v2deprecation.Valids()))- fs.Var:为- fallback、- clusterState和- v2deprecation配置标志。每个标志都绑定到- configFlags结构体中的相应字段,并为用户提供合适的描述。
            
            
              go
              
              
            
          
          fs.BoolVar(&cfg.printVersion, "version", false, "Print the version and exit.")- fs.BoolVar(&cfg.printVersion, "version", false):添加- --version标志,用于输出 ETCD 的版本信息并退出。
6. 处理 ignored 标志
        
            
            
              go
              
              
            
          
          for _, f := range cfg.ignored {
    fs.Var(&flags.IgnoredFlag{Name: f}, f, "")
}- for _, f := range cfg.ignored:遍历- ignored字段(一个包含不需要处理的标志的切片),并为每个标志创建一个忽略的- flag。
7. 返回配置实例
            
            
              go
              
              
            
          
          return cfg- return cfg:函数结束时返回配置实例- cfg。该实例包含了 ETCD 启动时的所有配置,包括命令行标志、配置文件、集群信息等。
二、parse()函数解析
parse 函数是 config 结构体的方法,负责解析传入的命令行参数,处理配置文件和环境变量,并根据解析结果返回相应的错误或配置。下面是该函数的详细逐行解析:
函数签名:
            
            
              go
              
              
            
          
          func (cfg *config) parse(arguments []string) error- cfg *config:接收一个指向- config结构体的指针,- parse方法修改该配置实例。
- arguments []string:命令行参数的切片,通常是- os.Args[1:]。
1. 解析命令行参数:
            
            
              go
              
              
            
          
          perr := cfg.cf.flagSet.Parse(arguments)- cfg.cf.flagSet.Parse(arguments):解析传入的命令行参数- arguments。- flagSet是配置中的- FlagSet,负责命令行标志的解析。
- 返回值 perr:解析过程中返回的错误,如果没有错误,perr为nil。
2. 错误处理:
            
            
              go
              
              
            
          
          switch {
case perr == nil:
case errors.Is(perr, flag.ErrHelp):
    fmt.Println(flagsline)
    os.Exit(0)
default:
    os.Exit(2)
}- perr == nil:如果解析成功,继续执行。
- errors.Is(perr, flag.ErrHelp):如果错误是- flag.ErrHelp(用户请求帮助信息),则打印帮助文本- flagsline,并正常退出。
- default:如果发生其他错误,程序退出并返回状态码 2。
3. 检查无效的标志:
            
            
              go
              
              
            
          
          if len(cfg.cf.flagSet.Args()) != 0 {
    return fmt.Errorf("%q is not a valid flag", cfg.cf.flagSet.Arg(0))
}- cfg.cf.flagSet.Args():获取所有未被解析的命令行参数。
- 如果还有未解析的参数(即无效的标志),则返回一个错误,指明该标志无效。
4. 打印版本信息:
            
            
              go
              
              
            
          
          if cfg.printVersion {
    fmt.Printf("etcd Version: %s\n", version.Version)
    fmt.Printf("Git SHA: %s\n", version.GitSHA)
    fmt.Printf("Go Version: %s\n", runtime.Version())
    fmt.Printf("Go OS/Arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
    os.Exit(0)
}- cfg.printVersion:如果- --version标志被设置为- true,则打印 ETCD 的版本信息、Git 哈希值、Go 版本以及操作系统架构信息。
- os.Exit(0):打印完版本信息后正常退出程序。
5. 解析配置文件:
            
            
              go
              
              
            
          
          if cfg.configFile == "" {
    cfg.configFile = os.Getenv(flags.FlagToEnv("ETCD", "config-file"))
}- cfg.configFile:检查是否提供了配置文件路径(- --config-file)。
- 如果没有提供配置文件路径,则尝试从环境变量中获取 ETCD_CONFIG_FILE值。
6. 从配置文件加载配置:
            
            
              go
              
              
            
          
          if cfg.configFile != "" {
    err = cfg.configFromFile(cfg.configFile)
    if lg := cfg.ec.GetLogger(); lg != nil {
        lg.Info(
            "loaded server configuration, other configuration command line flags and environment variables will be ignored if provided",
            zap.String("path", cfg.configFile),
        )
    }
} else {
    err = cfg.configFromCmdLine()
}- cfg.configFile != "":如果指定了配置文件,调用- configFromFile加载配置文件内容。
- cfg.configFromFile(cfg.configFile):读取配置文件并应用到配置结构体。
- 如果没有配置文件,使用命令行参数(configFromCmdLine)来加载配置。
7. 设置 ETCD v2 相关弃用配置:
            
            
              go
              
              
            
          
          if cfg.ec.V2Deprecation == "" {
    cfg.ec.V2Deprecation = cconfig.V2DeprDefault
}- cfg.ec.V2Deprecation:如果未设置 v2 API 弃用配置,设置为默认值- cconfig.V2DeprDefault。
8. 解析 WarningUnaryRequestDuration 标志:
        
            
            
              go
              
              
            
          
          cfg.ec.WarningUnaryRequestDuration, perr = cfg.parseWarningUnaryRequestDuration()
if perr != nil {
    return perr
}- cfg.parseWarningUnaryRequestDuration():解析- --warning-unary-request-duration标志,用于设置警告阈值。
- 如果解析出错,返回错误。
9. 处理弃用的标志:
            
            
              go
              
              
            
          
          var warningsForDeprecatedFlags []string
cfg.cf.flagSet.Visit(func(f *flag.Flag) {
    if msg, ok := deprecatedFlags[f.Name]; ok {
        warningsForDeprecatedFlags = append(warningsForDeprecatedFlags, msg)
    }
})
if len(warningsForDeprecatedFlags) > 0 {
    if lg := cfg.ec.GetLogger(); lg != nil {
        for _, msg := range warningsForDeprecatedFlags {
            lg.Warn(msg)
        }
    }
}- cfg.cf.flagSet.Visit:遍历所有解析的标志,检查是否有弃用的标志。
- deprecatedFlags:包含弃用标志的映射。
- 日志警告:如果有弃用的标志,记录警告日志。
10. 返回配置解析错误:
            
            
              go
              
              
            
          
          return err- 如果过程中没有错误,返回 nil,表示解析成功。如果出现任何错误,将返回错误。
三、总结:
newConfig() 函数的主要作用是创建并初始化一个包含 ETCD 启动配置的实例。它负责:
- 创建并配置命令行标志。
- 加载和验证配置。
- 提供 ETCD 启动时所需的所有配置信息。
该函数通过 flagSet 处理所有命令行标志,提供给 ETCD 启动过程所需的配置信息。
- 命令行参数解析 :parse方法主要负责解析命令行参数、配置文件和环境变量。
- 版本信息输出:如果用户请求,打印 ETCD 的版本信息并退出。
- 配置文件加载:支持从配置文件和命令行参数加载配置。
- 弃用标志处理:检查是否使用了弃用的命令行标志,并记录警告。
- 错误处理:在遇到无效标志或其他错误时,及时返回错误并中止。
该函数的目的是确保配置的正确性并处理所有启动时的参数设置。