源码分析按从下至上的,还原是从上至下的。
在influxdb/cmd/influxd/main.go文件,是influxdb服务端程序入口,是服务端main()函数所在处。
fun main() {
rand.Seed(time.Now().UnixNano())
m := NewMain()
if err := m.Run(os.Args[1:]...); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
调用构造函数NewMain()生成一个Main结构体实例m,再用实例m调用结构体的方法Run(args …String)。
下图方法、函数所在位置:influxdb/cmd/influxd/main.go文件
// Main represents the program execution.
type Main struct {
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
}
// NewMain return a new instance of Main.
func NewMain() *Main {
return &Main{
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
}
// Run determines and runs the command specified by the CLI args.
func (m *Main) Run(args ...string) error {
name, args := cmd.ParseCommandName(args)
// Extract name from args.
switch name {
case "", "run":
cmd := run.NewCommand()
// Tell the server the build details.
cmd.Version = version
cmd.Commit = commit
cmd.Branch = branch
if err := cmd.Run(args...); err != nil {
return fmt.Errorf("run: %s", err)
}
……
在main包的Run方法中调用github.com/influxdata/influxdb/cmd包中的func ParseCommandName(args []string) (string, []string) {}函数(如下图所示),返回命令名称和参数列表。如果第一个参数不带破折号,则直接赋值给name,否则判断是不是-h,-help,--help三个中的一个,是的话就赋值字符串help给变量name。如果第一个参数是help并且参数长度大于2,并且第二个参数不是以破折号开头,则返回第二个参数,和参数列表-h。如果name不为空,则返回name参数列表args[1:]。否则返回空,和参数裂变args。该函数也被其他几个模块的main函数调用,比如influxdb/cmd/influxd/main.go、influxdb/cmd/influxtools/main.go等。
下面函数所在位置:influxdb/cmd/parse.go
// ParseCommandName extracts the command name and args from the args list.
func ParseCommandName(args []string) (string, []string) {
// Retrieve command name as first argument.
var name string
if len(args) > 0 {
if !strings.HasPrefix(args[0], "-") {
name = args[0]
} else if args[0] == "-h" || args[0] == "-help" || args[0] == "--help" {
// Special case -h immediately following binary name
name = "help"
}
}
// If command is "help" and has an argument then rewrite args to use "-h".
if name == "help" && len(args) > 2 && !strings.HasPrefix(args[1], "-") {
return args[1], []string{"-h"}
}
// If a named command is specified then return it with its arguments.
if name != "" {
return name, args[1:]
}
return "", args
}
在main包的Run方法中,根据命令名称,调用不同模块的,比如,如果是run命令(从源码可以看出,如果没指定命令名称,则默认是run命令),则调用influxdb/cmd/influxd/run包中的NewCommand()构造函数,实例化一个Command结构体的对象cmd,再进行一些初始化,然后利用该对象调用其自身的Run方法。入参是之前在命令行中输入的除了命令名称的参数列表。
下图函数所在位置:influxdb/cmd/influxd/run/command.go文件。
func (cmd *Command) Run(args ...string) error {
// Parse the command line flags.
options, err := cmd.ParseFlags(args...)
if err != nil {
return err
}
config, err := cmd.ParseConfig(options.GetConfigPath())
if err != nil {
return fmt.Errorf("parse config: %s", err)
}
// Apply any environment variables on top of the parsed config
if err := config.ApplyEnvOverrides(cmd.Getenv); err != nil {
return fmt.Errorf("apply env config: %v", err)
}
// Validate the configuration.
if err := config.Validate(); err != nil {
return fmt.Errorf("%s. To generate a valid configuration file run `influxd config > influxdb.generated.conf`", err)
}
var logErr error
if cmd.Logger, logErr = config.Logging.New(cmd.Stderr); logErr != nil {
// assign the default logger
cmd.Logger = logger.New(cmd.Stderr)
}
// Attempt to run pprof on :6060 before startup if debug pprof enabled.
if config.HTTPD.DebugPprofEnabled {
runtime.SetBlockProfileRate(int(1 * time.Second))
runtime.SetMutexProfileFraction(1)
go func() { http.ListenAndServe("localhost:6060", nil) }()
}
// Print sweet InfluxDB logo.
if !config.Logging.SuppressLogo && logger.IsTerminal(cmd.Stdout) {
fmt.Fprint(cmd.Stdout, logo)
}
// Mark start-up in log.
cmd.Logger.Info("InfluxDB starting",
zap.String("version", cmd.Version),
zap.String("branch", cmd.Branch),
zap.String("commit", cmd.Commit))
cmd.Logger.Info("Go runtime",
zap.String("version", runtime.Version()),
zap.Int("maxprocs", runtime.GOMAXPROCS(0)))
// If there was an error on startup when creating the logger, output it now.
if logErr != nil {
cmd.Logger.Error("Unable to configure logger", zap.Error(logErr))
}
// Write the PID file.
if err := cmd.writePIDFile(options.PIDFile); err != nil {
return fmt.Errorf("write pid file: %s", err)
}
cmd.pidfile = options.PIDFile
if config.HTTPD.PprofEnabled {
// Turn on block and mutex profiling.
runtime.SetBlockProfileRate(int(1 * time.Second))
runtime.SetMutexProfileFraction(1) // Collect every sample
}
// Create server from config and start it.
buildInfo := &BuildInfo{
Version: cmd.Version,
Commit: cmd.Commit,
Branch: cmd.Branch,
Time: cmd.BuildTime,
}
s, err := NewServer(config, buildInfo)
if err != nil {
return fmt.Errorf("create server: %s", err)
}
s.Logger = cmd.Logger
s.CPUProfile = options.CPUProfile
s.MemProfile = options.MemProfile
if err := s.Open(); err != nil {
return fmt.Errorf("open server: %s", err)
}
cmd.Server = s
// Begin monitoring the server's error channel.
go cmd.monitorServerErrors()
return nil
}
在run包中的Run(args...)方法中,先调用ParseFlags(args...)方法解析相应的参数,ParseFlags(args...)方法里又调用了flag包来完成解析,并得到配置选项options。比如我们使用-config influxd.conf参数,会得到一个仅含有ConfigPath的Options对象。再利用run包中的Command结构体的ParseConfig(path string)得到Config对象。利用options.GetConfigPath()得到配置文件路径。利用run包下NewServer函数以及之前得到的config和新构造的buildinfo得到influxd/cmd/influxd/run/server.go文件中Server结构体的实例s,最后利用实例s调用s.Open()方法开启服务。
而在Server结构体的Open()方法中,调用Server的appendOpenTSDBService(c opentsdb.Config)方法添加了 opentsdb的服务。实际上是将实现了server.go文件中Service接口的opentsdb.Service实现类添加到run.Service类型的slice里面去。
for _, i := range s.config.OpenTSDBInputs {
if err := s.appendOpenTSDBService(i); err != nil {
return err
}
}
func (s *Server) appendOpenTSDBService(c opentsdb.Config) error {
if !c.Enabled {
return nil
}
srv, err := opentsdb.NewService(c)
if err != nil {
return err
}
srv.PointsWriter = s.PointsWriter
srv.MetaClient = s.MetaClient
s.Services = append(s.Services, srv)
return nil
}
// Service represents a service attached to the server.
type Service interface {
WithLogger(log *zap.Logger)
Open() error
Close() error
}
添加完所有的services之后,使用for range循环对所有的service执行打开服务操作。其中具有的Service有opentsdb.Service、httpd.Service等。利用多态的性质,分别打开每个具体的服务。
for _, service := range s.Services {
if err := service.Open(); err != nil {
return fmt.Errorf("open service: %s", err)
}
}
如下图所示,都是实现了run.Service接口的模块。
在httpd模块中,在httpd.NewService(c Config)函数里,实例化Service的时候,同时调用了NewHandler(c Config)函数,实例化了一个Handler,而实例化Handler时,又调用了h.AddRoutes(routes ...Route)方法,AddRoutes实现了对Handler成员变量mux的初始化。
除了run之外的其他命令如下所示:
case "backup":
name := backup.NewCommand()
if err := name.Run(args...); err != nil {
return fmt.Errorf("backup: %s", err)
}
case "restore":
name := restore.NewCommand()
if err := name.Run(args...); err != nil {
return fmt.Errorf("restore: %s", err)
}
case "config":
if err := run.NewPrintConfigCommand().Run(args...); err != nil {
return fmt.Errorf("config: %s", err)
}
case "version":
if err := NewVersionCommand().Run(args...); err != nil {
return fmt.Errorf("version: %s", err)
}
case "help":
if err := help.NewCommand().Run(args...); err != nil {
return fmt.Errorf("help: %s", err)
}
default:
return fmt.Errorf(`unknown command "%s"`+"\n"+`Run 'influxd help' for usage`+"\n\n", name)
}