在阅读查询过程的代码时,许多操作与Block的处理有关。如果对于Tempo如何将Block进行压缩和删除逻辑不理解,无法理解其中的查询逻辑。所以本节先对Tempo Compactor组件的代码进行阅读讲解,了解Compactor的结构和其同步Block的过程,为了解其如何对Block进行压缩和删除的逻辑做铺垫。
类关系图谱

Compactor模块只依赖Store模块。所以Compactor所有的操作都是依赖storage.Store对象完成。在上面结构图里着重体现的也是storage.Store的组成。
storage.Store结构中有三个重要的对象完成Compactor的压缩和清理过程,分别是tempodb.Reader
、tempodb.Writer
、tempodb.Compactor
。
这三个对象都是由tempodb.readerWriter
定义,且是一个共有的对象。在tempodb.readerWriter
中定义了:
负责从对象存储读取Block的
r backend.Reader负责对对象存储的Block进行删除或者压缩后写入操作的
w backend.Writer负责执行对哪些Block进行压缩、确定哪些Block可以删除的逻辑操作
c backend.Compactor
blocklistPoller *blocklist.Poller
负责周期性的通过reader遍历所有的Block,并写入blocklist *blocklist.List
,在blocklist中维护了两个结构metas
、compactedMetas
。从对象存储中读取到的Block如果没有被压缩就会被添加至metas
,对metas进行压缩后会写入compactedMetas
。
以上就是Compactor组件运行过程中所依赖的结构的层级关系,以及各对象的职责划分。
EnablePolling
在Compactor组件的starting函数里会调用store.EnablePolling
开启定时从存储中同步Blocks。如果不开启compactor(不建议在生产环境使用),则不会执行该操作。
程序启动后会直接开始同步Blocks,同时会根据配置blocklist_poll
(默认5分钟)的时间定时同步。

通过block的父目录可以获取到有哪些租户,遍历每个租户同步租户的blockList。
pollTenantBlocks
获取currentBlockIDs, currentCompactedBlockIDs

通过遍历对象存储中租户下的目录获取blockID列表,已经根据该目录下存在meta.json文件还是meta.compacted.json文件确定将blockappend至blockIDs、compactedBlockIDs。返回赋值给currentBlockIDs
,currentCompactedBlockIDs
。

previous *List
由于pollTenantBlocks会周期性的调用,previous中存放了最近一次同步、或者处理后的结果,其结构如下,其中每个属性都在后续的逻辑处理中有重要的作用,已添加注释说明:
// PerTenant is a map of tenant ids to backend.BlockMetas
type PerTenant map[string][]*backend.BlockMeta
// PerTenantCompacted is a map of tenant ids to backend.CompactedBlockMetas
type PerTenantCompacted map[string][]*backend.CompactedBlockMeta
type List struct {
mtx sync.Mutex
metas PerTenant 当前租户的Blocks
compactedMetas PerTenantCompacted 当前租户的compactedBlocks
// used by the compactor to track local changes it is aware of
added PerTenant 新添加的Blocks
removed PerTenant 转化为compactedBlocks的Blocks
compactedAdded PerTenantCompacted // 新添加的Blocks compactedBlocks
compactedRemoved PerTenantCompacted // 达到保留时间,待删除的compactedBlocks
}
c backend.Compactor
负责执行对哪些Block进行压缩、确定哪些Block可以删除的逻辑操作,最终对List上述的各个成员属性进行动态维护。
previous确定newBlockList、newCompactedBlocklist、unknownBlockIDs
遍历获取到的currentBlockIDs,如果在previous.meta里,添加至newBlockList,如果不在previous.meta里,则设置unknownBlockIDs[blockID] = false
(这里的false表示这时候可以确定这个block是要添加至List.metas属性里的)
遍历获取到的currentCompactedBlockIDs,如果在previous.compactedMetas里,则添加至newCompactedBlocklist,如果不在,那么就没法确定其真实状态了,设置unknownBlockIDs[blockID] = true
。

pollUnknown 确定unknownBlockIDs的真实身份
如果为false(还不确定身份),再次从对象存储里面读取是否存在meta.json。
如果不存在,那么或许是在之前的处理步骤中异常了,根据当前block的内容重新渲染meta.compacted.json文件并返回:

从对象存储里面读取Blocks时是根据配置blocklist_poll_concurrency
(默认50)并发获取Blocks,boundedwaitgroup通过channel实现最多支持多少个并发同时进行,超过并发的阻塞等待。
bg = boundedwaitgroup.New(p.cfg.PollConcurrency)
最终确定blocks是属于newBlockList
,还是newCompactedBlocklist
。
最后将最新的newBlockList
、newCompactedBlocklist
按照StartTime进行排序后返回。
ApplyPollResults 维护List的各个成员
将newBlockList
、newCompactedBlocklist
赋值给metas
、compactedMetas
.

ApplyPollResults 函数在对Block进行压缩处理后也会调用,所以这里有更新l.added, l.removed, l.compactedAdded, l.compactedRemoved。在上述同步Blocks的过程中没有对这些属性的处理过程,我们暂且认为这些参数为空,在后面对Block压缩的过程再进行讲解。
到这里完成了Compactor组件中各Go结构体(类)的定义和关系和Compactor组件如何同步Block的源码解读。下一节将继续对Compactor组件的源码进行讲解,了解Compactor组件对同步的Block是如何进行压缩、清理处理的。
往期回顾:
Grafana Tempo源码解读(五)总结Tempo接收数据的限制以及配置调整
Grafana Tempo源码解读(四)Ingester组件将数据写入持久化存储
Grafana Tempo源码解读(三)Ingester组件接收Trace数据的过程
Grafana Tempo源码解读(二)Distributor对Trace数据的处理和发送至Ingester
Grafana Tempo源码解读(一)Distributor建立监听接收Trace数据




