Thanos源码专题【左扬精讲】——Thanos main.go(release-0.26)源码阅读和分析(详解 cmd/main.go )

Thanos源码专题精讲——Thanos main.go(release-0.26)源码阅读和分析(详解 cmd/main.go )

https://github.com/thanos-io/thanos/blob/v0.26.0/cmd/thanos/main.go

// Copyright (c) The Thanos Authors.
// Licensed under the Apache License 2.0.

package main

import (
	"context"
	"fmt"
	"io"
	"os"
	"os/signal"
	"path/filepath"
	"runtime"
	"runtime/debug"
	"syscall"

	"github.com/go-kit/log"                                     //  导入go-kit日志包
	"github.com/go-kit/log/level"                               //  导入go-kit日志级别包
	"github.com/oklog/run"                                      //  导入oklog运行包
	"github.com/opentracing/opentracing-go"                     //  导入opentracing-go包
	"github.com/pkg/errors"                                     //  导入错误处理包
	"github.com/prometheus/client_golang/prometheus"            //  导入prometheus客户端包
	"github.com/prometheus/client_golang/prometheus/collectors" //  导入prometheus客户端收集器包
	"github.com/prometheus/common/version"                      //  导入prometheus版本包
	"go.uber.org/automaxprocs/maxprocs"                         //  导入自动调整最大进程数包
	"gopkg.in/alecthomas/kingpin.v2"                            //  导入kingpin命令行参数包

	"github.com/thanos-io/thanos/pkg/extkingpin"     //  导入扩展kingpin命令行参数包
	"github.com/thanos-io/thanos/pkg/logging"        //  导入日志包
	"github.com/thanos-io/thanos/pkg/tracing/client" //  导入追踪客户端包
)

// main 函数是程序的入口点。它执行以下步骤:
// 1. 设置在内存访问错误时发生 panic。
// 2. 根据环境变量 DEBUG 设置互斥锁和阻塞剖析。
// 3. 创建一个新的应用程序实例。
// 4. 添加一些命令行标志,包括调试名称、日志级别、日志格式和追踪配置。
// 5. 注册应用程序的不同组件。
// 6. 解析命令行参数。
// 7. 根据日志级别和格式创建一个新的日志记录器。
// 8. 根据容器中的 CPU 限制自动调整 GOMAXPROCS。
// 9. 创建一个新的度量标准注册表,并注册一些收集器。
// 10. 创建一个新的组来运行命令。
// 11. 创建一个新的追踪器,并设置全局追踪器。
// 12. 创建一个信号通道来分发重新加载事件到子命令。
// 13. 设置命令。
// 14. 监听终止信号。
// 15. 监听重新加载信号。
// 16. 运行组。
// 17. 在退出时打印一条信息。
func main() {
	// We use mmaped resources in most of the components so hardcode PanicOnFault to true. This allows us to recover (if we can e.g if queries
	// are temporarily accessing unmapped memory).
	// 设置内存访问错误时发生 panic,这样程序在访问非法内存时会终止,以便快速发现问题。
	debug.SetPanicOnFault(true)

	// If the DEBUG environment variable is set, enable mutex and block profiling.
	// 如果环境变量 DEBUG 被设置,则启用互斥锁和阻塞剖析,帮助开发者调试并发问题
	if os.Getenv("DEBUG") != "" {
		runtime.SetMutexProfileFraction(10)
		runtime.SetBlockProfileRate(10)
	}

	// Create a new app with the given name and version. 创建一个新的应用程序实例,并设置其名称和版本。这将用于解析命令行参数、注册子命令等。
	app := extkingpin.NewApp(kingpin.New(filepath.Base(os.Args[0]), "A block storage based long-term storage for Prometheus.").Version(version.Print("thanos")))
	// Create a flag for the debug name. 创建一个标志,用于指定调试名称。这将作为日志行前缀添加到日志中,有助于在多实例环境中区分不同进程的输出。
	debugName := app.Flag("debug.name", "Name to add as prefix to log lines.").Hidden().String()
	// Create a flag for the log level. 创建一个标志,用于指定日志级别。这将控制记录到标准输出的消息的详细程度。
	logLevel := app.Flag("log.level", "Log filtering level.").
		Default("info").Enum("error", "warn", "info", "debug")
	// Create a flag for the log format. 创建一个标志,用于指定日志格式。这将控制日志消息的输出格式。例如,logfmt 格式更适合人类阅读,而 json 格式更适用于机器处理。
	logFormat := app.Flag("log.format", "Log format to use. Possible options: logfmt or json.").
		Default(logging.LogFormatLogfmt).Enum(logging.LogFormatLogfmt, logging.LogFormatJSON)
	// Create a flag for the tracing configuration. 创建一个标志,用于指定跟踪配置。这将允许用户指定要使用的跟踪后端和相关的配置选项。例如,Jaeger、Zipkin 或 Datadog。
	tracingConfig := extkingpin.RegisterCommonTracingFlags(app)

	// Register the different components of the app.
	// 【问】这里为什么要注册这么多组件?
	// 【答】组件化设计使得每个功能模块可以独立配置、启用或禁用,增强了可维护性和可扩展性。每个 register* 函数负责为应用程序注册一个独立的组件。组件化的设计让每个功能模块可以灵活配置和扩展。
	registerSidecar(app)       // 注册边车组件。
	registerStore(app)         // 注册存储组件。
	registerQuery(app)         // 注册查询组件。
	registerRule(app)          // 注册规则组件。
	registerCompact(app)       // 注册压缩组件。
	registerTools(app)         // 注册工具组件。
	registerReceive(app)       // 注册接收组件。
	registerQueryFrontend(app) // 注册查询前端组件。

	// Parse the command line arguments. 解析命令行参数。这将根据提供的标志和子命令设置应用程序的配置。
	cmd, setup := app.Parse()
	// Create a new logger with the given log level and format.
	logger := logging.NewLogger(*logLevel, *logFormat, *debugName)

	// Running in container with limits but with empty/wrong value of GOMAXPROCS env var could lead to throttling by cpu
	// maxprocs will automate adjustment by using cgroups info about cpu limit if it set as value for runtime.GOMAXPROCS.
	// 自动调整 GOMAXPROCS,适应容器的 CPU 限制,避免因设置错误导致的性能问题
	undo, err := maxprocs.Set(maxprocs.Logger(func(template string, args ...interface{}) {
		level.Debug(logger).Log("msg", fmt.Sprintf(template, args))
	}))
	// undo 是一个函数,用于撤销 maxprocs.Set() 的设置。如果设置了 GOMAXPROCS 环境变量,它将使用 cgroups 信息自动调整。如果没有设置或值为空/错误,则不会进行任何更改。
	defer undo()
	if err != nil {
		level.Warn(logger).Log("warn", errors.Wrapf(err, "failed to set GOMAXPROCS: %v", err)) //  如果err不为nil,则记录警告日志
	}

	// Create a new metrics registry.
	// 创建 Prometheus 的度量标准注册表,收集应用程序的各项性能指标(如 CPU、内存、进程信息)
	metrics := prometheus.NewRegistry()
	// Register the collectors to the metrics registry.
	// 创建 Prometheus 度量标准注册表并注册相关收集器,这样应用就能自动监控 CPU 使用率、内存使用情况等。
	metrics.MustRegister(
		version.NewCollector("thanos"),                                    // 应用版本收集器
		collectors.NewGoCollector(),                                       // Go 运行时指标收集器
		collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}), // 进程指标收集器
	)

	// Some packages still use default Register. Replace to have those metrics.
	// 将默认的注册器替换为 metrics,以便收集这些指标。这将确保所有指标都被收集,有助于监控应用程序的性能和健康状况。
	prometheus.DefaultRegisterer = metrics

	// Create a new group to run the commands.
	// 创建一个新的 goroutine 组,用于运行命令。
	var g run.Group
	// Create a new tracer.
	// 设置追踪器(tracing),可选的分布式追踪配置
	var tracer opentracing.Tracer
	// Setup optional tracing.
	{
		var (
			ctx             = context.Background()
			closer          io.Closer
			confContentYaml []byte
		)

		confContentYaml, err = tracingConfig.Content()
		if err != nil {
			level.Error(logger).Log("msg", "getting tracing config failed", "err", err)
			os.Exit(1)
		}

		if len(confContentYaml) == 0 {
			tracer = client.NoopTracer() // 如果没有配置追踪,则使用 NoopTracer
		} else {
			tracer, closer, err = client.NewTracer(ctx, logger, metrics, confContentYaml)
			if err != nil {
				fmt.Fprintln(os.Stderr, errors.Wrapf(err, "tracing failed"))
				os.Exit(1)
			}
		}

		// This is bad, but Prometheus does not support any other tracer injections than just global one.
		// TODO(bplotka): Work with basictracer to handle gracefully tracker mismatches, and also with Prometheus to allow
		// tracer injection.
		// 这是不好的,但 Prometheus 不支持除了全局追踪器之外的任何追踪器注入。
		opentracing.SetGlobalTracer(tracer)

		ctx, cancel := context.WithCancel(ctx) // 创建一个带有取消功能的上下文,用于处理信号和取消操作
		g.Add(func() error {
			<-ctx.Done() // 等待上下文取消信号
			return ctx.Err()
		}, func(error) {
			if closer != nil {
				if err := closer.Close(); err != nil {
					// 关闭追踪器失败
					level.Warn(logger).Log("msg", "closing tracer failed", "err", err)
				}
			}
			cancel()
		})
	}
	// Create a signal channel to dispatch reload events to sub-commands.
	// 创建信号通道,处理程序终止(SIGINT, SIGTERM)和重新加载(SIGHUP)信号
	reloadCh := make(chan struct{}, 1)

	// Setup the command.
	if err := setup(&g, logger, metrics, tracer, reloadCh, *logLevel == "debug"); err != nil {
		// Use %+v for github.com/pkg/errors error to print with stack.
		level.Error(logger).Log("err", fmt.Sprintf("%+v", errors.Wrapf(err, "preparing %s command failed", cmd)))
		os.Exit(1)
	}

	// Listen for termination signals.
	// 设置中断信号和重载信号处理函数,以便在接收到中断或重载信号时优雅地关闭应用程序。
	{
		cancel := make(chan struct{})
		g.Add(func() error {
			return interrupt(logger, cancel) // interrupt 函数处理程序终止信号(如 SIGINT, SIGTERM)。
		}, func(error) {
			close(cancel)
		})
	}

	// Listen for reload signals.
	{
		cancel := make(chan struct{})
		g.Add(func() error {
			return reload(logger, cancel, reloadCh) // reload 函数处理重新加载信号(如 SIGHUP)。
		}, func(error) {
			close(cancel)
		})
	}

	// Run the group.
	// 运行所有添加到运行组中的任务,直到所有任务都完成或发生错误。
	if err := g.Run(); err != nil {
		// Use %+v for github.com/pkg/errors error to print with stack.
		// 打印错误信息并退出程序。使用 %+v 格式化错误信息,以便包含堆栈跟踪。
		level.Error(logger).Log("err", fmt.Sprintf("%+v", errors.Wrapf(err, "%s command failed", cmd)))
		os.Exit(1)
	}
	level.Info(logger).Log("msg", "exiting")
}

// interrupt 函数用于处理中断信号和取消信号
//
// 参数:
// logger - 日志记录器,用于记录日志
// cancel - 一个只读通道,用于接收取消信号
//
// 返回值:
// error - 如果接收到取消信号,则返回错误信息;否则返回 nil
func interrupt(logger log.Logger, cancel <-chan struct{}) error {
	// 创建一个信号通道
	c := make(chan os.Signal, 1)
	// 监听 SIGINT 和 SIGTERM 信号
	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
	select {
	case s := <-c:
		// 接收到信号,记录日志并返回 nil
		// 捕获到信号。正在退出。
		level.Info(logger).Log("msg", "caught signal. Exiting.", "signal", s)
		return nil
	case <-cancel:
		// 接收到取消信号,返回错误
		// 取消
		return errors.New("canceled")
	}
}

// reload 函数用于监听操作系统信号,并在接收到 SIGHUP 信号时重新加载配置。
//
// 参数:
// - logger: log.Logger 类型,用于记录日志。
// - cancel: <-chan struct{} 类型,用于接收取消信号。
// - r: chan<- struct{} 类型,用于发送重新加载信号。
//
// 返回值:
// - error 类型,如果发生错误则返回错误信息,否则返回 nil。
func reload(logger log.Logger, cancel <-chan struct{}, r chan<- struct{}) error {
	// 创建一个长度为1的通道,用于接收信号
	c := make(chan os.Signal, 1)
	// 通知通道c接收SIGHUP信号
	signal.Notify(c, syscall.SIGHUP)
	for {
		select {
		case s := <-c:
			// 打印日志,表示捕获到信号,正在重新加载
			level.Info(logger).Log("msg", "caught signal. Reloading.", "signal", s)
			select {
			case r <- struct{}{}:
				// 打印日志,表示重新加载已调度
				level.Info(logger).Log("msg", "reload dispatched.")
			default:
			}
		case <-cancel:
			// 如果接收到取消信号,则返回错误
			return errors.New("canceled")
		}
	}
}

// getFlagsMap 将 kingpin.FlagModel 类型的切片转换为 map[string]string 类型
// 参数:
//   - flags: kingpin.FlagModel 类型的切片,包含所有标志位信息
//
// 返回值:
//   - map[string]string: 包含所有非默认标志位名称及其值的映射
func getFlagsMap(flags []*kingpin.FlagModel) map[string]string {
	flagsMap := map[string]string{}

	// 创建一个空的kingpin命令行参数解析器实例,用于排除默认标志位
	// Exclude kingpin default flags to expose only Thanos ones.
	boilerplateFlags := kingpin.New("", "").Version("")

	// 遍历所有标志位
	for _, f := range flags {
		// 如果标志位是默认标志位,则跳过
		if boilerplateFlags.GetFlag(f.Name) != nil {
			continue
		}
		// 将非默认标志位添加到flagsMap中
		flagsMap[f.Name] = f.Value.String()
	}
	return flagsMap
}
posted @ 2025-02-07 18:53  左扬  阅读(22)  评论(0)    收藏  举报