NSQD启动分析以及对flag包的使用

本文详细介绍了Go语言中NSQD服务的启动流程,包括源码位置、核心结构体和方法,以及启动入口。同时深入探讨了flag包的使用,包括Value、Getter和boolFlag接口,以及FlagSet的实现。此外,还展示了如何自定义flag类型。文章还阐述了NSQD中对flag包的具体应用,如校验基础选项等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、NSQD启动入口

 

源码位置:apps/nsqd/main.go

核心结构体

 

type program struct { once sync.Once nsqd *nsqd.NSQD }

核心依赖包:github.com/judwhite/go-svc/svc

核心方法:Init,Start,Stop

 

核心依赖包:svc/svc_other.go

启动入口方法:Run,会依次调用Service接口的以下方法:Init->Start->Stop

program 结构体实现了github.com/judwhite/go-svc/svc中的Service接口,并相应实现了Init方法、Start方法和Stop方法

在方法svc.Run(prg, syscall.SIGINT, syscall.SIGTERM)启动之后,会调用Start()方法,处理命令行参数,通过方法nsqdFlagSet来实现。

具体实现位于apps/nsqd/options.go

 

总体的NSQD启动流程如下:

 

2、golang flag包探索(Go SDK 1.15.6)

(1)在flag包中,关键的接口有Value接口、Getter接口、boolFlag接口

// Value is the interface to the dynamic value stored in a flag.
// (The default value is represented as a string.)
//
// If a Value has an IsBoolFlag() bool method returning true,
// the command-line parser makes -name equivalent to -name=true
// rather than using the next command-line argument.
//
// Set is called once, in command line order, for each flag present.
// The flag package may call the String method with a zero-valued receiver,
// such as a nil pointer.
type Value interface {
   String() string
   Set(string) error
}
// Getter is an interface that allows the contents of a Value to be retrieved.
// It wraps the Value interface, rather than being part of it, because it
// appeared after Go 1 and its compatibility rules. All Value types provided
// by this package satisfy the Getter interface.
type Getter interface {
   Value
   Get() interface{}
}

// optional interface to indicate boolean flags that can be
// supplied without "=value" text
type boolFlag interface {
   Value
   IsBoolFlag() bool
}

 

并为类型boolValue、intValue、int64Value、uintValue、uint64Value、stringValue、float64Value、durationValue分别实现了Value接口和Getter接口

(2)核心结构体为 Flag和FlagSet,并且Flag的名称在FlagSet中必须是唯一的。

 

// A Flag represents the state of a flag.
type Flag struct {
   Name     string // name as it appears on command line
   Usage    string // help message
   Value    Value  // value as set
   DefValue string // default value (as text); for usage message
}

// Flag names must be unique within a FlagSet. An attempt to define a flag whose
// name is already in use will cause a panic.
type FlagSet struct {
   // Usage is the function called when an error occurs while parsing flags.
   // The field is a function (not a method) that may be changed to point to
   // a custom error handler. What happens after Usage is called depends
   // on the ErrorHandling setting; for the command line, this defaults
   // to ExitOnError, which exits the program after calling Usage.
   Usage func()

   name          string
   parsed        bool
   actual        map[string]*Flag
   formal        map[string]*Flag
   args          []string // arguments after flags
   errorHandling ErrorHandling
   output        io.Writer // nil means stderr; use Output() accessor
}

并且这两个结构体,为上面的各种类型,分别实现了以下方法和函数(Int类型为例),其中xxxVar需在init()方法中实现。

 

func IntVar(p *int, name string, value int, usage string)
func (f *FlagSet) IntVar(p *int, name string, value int, usage string)
func Int(name string, value int, usage string) *int 
func (f *FlagSet) Int(name string, value int, usage string) *int

同时提供了两个通用的函数和方法,前提是用户需要实现Value接口

 

func Var(value Value, name string, usage string) 
func (f *FlagSet) Var(value Value, name string, usage string)

解析函数将会在第一个non-flag参数之前("-"是一个 non-flag 参数)或者是结束符 "--"之后停止。

 

// parseOne parses one flag. It reports whether a flag was seen.
func (f *FlagSet) parseOne() (bool, error) {
   if len(f.args) == 0 {
      return false, nil
   }
   s := f.args[0]
   if len(s) < 2 || s[0] != '-' {
      return false, nil
   }
   numMinuses := 1
   if s[1] == '-' {
      numMinuses++
      if len(s) == 2 { // "--" terminates the flags
         f.args = f.args[1:]
         return false, nil
      }
   }
   name := s[numMinuses:]
   if len(name) == 0 || name[0] == '-' || name[0] == '=' {
      return false, f.failf("bad flag syntax: %s", s)
   }

   // it's a flag. does it have an argument?
   f.args = f.args[1:]
   hasValue := false
   value := ""
   for i := 1; i < len(name); i++ { // equals cannot be first
      if name[i] == '=' {
         value = name[i+1:]
         hasValue = true
         name = name[0:i]
         break
      }
   }
   m := f.formal
   flag, alreadythere := m[name] // BUG
   if !alreadythere {
      if name == "help" || name == "h" { // special case for nice help message.
         f.usage()
         return false, ErrHelp
      }
      return false, f.failf("flag provided but not defined: -%s", name)
   }

   if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // special case: doesn't need an arg
      if hasValue {
         if err := fv.Set(value); err != nil {
            return false, f.failf("invalid boolean value %q for -%s: %v", value, name, err)
         }
      } else {
         if err := fv.Set("true"); err != nil {
            return false, f.failf("invalid boolean flag %s: %v", name, err)
         }
      }
   } else {
      // It must have a value, which might be the next argument.
      if !hasValue && len(f.args) > 0 {
         // value is the next arg
         hasValue = true
         value, f.args = f.args[0], f.args[1:]
      }
      if !hasValue {
         return false, f.failf("flag needs an argument: -%s", name)
      }
      if err := flag.Value.Set(value); err != nil {
         return false, f.failf("invalid value %q for flag -%s: %v", value, name, err)
      }
   }
   if f.actual == nil {
      f.actual = make(map[string]*Flag)
   }
   f.actual[name] = flag
   return true, nil
}

(3)命令行的语法主要有以下几种形式:

 

-flag //只支持bool类型
-flag=x
-flag x //只支持非bool类型

以上语法对于一个或两个‘-’号,效果是一样的,但是要注意对于第三种情况,只支持非bool类型。

因为遇到以下格式:

 

cmd -x *

其中的*是Unix shell通配符,当*为0,false表示为一个文件。也有可能表示x标签的值为0或false,会产生二义性,因此规定第三种只支持非bool类型。

对于Integer,可接收的值,可以为1234, 0664, 0x1234 或者为负数。

对于Boolean,可接收到的值,可以为1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False。

对于Duration,可接受对time.ParseDuration任何有效的输入。

FlagSet允许定义独立的Flag集。

(4)flag 用法 (参考flag_test.go)

 

func testParse(f *FlagSet, t *testing.T) {
   if f.Parsed() {
      t.Error("f.Parse() = true before Parse")
   }
   boolFlag := f.Bool("bool", false, "bool value")
   bool2Flag := f.Bool("bool2", false, "bool2 value")
   intFlag := f.Int("int", 0, "int value")
   int64Flag := f.Int64("int64", 0, "int64 value")
   uintFlag := f.Uint("uint", 0, "uint value")
   uint64Flag := f.Uint64("uint64", 0, "uint64 value")
   stringFlag := f.String("string", "0", "string value")
   float64Flag := f.Float64("float64", 0, "float64 value")
   durationFlag := f.Duration("duration", 5*time.Second, "time.Duration value")
   extra := "one-extra-argument"
   args := []string{
      "-bool",
      "-bool2=true",
      "--int", "22",
      "--int64", "0x23",
      "-uint", "24",
      "--uint64", "25",
      "-string", "hello",
      "-float64", "2718e28",
      "-duration", "2m",
      extra,
   }
   if err := f.Parse(args); err != nil {
      t.Fatal(err)
   }
   if !f.Parsed() {
      t.Error("f.Parse() = false after Parse")
   }
   //注意boolFlag应该为true
   if *boolFlag != true {
      t.Error("bool flag should be true, is ", *boolFlag)
   }
   if *bool2Flag != true {
      t.Error("bool2 flag should be true, is ", *bool2Flag)
   }
   if *intFlag != 22 {
      t.Error("int flag should be 22, is ", *intFlag)
   }
   if *int64Flag != 0x23 {
      t.Error("int64 flag should be 0x23, is ", *int64Flag)
   }
   if *uintFlag != 24 {
      t.Error("uint flag should be 24, is ", *uintFlag)
   }
   if *uint64Flag != 25 {
      t.Error("uint64 flag should be 25, is ", *uint64Flag)
   }
   if *stringFlag != "hello" {
      t.Error("string flag should be `hello`, is ", *stringFlag)
   }
   if *float64Flag != 2718e28 {
      t.Error("float64 flag should be 2718e28, is ", *float64Flag)
   }
   if *durationFlag != 2*time.Minute {
      t.Error("duration flag should be 2m, is ", *durationFlag)
   }
   if len(f.Args()) != 1 {
      t.Error("expected one argument, got", len(f.Args()))
   } else if f.Args()[0] != extra {
      t.Errorf("expected argument %q got %q", extra, f.Args()[0])
   }
}

自定义类型,需要实现Value接口:

 

// These constants cause FlagSet.Parse to behave as described if the parse fails.
const (
   ContinueOnError ErrorHandling = iota // Return a descriptive error.
   ExitOnError                          // Call os.Exit(2) or for -h/-help Exit(0).
   PanicOnError                         // Call panic with a descriptive error.
)

// 定义一个自定义的flag 类型
type flagVar []string
//实现Value接口
func (f *flagVar) String() string {
    //转换为切片类型
   return fmt.Sprint([]string(*f))
}
//把值设置到类型
func (f *flagVar) Set(value string) error {
   *f = append(*f, value)
   return nil
}

func TestUserDefined(t *testing.T) {
   var flags FlagSet
    //初始化FlagSet的名称为"test"
   flags.Init("test", ContinueOnError)
   //定义自定义的flag类型变量
   var v flagVar
   //用Var方法,这样才能设置f.formal[name]
   flags.Var(&v, "v", "usage")
   if err := flags.Parse([]string{"-v", "1", "-v", "2", "-v=3"}); err != nil {
      t.Error(err)
   }
   if len(v) != 3 {
      t.Fatal("expected 3 args; got ", len(v))
   }
   expect := "[1 2 3]"
   if v.String() != expect {
      t.Errorf("expected value %q got %q", expect, v.String())
   }
}

3、nsqd 中对flag包的使用

(1) 主要使用了flag.NewFlagSet方法

 

// NewFlagSet returns a new, empty flag set with the specified name and
// error handling property. If the name is not empty, it will be printed
// in the default usage message and in error messages.
func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {
   f := &FlagSet{
      name:          name,
      errorHandling: errorHandling,
   }
   f.Usage = f.defaultUsage
   return f
}

// defaultUsage is the default function to print a usage message.
func (f *FlagSet) defaultUsage() {
   if f.name == "" {
      fmt.Fprintf(f.Output(), "Usage:\n")
   } else {
      fmt.Fprintf(f.Output(), "Usage of %s:\n", f.name)
   }
   f.PrintDefaults()
}

具体位于apps/nsqd/options.go中的函数

func nsqdFlagSet(opts *nsqd.Options) *flag.FlagSet

 

在此方法里面设置了nsqd启动所需要的命令行参数。

(2) 同时在apps/nsqd/main.go中的

func (p *program) Start() error

方法中,利用了flagSet.Lookup方法,对nsqd的versionconfig file 的基础选项,进行了校验。

 

//省略其余代码
if flagSet.Lookup("version").Value.(flag.Getter).Get().(bool) {
   fmt.Println(version.String("nsqd"))
   os.Exit(0)
}

var cfg config
configFile := flagSet.Lookup("config").Value.String()
if configFile != "" {
   _, err := toml.DecodeFile(configFile, &cfg)
   if err != nil {
      logFatal("failed to load config file %s - %s", configFile, err)
   }
}
cfg.Validate()
//省略其余代码

FlagSet的Lookup函数定义如下,从formal map[string]*Flag 中查询Flag

 

// Lookup returns the Flag structure of the named flag, returning nil if none exists.
func (f *FlagSet) Lookup(name string) *Flag {
   return f.formal[name]
}

类似于FlagSet 的Int64方法,其实底层是调用FlagSet 的Var方法的,此方法会往formal这个map中设置值

 

// Int64 defines an int64 flag with specified name, default value, and usage string.
// The return value is the address of an int64 variable that stores the value of the flag.
func (f *FlagSet) Int64(name string, value int64, usage string) *int64 {
   p := new(int64)
   f.Int64Var(p, name, value, usage)
   return p
}
// Int64Var defines an int64 flag with specified name, default value, and usage string.
// The argument p points to an int64 variable in which to store the value of the flag.
func (f *FlagSet) Int64Var(p *int64, name string, value int64, usage string) {
   f.Var(newInt64Value(value, p), name, usage)
}

(3)  自定义flag 类型

 源码路径:apps/nsqd/options.go

定义了 tlsRequiredOption 类型

type tlsRequiredOption int

实现了Value接口

func (t *tlsRequiredOption) Set(s string) error {
    //把字符串转换为小写
	s = strings.ToLower(s)
    //如果是tcp-https
	if s == "tcp-https" {
		*t = nsqd.TLSRequiredExceptHTTP
		return nil
	}
    
     //转换为bool类型
	required, err := strconv.ParseBool(s)
	if required {
		*t = nsqd.TLSRequired
	} else {
		*t = nsqd.TLSNotRequired
	}
	return err
}

func (t *tlsRequiredOption) String() string {
    //转换为十进制
	return strconv.FormatInt(int64(*t), 10)
}

实现了 Getter 接口

func (t *tlsRequiredOption) Get() interface{} { return int(*t) }

实现了boolFlag接口

func (t *tlsRequiredOption) IsBoolFlag() bool { return true }

解析

//省略其他代码	
tlsRequired := tlsRequiredOption(opts.TLSRequired)

flagSet.Var(&tlsRequired, "tls-required", "require TLS for client connections (true, false, tcp-https)")
//省略其他代码	

同时针对 

type tlsMinVersionOption uint16

也实现了Value和Getter接口,与tlsRequiredOption 用法类似

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DreamCatcher

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值