Jaeger源码解析 -- All in One 模式

简介

Jaeger 的 All-in-one 模式主要是用来快速启动一个本地服务用来测试,其中包含 Jaeger UI、collector、query、agent、这些组件。这个模式下的存储数据是放在内存中的。

启动 All-in-one 模式的 jaeger 最简单的方式是使用 Docker 镜像的来启动。

$ docker run -d --name jaeger \
  -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
  -p 5775:5775/udp \
  -p 6831:6831/udp \
  -p 6832:6832/udp \
  -p 5778:5778 \
  -p 16686:16686 \
  -p 14268:14268 \
  -p 9411:9411 \
  jaegertracing/all-in-one:1.8

下面的表格是 Jaeger All-in-one 暴露端口的列表:

端口协议组件功能
5775UDPagent接受zipkin.thrift compact thrift 协议(已过时,仅旧客户端使用)
6831UDPagent接受jaeger.thrift compact thrift 协议
6832UDPagent接受jaeger.thrift binary thrift 协议
5778HTTPagent服务配置
16686HTTPquery服务前端
14268HTTPcollector直接从客户端接受jaeger.thrift协议
9411HTTPcollector兼容 Zipkin 服务(可选的)

代码解析

本博文中使用的代码是 v190 版本。

入口

All-in-one 的入口在cmd/all-in-one/main.go中。实际上所有的组件入口都在 cmd 这个包下。

All-in-one 需要启动 agent query 和 collector 这三个组件,都在入口的启动函数中实现的。下面看下具体的代码。

准备工作
var signalsChannel = make(chan os.Signal)
signal.Notify(signalsChannel, os.Interrupt, syscall.SIGTERM)

首先创建一个用来接收信号的 channel,用来接收系统中断(os.Interrupt)和系统的 kill 指令(syscall.SIGTERM)。

if os.Getenv(storage.SpanStorageTypeEnvVar) == "" {
	os.Setenv(storage.SpanStorageTypeEnvVar, "memory") // other storage types default to SpanStorage
}

设置环境变量。

storageFactory, err := storage.NewFactory(storage.FactoryConfigFromEnvAndCLI(os.Args, os.Stderr))

strategyStoreFactory, err := ss.NewFactory(ss.FactoryConfigFromEnv())

上面的一行初始化了存储工厂,下面的是采样策略的工厂。接下来就看一下这两个工厂是如何初始化的,以及他们的作用是什么。

storageFactory

storage.NewFactory的入参是storage.FactoryConfigFromEnvAndCLI,先来看下这个方法。

func FactoryConfigFromEnvAndCLI(args []string, log io.Writer) FactoryConfig {
	// 从环境变量中获取span的存储类型
	spanStorageType := os.Getenv(SpanStorageTypeEnvVar)
	if spanStorageType == "" {
		// 如果环境变量中没有设置,从命令行中取值
		spanStorageType = spanStorageTypeFromArgs(args, log)
	}
	if spanStorageType == "" {
		// 设置默认的存储为cassandraStorageType
		spanStorageType = cassandraStorageType
	}
	// 考虑到有多个存储的情况
	spanWriterTypes := strings.Split(spanStorageType, ",")
	// 如果有多个存储的话,那么读操作只会从第一个存储类型中读取
	if len(spanWriterTypes) > 1 {
		fmt.Fprintf(log,
			"WARNING: multiple span storage types have been specified. "+
				"Only the first type (%s) will be used for reading and archiving.\n\n",
			spanWriterTypes[0],
		)
	}
	// 获取dependency存储类型
	depStorageType := os.Getenv(DependencyStorageTypeEnvVar)
	if depStorageType == "" {
		depStorageType = spanWriterTypes[0]
	}
	return FactoryConfig{
		SpanWriterTypes:         spanWriterTypes,
		SpanReaderType:          spanWriterTypes[0],
		DependenciesStorageType: depStorageType,
	}
}

从上面的代码可以看出,这个方法的作用是从环境变量或者命令行中读取出存储配置,现在有如下两种类型的存储。

// 后端用来存储spans的方式
SpanStorageTypeEnvVar = "SPAN_STORAGE_TYPE"

// 后端用来存储dependencies的方式
DependencyStorageTypeEnvVar = "DEPENDENCY_STORAGE_TYPE"

span 中存储的是链路追踪的数据,dependency 中存储和依赖相关的数据。Dependencies 就是在 Jaeger UI 上方菜单中的 Dependencies 一栏。

在我们的情境中,FactoryConfigFromEnvAndCLI方法的返回值都是memory类型的存储。

然后我们再看一下NewFactory方法。

func NewFactory(config FactoryConfig) (*Factory, error) {
	f := &Factory{FactoryConfig: config}
	uniqueTypes := map[string]struct{}{
		f.SpanReaderType:          {},
		f.DependenciesStorageType: {},
	}
	// 遍历并去重
	for _, storageType := range f.SpanWriterTypes {
		uniqueTypes[storageType] = struct{}{}
	}
	f.factories = make(map[string]storage.Factory)
	// 根据去重后的结果实例化工厂
	for t := range uniqueTypes {
		ff, err := f.getFactoryOfType(t)
		if err != nil {
			return nil, err
		}
		f.factories[t] = ff
	}
	return f, nil
}

从上面的代码可以看出,NewFactory方法是判断需要用到哪几种存储类型,并分别实例化的过程。

strategyStoreFactory

这里的NewFactory方法同样有一个入参FactoryConfigFromEnv,首先看下这个方法。

func FactoryConfigFromEnv() FactoryConfig {
	strategyStoreType := os.Getenv(SamplingTypeEnvVar)
	if strategyStoreType == "" {
		strategyStoreType = staticStrategyStoreType
	}
	return FactoryConfig{
		StrategyStoreType: strategyStoreType,
	}
}

通过上面的代码可以看出,FactoryConfigFromEnv方法作用就是从环境变量中获取采样类型。共有两种采样类型。

staticStrategyStoreType   = "static"
adaptiveStrategyStoreType = "adaptive"

默认的采样类型是 static。

NewFactory方法也与上面的方法类似,获取配置的采样的类型并实例化,值得注意的是,这里的采样类型只实现了 static ,没有实现 adaptive 类型的工厂。

func NewFactory(config FactoryConfig) (*Factory, error) {
	f := &Factory{FactoryConfig: config}
	uniqueTypes := map[string]struct{}{
		f.StrategyStoreType: {},
	}
	f.factories = make(map[string]strategystore.Factory)
	for t := range uniqueTypes {
		ff, err := f.getFactoryOfType(t)
		if err != nil {
			return nil, err
		}
		f.factories[t] = ff
	}
	return f, nil
}

值得一提的是,这里的 f 并不是真正的工厂,而是存在 f.factories 里面。

初始化配置
v := viper.New()

在这里新建一个 viper 实例,作用是存储配置。viper 是Go应用程序的完整配置解决方案。

接下来跳过下面一大段关于 command 配置的代码,直接看后面的部分。

flags.SetDefaultHealthCheckPort(collector.CollectorDefaultHealthCheckHTTPPort)

config.AddFlags(
	v,
	command,
	flags.AddConfigFileFlag,
	flags.AddFlags,
	storageFactory.AddFlags,
	agentApp.AddFlags,
	agentRep.AddFlags,
	agentTchanRep.AddFlags,
	agentGrpcRep.AddFlags,
	collector.AddFlags,
	queryApp.AddFlags,
	pMetrics.AddFlags,
	strategyStoreFactory.AddFlags,
)

在这里设置了默认的健康检查的端口,并在AddFlags方法中,将一些默认的参数写入到之前新建的 viper 当中。

写入配置参数之后,准备工作也就完成了。接下来就到了启动阶段。

启动

启动的方式使用了cobra配置的命令行,直接看RunE方法。

sFlags := new(flags.SharedFlags).InitFromViper(v)
logger, err := sFlags.NewLogger(zap.NewProductionConfig())

初始化日志配置,默认的日志级别是 info。

hc, err := sFlags.NewHealthCheck(logger)

监听健康检查接口。

mBldr := new(pMetrics.Builder).InitFromViper(v)
// 实例化Prometheus工厂
rootMetricsFactory, err := mBldr.CreateMetricsFactory("")
// 嵌套创建一个工厂,以上面的Prometheus工厂为父工厂
metricsFactory := rootMetricsFactory.Namespace(metrics.NSOptions{Name: "jaeger", Tags: nil})

metrics 收集相关配置,默认的 metrics 服务是 Prometheus。

storageFactory.InitFromViper(v)
if err := storageFactory.Initialize(metricsFactory, logger); err != nil {
	logger.Fatal("Failed to init storage factory", zap.Error(err))
}

初始化存储工厂配置,其实就是返回一个初始化好的结构体,在这里这个结构体是memory类型的,这个结构体中实现了 span 的ReaderWriter和 dependency 的Reader接口。

spanReader, err := storageFactory.CreateSpanReader()

spanWriter, err := storageFactory.CreateSpanWriter()

dependencyReader, err := storageFactory.CreateDependencyReader()

这里是实例化上面说的 span 的ReaderWriter和 dependency 的Reader接口这三个接口。我们具体看下这三个接口是如何实例化的。(仅限于基于 memory 的实现)

func (f *Factory) CreateSpanReader() (spanstore.Reader, error) {
	factory, ok := f.factories[f.SpanReaderType]
	if !ok {
		return nil, fmt.Errorf("No %s backend registered for span store", f.SpanReaderType)
	}
	return factory.CreateSpanReader()
}

func (f *Factory) CreateSpanReader() (spanstore.Reader, error) {
	return f.store, nil
}

从上面的代码中可以看出,CreateSpanReader方法直接将store结构返回了,因为这个结构里面已经实现了 reader 相关接口。

func (f *Factory) CreateSpanWriter() (spanstore.Writer, error) {
	var writers []spanstore.Writer
	for _, storageType := range f.SpanWriterTypes {
		factory, ok := f.factories[storageType]
		if !ok {
			return nil, fmt.Errorf("No %s backend registered for span store", storageType)
		}
		writer, err := factory.CreateSpanWriter()
		if err != nil {
			return nil, err
		}
		writers = append(writers, writer)
	}
	if len(f.SpanWriterTypes) == 1 {
		return writers[0], nil
	}
	return spanstore.NewCompositeWriter(writers...), nil
}

func (f *Factory) CreateSpanWriter() (spanstore.Writer, error) {
	return f.store, nil
}

SpanWriter 的逻辑和 reader 的逻辑大致一样,不同的是 writer 可能会有多个,所以需要放到切片当中。

func (f *Factory) CreateDependencyReader() (dependencystore.Reader, error) {
	factory, ok := f.factories[f.DependenciesStorageType]
	if !ok {
		return nil, fmt.Errorf("No %s backend registered for span store", f.DependenciesStorageType)
	}
	return factory.CreateDependencyReader()
}

func (f *Factory) CreateDependencyReader() (dependencystore.Reader, error) {
	return f.store, nil
}

DependencyReader 和 SpanReader 逻辑一致。

下面是采样策略工厂的实例化。

strategyStoreFactory.InitFromViper(v)
strategyStore := initSamplingStrategyStore(strategyStoreFactory, metricsFactory, logger)

首先看下InitFromViper方法。

func (f *Factory) InitFromViper(v *viper.Viper) {
	for _, factory := range f.factories {
		if conf, ok := factory.(plugin.Configurable); ok {
			conf.InitFromViper(v)
		}
	}
}

// static/factory.go
func (f *Factory) InitFromViper(v *viper.Viper) {
	f.options.InitFromViper(v)
}

func (opts *Options) InitFromViper(v *viper.Viper) *Options {
	opts.StrategiesFile = v.GetString(samplingStrategiesFile)
	return opts
}

首先遍历所有之前存入的工厂,然后进到InitFromViper方法的实现当中。这里是进入到static/factory.go这个实现方法里面。然后取出samplingStrategiesFile对应的值,放入到opts变量当中。在这里这个值是空字符串。

接下来是initSamplingStrategyStore方法。

func initSamplingStrategyStore(
	samplingStrategyStoreFactory *ss.Factory,
	metricsFactory metrics.Factory,
	logger *zap.Logger,
) strategystore.StrategyStore {
	if err := samplingStrategyStoreFactory.Initialize(metricsFactory, logger); err != nil {
		logger.Fatal("Failed to init sampling strategy store factory", zap.Error(err))
	}
	strategyStore, err := samplingStrategyStoreFactory.CreateStrategyStore()
	if err != nil {
		logger.Fatal("Failed to create sampling strategy store", zap.Error(err))
	}
	return strategyStore
}

上面的代码做了两件事情,调用Initialize方法,这个方法的在这里只是将 logger 赋值 factor 内变量,不做深入到讨论。另外一个方法CreateStrategyStore是新建一个采样策略存储,这里用的是静态策略,然后把这些策略存储起来。下面省略几步跳转,直接进入'strategy_store.go代码里面。

func NewStrategyStore(options Options, logger *zap.Logger) (ss.StrategyStore, error) {
	h := &strategyStore{
		logger:            logger,
		serviceStrategies: make(map[string]*sampling.SamplingStrategyResponse),
	}
	strategies, err := loadStrategies(options.StrategiesFile)
	if err != nil {
		return nil, err
	}
	h.parseStrategies(strategies)
	return h, nil
}

首先新建了一个 strategyStore的数据结构,其中serviceStrategies是一个 map 结构的变量,存储的就是具体服务的采样策略。map 的 key 是服务的名称。在loadStrategies方法中,根据在 viper 中配置的策略路径来读取文件,这里的路径为空,所以这个方法就直接返回,返回的数值为空。

接下来的parseStrategies方法中,由于入参为空,所以将采样策略赋值为默认参数,并返回。

defaultStrategy = sampling.SamplingStrategyResponse{
	StrategyType: sampling.SamplingStrategyType_PROBABILISTIC,
	ProbabilisticSampling: &sampling.ProbabilisticSamplingStrategy{
		SamplingRate: defaultSamplingProbability,
	},
}

默认的采样策略是概率采样,使用的采样概率是 0.001。

接下来是从 viper 中获取参数,为下面初始化做准备。

// agent相关参数
aOpts := new(agentApp.Builder).InitFromViper(v)
// agent上报给query的请求类型,是grpc还是tchannel
repOpts := new(agentRep.Options).InitFromViper(v)
// 设置tchannel连接参数
tchannelRepOpts := agentTchanRep.NewBuilder().InitFromViper(v, logger)
// 设置grpc连接参数
grpcRepOpts := new(agentGrpcRep.Options).InitFromViper(v)
// collector相关参数
cOpts := new(collector.CollectorOptions).InitFromViper(v)
// query相关参数
qOpts := new(queryApp.QueryOptions).InitFromViper(v)

接下来分别启动这三个服务。

startAgent(aOpts, repOpts, tchannelRepOpts, grpcRepOpts, cOpts, logger, metricsFactory)
grpcServer := startCollector(cOpts, spanWriter, logger, metricsFactory, strategyStore, hc)
startQuery(qOpts, spanReader, dependencyReader, logger, rootMetricsFactory, metricsFactory, mBldr, hc, archiveOptions(storageFactory, logger))

分别看下这几个启动的过程。

启动 Agent
func startAgent(
	b *agentApp.Builder,
	repOpts *agentRep.Options,
	tchanRep *agentTchanRep.Builder,
	grpcRepOpts *agentGrpcRep.Options,
	cOpts *collector.CollectorOptions,
	logger *zap.Logger,
	baseFactory metrics.Factory,
) {
	// 创建特定的metrics工厂
	metricsFactory := baseFactory.Namespace(metrics.NSOptions{Name: "agent", Tags: nil})

	cp, err := createCollectorProxy(cOpts, repOpts, tchanRep, grpcRepOpts, logger, metricsFactory)
	if err != nil {
		logger.Fatal("Could not create collector proxy", zap.Error(err))
	}

	agent, err := b.CreateAgent(cp, logger, baseFactory)
	if err != nil {
		logger.Fatal("Unable to initialize Jaeger Agent", zap.Error(err))
	}

	logger.Info("Starting agent")
	if err := agent.Run(); err != nil {
		logger.Fatal("Failed to run the agent", zap.Error(err))
	}
}

func createCollectorProxy(
	cOpts *collector.CollectorOptions,
	repOpts *agentRep.Options,
	tchanRepOpts *agentTchanRep.Builder,
	grpcRepOpts *agentGrpcRep.Options,
	logger *zap.Logger,
	mFactory metrics.Factory,
) (agentApp.CollectorProxy, error) {
	switch repOpts.ReporterType {
	case agentRep.GRPC:
		grpcRepOpts.CollectorHostPort = append(grpcRepOpts.CollectorHostPort, fmt.Sprintf("127.0.0.1:%d", cOpts.CollectorGRPCPort))
		return agentGrpcRep.NewCollectorProxy(grpcRepOpts, mFactory, logger)
	case agentRep.TCHANNEL:
		tchanRepOpts.CollectorHostPorts = append(tchanRepOpts.CollectorHostPorts, fmt.Sprintf("127.0.0.1:%d", cOpts.CollectorPort))
		return agentTchanRep.NewCollectorProxy(tchanRepOpts, mFactory, logger)
	default:
		return nil, errors.New(fmt.Sprintf("unknown reporter type %s", string(repOpts.ReporterType)))
	}
}

在启动 Agent 服务之前,首先调用createCollectorProxy方法创建了 Collector 的代理,用于向 Collector 上报数据。在这个方法中,根据通信协议的类型创建不同的代理。

接下来调用CreateAgent方法创建一个 Agent 实例,在入参中包含刚刚创建的 Collector 代理。由于创建 Agent 实例属于 Agent 模块范围,这个方法以后再详细展开。

如果上述过程都没有错误,那么就启动 Agent。

启动 Collector

启动 Collector 的代码较长,下面分步拆解一下。

	spanBuilder, err := collector.NewSpanHandlerBuilder(
		cOpts,
		spanWriter,
		basic.Options.LoggerOption(logger),
		basic.Options.MetricsFactoryOption(metricsFactory),
	)
	zipkinSpansHandler, jaegerBatchesHandler, grpcHandler := spanBuilder.BuildHandlers()

新建一个 spanBuilder,并创建了三个 handler。其中zipkinSpansHandlerjaegerBatchesHandler 实现了 TChanCollector 接口,可以处理 Tchan RPC 的调用。

	{
		ch, err := tchannel.NewChannel("jaeger-collector", &tchannel.ChannelOptions{})
		server := thrift.NewServer(ch)
		server.Register(jc.NewTChanCollectorServer(jaegerBatchesHandler))
		server.Register(zc.NewTChanZipkinCollectorServer(zipkinSpansHandler))
		// 设置一个handler可以处理采样策略
		server.Register(sc.NewTChanSamplingManagerServer(sampling.NewHandler(strategyStore)))
		portStr := ":" + strconv.Itoa(cOpts.CollectorPort)
		listener, err := net.Listen("tcp", portStr)
		logger.Info("Starting jaeger-collector TChannel server", zap.Int("port", cOpts.CollectorPort))
		// 启动tchan服务
		ch.Serve(listener)
	}

上面的代码是启动一个 tchannel,可以处理 zipkinSpansHandlerjaegerBatchesHandler

接下来启动 GRPC 服务。

func startGRPCServer(
	port int,
	handler *collectorApp.GRPCHandler,
	samplingStore strategystore.StrategyStore,
	logger *zap.Logger,
) (*grpc.Server, error) {
	server := grpc.NewServer()
	// 传入grpc handler
	_, err := grpcserver.StartGRPCCollector(port, server, handler, samplingStore, logger, func(err error) {
		logger.Fatal("gRPC collector failed", zap.Error(err))
	})
	if err != nil {
		return nil, err
	}
	return server, err
}

后面的代码是用来处理 Zipkin Http Api 的,Zipkin 服务可以直接将数据上报到 Collecotor,这里就不在展开。

到这里,Agent 组件就启动完成了,一共启动了三个服务,Tchannel、GRPC、和一个专门处理 Zipkin 的 Http 服务。

启动Query
	tracer, closer, err := jaegerClientConfig.Configuration{
		Sampler: &jaegerClientConfig.SamplerConfig{
			Type:  "const",
			Param: 1.0,
		},
		RPCMetrics: true,
	}.New(
		"jaeger-query",
		jaegerClientConfig.Metrics(rootFactory),
		jaegerClientConfig.Logger(jaegerClientZapLog.NewLogger(logger)),
	)
	opentracing.SetGlobalTracer(tracer)

上面的代码首先创建了一个 tracer,用来收集自身的信息并上报给 Agent 组件。

spanReader = storageMetrics.NewReadMetricsDecorator(spanReader, baseFactory.Namespace(metrics.NSOptions{Name: "query", Tags: nil}))

接下来在spanReader中添加了一些 metrics 相关的方法,以便收集运行中的指标信息。这里用到了装饰器的设计模式。

handlerOpts = append(handlerOpts, queryApp.HandlerOptions.Logger(logger), queryApp.HandlerOptions.Tracer(tracer))
apiHandler := queryApp.NewAPIHandler(
	spanReader,
	depReader,
	handlerOpts...)

r := mux.NewRouter()
if qOpts.BasePath != "/" {
	r = r.PathPrefix(qOpts.BasePath).Subrouter()
}
// 注册 url
apiHandler.RegisterRoutes(r)
queryApp.RegisterStaticHandler(r, logger, qOpts)
// 注册处理metrics的handler
if h := metricsBuilder.Handler(); h != nil {
		logger.Info("Registering metrics handler with jaeger-query HTTP server", zap.String("route", metricsBuilder.HTTPRoute))
		r.Handle(metricsBuilder.HTTPRoute, h)
	}

在上面的代码中,apiHandler相当与是所有内置 API 的合集,同时也承接着转发流量的功能。

go func() {
		defer closer.Close()
		if err := http.ListenAndServe(portStr, recoveryHandler(r)); err != nil {
			logger.Fatal("Could not launch jaeger-query service", zap.Error(err))
		}
		hc.Set(healthcheck.Unavailable)
	}()

最后启动一个协程,在里面运行 http 服务。

三个组件都启动后,jaeger all-in-one 就成功启动了。如果有数据汇报给了 agent 组件,那么打开网页就会看到追踪的情况。

参考文档

Golang的 signal
SIGKILL和SIGTERM、SIGINT

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值