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的version和config 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 用法类似