在Grafana全家桶系列向大家介绍了在Grafana生态中用于支持链路跟踪的后端服务Grafana Tempo,本系列将会结合着生产实践中遇到的问题来阅读Grafana Tempo源码,了解其代码实现逻辑。
在使用Grafana Tempo投入生产实践中时,我们存在着大量的Trace数据写入(占有空间约20T),这个时候出现了前后端Trace无法串起来或者查询慢、404问题。在对官方提供的文档进行研究后仍然无法彻底解决,说明对各项配置的理解还不到位。在资料有限的情况下,只能通过阅读源码来深入理解Grafana Tempo的运行机制和配置参数的含义,来解决生产实践中遇到的各种问题。
在Grafana全家桶(二)链路跟踪Grafana Tempo的介绍和部署一文中向大家介绍了Grafana Tempo及其架构,如下图:

本系列将从以下几个角度开展阅读Grafana Tempo源码:
Trace写入的流程,解读数据被Distributor组件接收后,如何发送至Ingester组件做持久化存储。
Grafana前端发起Trace查询请求后,读取数据的流程,数据流如何从存储、Ingester组件返回至Querier组件和Query Frontend组件。
了解Metrics generator如何从Distributor接收Trace,然后如何处理成Metrics写入时序数据库。
了解Compactor如何根据各项配置对存储中的数据进行删除处理
通过代码逻辑解读,了解Grafana Tempo整个数据读写的核心流程以及各项配置的作用。针对配置对Grafana Tempo各组件做优化调整。
Distributor程序启动
本章节将从Distributor组件的源码阅读起,了解Trace数据到达Distributor组件后,Distributor组件如何接收处理的。
程序入口 cmd\tempo\main.go
加载并校验配置 modules\distributor\config.go

根据配置初始化APP,启动Tempo服务

加载配置
loadconfig()中首先读取命令行参数并设置一些参数的初始值
根据命令行参数-target指定Tempo服务运行的模块,当前代码以Distributor模块进行讲解。

根据命令行参数-config.file指定读取的配置文件路径,并将值序列化至config

UnmarshalStrict会对config在加载命令行参数时初始化的值进行覆盖,而在配置中不存在的配置项则会采用默认配置。
配置项的含义和作用在后续代码流程用到的地方进行讲解。不在此对每项进行详细解读。
初始化APP
初始化Middleware
setupAuthMiddleware()
开启多租户则从header中读取租户名称,并在context中将租户名称保存为orgIDContextKey的Key中。

若未开启多租户则设置,则取默认租户名称single-tenant设置至orgIDContextKey

Middleware的下一步处理是由consumer.Traces的ConsumerTraces(ctx, td)进行处理。
启动ModuleManager
setupModuleManager()
注册每个module的初始化函数initXXX(), 并构建每个module依赖的module,依赖关系如下:

Distributor模块依赖Common, IngesterRing, MetricsGeneratorRing,同时每个模块又有自己的依赖。
在后续运行Tempo服务将会初始化Distributor依赖的模块和其自身模块。
运行APP
根据target初始化Module
由于Distributor依赖的模块本身又有依赖,这些依赖有重复依赖,还存在前后的依赖顺序关系,在initModule根据依赖关系获取从最底层到最上层的依赖关系顺序。这两个函数的处理过程大家可以结合上面的依赖图谱来进行理解,里面的逻辑有些绕。

有了依赖顺序关系后,进行遍历依次获取对应模块的service

在初始化模块的service时,还有两个函数的处理DependenciesForModule、inverseDependenciesForModule
这两个函数是获取当前模块所依赖的所有模块和获取依赖当前模块的所有模块,并赋值给moduleService的startDeps、stopDeps
在moduleService里可以看到在启动service时会根据startDeps依次启动以来的service

在停止service时,会按照被依赖的顺序依次等待被依赖的service已经停止。

serviceManager
注册的service将会交由serviceManager进行统一管理

在vendor\github.com\grafana\dskit\services\service.go
中定义了service的生命周期

各个模块的service都实现了该service接口,所以可以被serviceManager进行统一管理。

初始化Distributor
本节对Distributor模块代码的讲解,服务在启动时将会指定参数-target=distributor
在initDistributor()中将会创建distributor对象

在创建distributor时会指定forwarders,forwarders由配置文件定义,指定需要将数据异步写入的组件名称和对应的地址。例如,开启了metrics_generator,Distributor需要将Trace写入metricsGenerator模块。

初始化Distributor时,创建receiver来处理接收http请求、grpc请求传入的Trace数据

Receiver中定义的ConsumeTraces将会接管经过Middleware的请求,进行处理。接收的Trace数据会交给pusher.PushTraces()

当数据流扭转至Distributor的PushTraces方法后,将进入Distributor的处理核心逻辑。在PushTraces方法内,会做数据的写入限流逻辑判断,数据格式转化,计算hash key,然后根据hash key将数据通过hash一致性算法发送给指定的Ingester的过程。

PushTraces方法详细的处理过程将在下一章节进行详细解读,了解各项处理依赖哪些配置,以及处理的逻辑。
本节对Distributor接收数据的处理过程进行讲解,通过本节大家基本了解:
Grafana Tempo的代码结构
如何通过一套代码实现各个组件(Distributor、Ingester、MetricsGenerator、Compactor、QueryFronted、Querier)的注册
通过启动参数
-target
指定对应的组件模块运行。了解了Distributor数如何接收数据和扭转的。




