Prometheus源码专题【左扬精讲】—— 监控系统 Prometheus 3.4.0 源码解析:head_wal.go 的 WAL 写入策略与缓存管理源码解读

Prometheus源码专题【左扬精讲】—— 监控系统 Prometheus 3.4.2 源码解析:head_wal.go 的 WAL 写入与缓存策略源码解读

https://github.com/prometheus/prometheus/blob/v3.4.2/tsdb/head_wal.go

        以下代码源自 Prometheus TSDB(Time Series Database,时间序列数据库)的头部存储(head_wal.go)模块TSDB 实现数据持久化、故障恢复与高效读写的核心组件

        该模块主要承担三方面核心职责:

      • WAL(Write-Ahead Log,预写日志)回放:TSDB 启动时,通过回放 WAL 恢复内存中未持久化的时间序列、样本、直方图等数据,保证数据不丢失;
      • WBL(Out-of-Order WAL,乱序预写日志)处理:专门处理乱序到达的样本,通过独立的并发逻辑将乱序数据整合到内存存储中;
      • Chunk Snapshot(块快照)管理:定期将内存中的时间序列、墓碑(删除标记)、示例(Exemplar)数据生成快照并持久化到磁盘,减少 WAL 回放耗时,加速启动流程。

一、基础数据结构:

1.1、histogramRecord:直方图样本统一封装

https://github.com/prometheus/prometheus/blob/v3.4.2/tsdb/head_wal.go#L48

// histogramRecord 整合两种直方图样本类型(RefHistogramSample 与 RefFloatHistogramSample)
// 设计目的:WAL 回放时,避免对「普通直方图」和「浮点直方图」编写重复处理逻辑,简化代码结构
type histogramRecord struct {
	ref chunks.HeadSeriesRef       // 关联的时间序列引用 ID(用于定位内存中的具体时间序列)
	t   int64                      // 样本时间戳(Unix 时间戳,精确到毫秒/微秒,取决于 TSDB 配置)
	h   *histogram.Histogram       // 普通直方图样本(桶边界为整数,适用于常规数值类型指标)
	fh  *histogram.FloatHistogram  // 浮点直方图样本(桶边界为浮点数,适用于高精度/小数指标,如 latency)
}  

1.2、seriesRefSet:线程安全的未知序列跟踪集合

https://github.com/prometheus/prometheus/blob/v3.4.2/tsdb/head_wal.go#L55

// seriesRefSet 用于在多协程环境下,安全跟踪「被其他记录引用但尚未创建的时间序列 ID」
// 核心场景:WAL 日志中记录的顺序可能无序(如样本记录先于其所属的序列记录),需暂存这些「未知序列 ID」,避免数据丢失
type seriesRefSet struct {
	refs map[chunks.HeadSeriesRef]struct{}  // 存储未知序列 ID 的集合(用空结构体占位,节省内存)
	mtx  sync.Mutex                         // 互斥锁:保证多协程并发读写 refs 时的线程安全
}

// merge 方法:将另一个「未知序列 ID 集合」合并到当前集合
// 作用:多协程处理 WAL 分片后,汇总各分片的未知序列 ID,用于最终日志告警
func (s *seriesRefSet) merge(other map[chunks.HeadSeriesRef]struct{}) {
	s.mtx.Lock()         // 加锁:防止并发写冲突
	defer s.mtx.Unlock() // 延迟解锁:确保函数退出时释放锁,避免死锁
	maps.Copy(s.refs, other) // 批量复制其他集合的 ID 到当前集合(maps.Copy 是 Go 1.21+ 提供的安全复制函数)
}

// count 方法:获取当前未知序列 ID 的总数
// 作用:最终统计未知序列数量,用于日志输出(如 "Unknown series references: 100")
func (s *seriesRefSet) count() int {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	return len(s.refs) // 直接返回集合长度,无额外计算开销
} 

1.3、counterAddNonZero:Prometheus 计数器安全更新工具

https://github.com/prometheus/prometheus/blob/v3.4.2/tsdb/head_wal.go#L72

// counterAddNonZero 是 Prometheus 计数器(CounterVec)的专用更新函数
// 核心逻辑:仅当增加值 > 0 时才更新计数器,避免无效的 0 值更新(减少指标采集与存储开销)
// 参数说明:
//   - v: 目标计数器(CounterVec,带标签的计数器,如 wal_replay_unknown_refs_total)
//   - value: 要增加的数值(需为非负,因 Counter 仅支持递增)
//   - lvs: 计数器标签值(如 "series"、"samples",用于区分不同类型的未知引用)
func counterAddNonZero(v *prometheus.CounterVec, value float64, lvs ...string) {
	if value > 0 { // 过滤 0 值更新:避免无意义的指标上报
		v.WithLabelValues(lvs...).Add(value) // 按标签维度更新计数器
	}
}  

二、WAL 回放核心函数:loadWAL

2.1、函数功能与参数说明

// loadWAL 是 TSDB 启动时恢复数据的核心函数:通过回放 WAL 日志,恢复内存中时间序列、样本、直方图等数据
// 关键作用:保证 TSDB 重启后,未持久化到磁盘的内存数据不丢失(WAL 是 TSDB 数据一致性的核心保障)
// 参数详解:
//   - r: wlog.Reader 类型,WAL 日志读取器(负责从磁盘读取 WAL 段文件内容)
//   - syms: labels.SymbolTable 类型,标签符号表(将重复标签字符串编码为整数,减少 WAL 存储与解码开销)
//   - multiRef: map[chunks.HeadSeriesRef]chunks.HeadSeriesRef 类型,序列 ID 映射表(处理「重复序列」场景:同一标签组的序列被多次创建,需映射到同一内存序列)
//   - mmappedChunks: map[chunks.HeadSeriesRef][]*mmappedChunk 类型,内存映射块集合(已持久化到磁盘的 Chunk,通过内存映射加载,避免全量读入内存)
//   - oooMmappedChunks: map[chunks.HeadSeriesRef][]*mmappedChunk 类型,乱序内存映射块集合(专门存储乱序样本的持久化 Chunk)
//   - lastSegment: int 类型,最后一个 WAL 段的编号(用于设置序列的 WAL 过期时间,控制 WAL 段清理逻辑)
// 返回值:error 类型,回放过程中的错误(如 WAL 损坏、序列创建失败)
func (h *Head) loadWAL(r *wlog.Reader, syms *labels.SymbolTable, multiRef map[chunks.HeadSeriesRef]chunks.HeadSeriesRef, mmappedChunks, oooMmappedChunks map[chunks.HeadSeriesRef][]*mmappedChunk, lastSegment int) (err error) {

2.2、初始化跟踪变量 —— 监控回放异常

// -------------------------- 1. 初始化异常跟踪变量 --------------------------
// unknownSeriesRefs:线程安全集合,跟踪「被其他记录引用但尚未创建的序列 ID」(如样本引用了未加载的序列)
unknownSeriesRefs := &seriesRefSet{refs: make(map[chunks.HeadSeriesRef]struct{}), mtx: sync.Mutex{}}

// 以下原子变量(atomic.Uint64)用于统计不同类型记录的「未知序列引用数」(原子操作保证多协程计数准确)
var unknownSampleRefs atomic.Uint64       // 样本记录引用的未知序列数
var unknownExemplarRefs atomic.Uint64     // 示例(Exemplar)记录引用的未知序列数
var unknownHistogramRefs atomic.Uint64    // 直方图记录引用的未知序列数
var unknownMetadataRefs atomic.Uint64     // 元数据(如指标类型、帮助信息)记录引用的未知序列数
var unknownTombstoneRefs atomic.Uint64    // 墓碑(删除标记)记录引用的未知序列数
var mmapOverlappingChunks atomic.Uint64   // 重复序列的内存映射块重叠数(用于数据一致性检查:如两个重复序列的 Chunk 时间范围重叠)

2.3、初始化并发处理组件 —— 提升回放效率

// -------------------------- 2. 初始化并发处理组件 --------------------------
var (
	wg             sync.WaitGroup          // 协程等待组:确保所有 WAL 处理协程完成后,再执行后续逻辑
	concurrency    = h.opts.WALReplayConcurrency // 回放并发度(配置项,默认与 CPU 核心数一致,充分利用多核资源)
	processors     = make([]walSubsetProcessor, concurrency) // 并发处理器数组:按序列 ID 分片处理,避免单协程瓶颈
	exemplarsInput chan record.RefExemplar // 示例数据输入通道(单独处理示例,避免阻塞样本/直方图处理流程)

	// 分片缓冲区:将样本/直方图按序列 ID 哈希分片,分发到不同处理器(负载均衡)
	shards          = make([][]record.RefSample, concurrency)       // 样本分片缓冲区
	histogramShards = make([][]histogramRecord, concurrency)       // 直方图分片缓冲区

	decoded                      = make(chan interface{}, 10) // 解码后数据通道(缓冲大小 10,减少解码协程阻塞)
	decodeErr, seriesCreationErr error                        // 全局错误变量:解码错误、序列创建错误(需跨协程传递)
)  

2.4、延迟清理 —— 异常退出时的资源释放

// -------------------------- 3. 延迟清理:处理异常退出场景 --------------------------
defer func() {
	// 若发生 WAL 损坏错误(*wlog.CorruptionErr)或序列创建错误,需主动清理资源,避免协程泄漏
	_, isCorruption := err.(*wlog.CorruptionErr)
	if isCorruption || seriesCreationErr != nil {
		// 关闭所有处理器并清空通道(确保处理器协程正常退出)
		for i := 0; i < concurrency; i++ {
			processors[i].closeAndDrain()
		}
		close(exemplarsInput) // 关闭示例输入通道
		wg.Wait()             // 等待所有协程完成,避免资源泄漏
	}
}()

2.5、启动样本并发处理器 —— 分片处理样本

// -------------------------- 4. 启动样本并发处理器协程 --------------------------
wg.Add(concurrency) // 等待组计数器 += 并发度(每个处理器对应一个协程)
for i := 0; i < concurrency; i++ {
	processors[i].setup() // 初始化处理器:创建输入/输出通道,分配缓冲区

	// 为每个处理器启动独立协程:处理分配给它的序列分片
	go func(wp *walSubsetProcessor) {
		// 核心逻辑:处理分片内的样本/直方图,返回异常统计(未知序列、重叠块等)
		missingSeries, unknownSamples, unknownHistograms, overlapping := wp.processWALSamples(h, mmappedChunks, oooMmappedChunks)
		// 汇总异常统计:将本分片的未知序列合并到全局集合
		unknownSeriesRefs.merge(missingSeries)
		unknownSampleRefs.Add(unknownSamples)       // 累加未知样本引用数
		mmapOverlappingChunks.Add(overlapping)     // 累加 Chunk 重叠数
		unknownHistogramRefs.Add(unknownHistograms) // 累加未知直方图引用数
		wg.Done() // 协程完成,等待组计数器 -= 1
	}(&processors[i])
}

2.6、启动示例(Exemplar)独立处理器

// -------------------------- 5. 启动示例(Exemplar)处理协程 --------------------------
wg.Add(1) // 等待组计数器 += 1(示例处理为独立协程)
exemplarsInput = make(chan record.RefExemplar, 300) // 带缓冲通道(大小 300),减少生产者阻塞
go func(input <-chan record.RefExemplar) {
	missingSeries := make(map[chunks.HeadSeriesRef]struct{}) // 本协程内的未知序列集合
	var err error
	defer wg.Done() // 协程退出时,等待组计数器 -= 1

	// 循环读取示例数据,直到通道关闭
	for e := range input {
		// 检查示例关联的序列是否存在(通过序列 ID 从内存中查询)
		ms := h.series.getByID(e.Ref)
		if ms == nil {
			unknownExemplarRefs.Inc()       // 累加未知示例引用数
			missingSeries[e.Ref] = struct{}{} // 暂存未知序列 ID
			continue
		}

		// 向序列添加示例:仅忽略「乱序示例」错误(WAL 回放应按时间顺序,乱序为异常但不阻断流程)
		err = h.exemplars.AddExemplar(
			ms.labels(), // 序列标签(用于示例关联)
			exemplar.Exemplar{Ts: e.T, Value: e.V, Labels: e.Labels}, // 示例数据(时间戳、值、标签)
		)
		// 若错误不是「乱序示例」,则打印告警日志(乱序示例在 WAL 回放中罕见,需关注)
		if err != nil && !errors.Is(err, storage.ErrOutOfOrderExemplar) {
			h.logger.Warn("Unexpected error when replaying WAL on exemplar record", "err", err)
		}
	}
	unknownSeriesRefs.merge(missingSeries) // 合并本协程的未知序列到全局
}(exemplarsInput)

2.7、启动 WAL 解码协程 —— 解析日志为结构化数据

// 启动一个匿名协goroutine负责解码WAL日志记录并发送到decoded通道
go func() {
    // 延迟关闭decoded通道,通知主流程解码已完成
    defer close(decoded)
    var err error
    // 创建基于符号表的解码器(用于高效解码标签)
    dec := record.NewDecoder(syms)
    
    // 循环读取WAL中的每一条记录
    for r.Next() {
        // 根据记录类型选择对应的解码逻辑
        switch dec.Type(r.Record()) {
        case record.Series: // 处理序列记录
            // 从对象池获取复用的切片(减少内存分配)
            series := h.wlReplaySeriesPool.Get()[:0]
            // 解码序列数据
            series, err = dec.Series(r.Record(), series)
            if err != nil {
                // 包装为WAL损坏错误,包含具体位置信息
                decodeErr = &wlog.CorruptionErr{
                    Err:     fmt.Errorf("decode series: %w", err),
                    Segment: r.Segment(), // 损坏发生的段号
                    Offset:  r.Offset(),  // 段内偏移量
                }
                return // 解码失败,退出协程
            }
            // 将解码后的序列发送到处理通道
            decoded <- series
            
        case record.Samples: // 处理样本记录
            samples := h.wlReplaySamplesPool.Get()[:0]
            samples, err = dec.Samples(r.Record(), samples)
            if err != nil {
                decodeErr = &wlog.CorruptionErr{
                    Err:     fmt.Errorf("decode samples: %w", err),
                    Segment: r.Segment(),
                    Offset:  r.Offset(),
                }
                return
            }
            decoded <- samples
            
        case record.Tombstones: // 处理墓碑记录(删除标记)
            tstones := h.wlReplaytStonesPool.Get()[:0]
            tstones, err = dec.Tombstones(r.Record(), tstones)
            if err != nil {
                decodeErr = &wlog.CorruptionErr{
                    Err:     fmt.Errorf("decode tombstones: %w", err),
                    Segment: r.Segment(),
                    Offset:  r.Offset(),
                }
                return
            }
            decoded <- tstones
            
        case record.Exemplars: // 处理示例记录
            exemplars := h.wlReplayExemplarsPool.Get()[:0]
            exemplars, err = dec.Exemplars(r.Record(), exemplars)
            if err != nil {
                decodeErr = &wlog.CorruptionErr{
                    Err:     fmt.Errorf("decode exemplars: %w", err),
                    Segment: r.Segment(),
                    Offset:  r.Offset(),
                }
                return
            }
            decoded <- exemplars
            
        case record.HistogramSamples, record.CustomBucketsHistogramSamples: // 处理普通直方图记录
            hists := h.wlReplayHistogramsPool.Get()[:0]
            hists, err = dec.HistogramSamples(r.Record(), hists)
            if err != nil {
                decodeErr = &wlog.CorruptionErr{
                    Err:     fmt.Errorf("decode histograms: %w", err),
                    Segment: r.Segment(),
                    Offset:  r.Offset(),
                }
                return
            }
            decoded <- hists
            
        case record.FloatHistogramSamples, record.CustomBucketsFloatHistogramSamples: // 处理浮点直方图记录
            hists := h.wlReplayFloatHistogramsPool.Get()[:0]
            hists, err = dec.FloatHistogramSamples(r.Record(), hists)
            if err != nil {
                decodeErr = &wlog.CorruptionErr{
                    Err:     fmt.Errorf("decode float histograms: %w", err),
                    Segment: r.Segment(),
                    Offset:  r.Offset(),
                }
                return
            }
            decoded <- hists
            
        case record.Metadata: // 处理元数据记录(指标类型、帮助信息等)
            meta := h.wlReplayMetadataPool.Get()[:0]
            meta, err := dec.Metadata(r.Record(), meta)
            if err != nil {
                decodeErr = &wlog.CorruptionErr{
                    Err:     fmt.Errorf("decode metadata: %w", err),
                    Segment: r.Segment(),
                    Offset:  r.Offset(),
                }
                return
            }
            decoded <- meta
            
        default:
            // 忽略未知类型的记录(兼容未来扩展或旧版本日志)
        }
    }
}()

// 记录按从旧到新的顺序回放
// 存储本流程中发现的未知序列ID
missingSeries := make(map[chunks.HeadSeriesRef]struct{})

// Outer标签用于跳出多层循环
Outer:
// 循环处理解码后的记录
for d := range decoded {
    // 根据记录类型进行分发处理
    switch v := d.(type) {
    case []record.RefSeries: // 处理序列记录
        for _, walSeries := range v {
            // 根据WAL中的序列ID和标签获取或创建内存序列
            // 最后一个参数false表示:若序列已存在则不更新标签
            mSeries, created, err := h.getOrCreateWithID(walSeries.Ref, walSeries.Labels.Hash(), walSeries.Labels, false)
            if err != nil {
                seriesCreationErr = err // 记录序列创建错误
                break Outer             // 跳出外层循环,终止处理
            }

            // 更新全局最大序列ID,确保后续创建的ID不重复
            if chunks.HeadSeriesRef(h.lastSeriesID.Load()) < walSeries.Ref {
                h.lastSeriesID.Store(uint64(walSeries.Ref))
            }
            
            // 处理重复序列(同一标签组的序列被多次创建)
            if !created {
                multiRef[walSeries.Ref] = mSeries.ref // 建立ID映射
                // 为重复序列设置WAL过期时间,确保在后续检查点中保留
                h.setWALExpiry(walSeries.Ref, lastSegment)
            }

            // 按序列ID哈希分片,分发到对应的处理器
            idx := uint64(mSeries.ref) % uint64(concurrency)
            processors[idx].input <- walSubsetProcessorInputItem{
                walSeriesRef:  walSeries.Ref,
                existingSeries: mSeries,
            }
        }
        // 将处理完的序列切片归还对象池
        h.wlReplaySeriesPool.Put(v)
        
    case []record.RefSample: // 处理样本记录
        samples := v
        // 获取全局最小有效时间(过滤过期样本)
        minValidTime := h.minValidTime.Load()
        
        // 将样本分批处理(每批最多5000个)
        // 防止大批次样本导致内存占用过高
        for len(samples) > 0 {
            m := 5000
            if len(samples) < m {
                m = len(samples)
            }
            
            // 初始化分片缓冲区(复用处理器的缓冲)
            for i := 0; i < concurrency; i++ {
                if shards[i] == nil {
                    shards[i] = processors[i].reuseBuf()
                }
            }
            
            // 遍历样本,按序列ID分片
            for _, sam := range samples[:m] {
                // 过滤过期样本
                if sam.T < minValidTime {
                    continue
                }
                // 替换重复序列的ID(使用映射表)
                if r, ok := multiRef[sam.Ref]; ok {
                    sam.Ref = r
                }
                // 按序列ID哈希计算分片索引
                mod := uint64(sam.Ref) % uint64(concurrency)
                shards[mod] = append(shards[mod], sam)
            }
            
            // 将分片后的样本发送到对应处理器
            for i := 0; i < concurrency; i++ {
                if len(shards[i]) > 0 {
                    processors[i].input <- walSubsetProcessorInputItem{samples: shards[i]}
                    shards[i] = nil // 清空缓冲区,准备下次使用
                }
            }
            
            // 处理剩余样本
            samples = samples[m:]
        }
        // 将样本切片归还对象池
        h.wlReplaySamplesPool.Put(v)
        
    case []tombstones.Stone: // 处理墓碑记录
        for _, s := range v {
            for _, itv := range s.Intervals {
                // 过滤过期的删除区间
                if itv.Maxt < h.minValidTime.Load() {
                    continue
                }
                // 替换重复序列的ID
                if r, ok := multiRef[chunks.HeadSeriesRef(s.Ref)]; ok {
                    s.Ref = storage.SeriesRef(r)
                }
                // 检查序列是否存在
                if m := h.series.getByID(chunks.HeadSeriesRef(s.Ref)); m == nil {
                    unknownTombstoneRefs.Inc() // 统计未知序列引用
                    missingSeries[chunks.HeadSeriesRef(s.Ref)] = struct{}{}
                    continue
                }
                // 应用删除区间
                h.tombstones.AddInterval(s.Ref, itv)
            }
        }
        // 将墓碑切片归还对象池
        h.wlReplaytStonesPool.Put(v)
        
    case []record.RefExemplar: // 处理示例记录
        for _, e := range v {
            // 过滤过期示例
            if e.T < h.minValidTime.Load() {
                continue
            }
            // 替换重复序列的ID
            if r, ok := multiRef[e.Ref]; ok {
                e.Ref = r
            }
            // 发送到示例处理器通道
            exemplarsInput <- e
        }
        // 将示例切片归还对象池
        h.wlReplayExemplarsPool.Put(v)
        
    case []record.RefHistogramSample: // 处理普通直方图记录
        samples := v
        minValidTime := h.minValidTime.Load()
        
        // 分批处理直方图样本(每批最多5000个)
        for len(samples) > 0 {
            m := 5000
            if len(samples) < m {
                m = len(samples)
            }
            
            // 初始化直方图分片缓冲区
            for i := 0; i < concurrency; i++ {
                if histogramShards[i] == nil {
                    histogramShards[i] = processors[i].reuseHistogramBuf()
                }
            }
            
            // 遍历直方图样本,按序列ID分片
            for _, sam := range samples[:m] {
                // 过滤过期样本
                if sam.T < minValidTime {
                    continue
                }
                // 替换重复序列的ID
                if r, ok := multiRef[sam.Ref]; ok {
                    sam.Ref = r
                }
                // 按序列ID哈希计算分片索引
                mod := uint64(sam.Ref) % uint64(concurrency)
                // 转换为统一的histogramRecord格式
                histogramShards[mod] = append(histogramShards[mod], histogramRecord{
                    ref: sam.Ref,
                    t:   sam.T,
                    h:   sam.H,
                })
            }
            
            // 将分片后的直方图样本发送到对应处理器
            for i := 0; i < concurrency; i++ {
                if len(histogramShards[i]) > 0 {
                    processors[i].input <- walSubsetProcessorInputItem{histogramSamples: histogramShards[i]}
                    histogramShards[i] = nil // 清空缓冲区
                }
            }
            
            // 处理剩余样本
            samples = samples[m:]
        }
        // 将直方图切片归还对象池
        h.wlReplayHistogramsPool.Put(v)
        
    case []record.RefFloatHistogramSample: // 处理浮点直方图记录
        samples := v
        minValidTime := h.minValidTime.Load()
        
        // 分批处理浮点直方图样本(每批最多5000个)
        for len(samples) > 0 {
            m := 5000
            if len(samples) < m {
                m = len(samples)
            }
            
            // 初始化直方图分片缓冲区
            for i := 0; i < concurrency; i++ {
                if histogramShards[i] == nil {
                    histogramShards[i] = processors[i].reuseHistogramBuf()
                }
            }
            
            // 遍历浮点直方图样本,按序列ID分片
            for _, sam := range samples[:m] {
                // 过滤过期样本
                if sam.T < minValidTime {
                    continue
                }
                // 替换重复序列的ID
                if r, ok := multiRef[sam.Ref]; ok {
                    sam.Ref = r
                }
                // 按序列ID哈希计算分片索引
                mod := uint64(sam.Ref) % uint64(concurrency)
                // 转换为统一的histogramRecord格式
                histogramShards[mod] = append(histogramShards[mod], histogramRecord{
                    ref: sam.Ref,
                    t:   sam.T,
                    fh:  sam.FH,
                })
            }
            
            // 将分片后的浮点直方图样本发送到对应处理器
            for i := 0; i < concurrency; i++ {
                if len(histogramShards[i]) > 0 {
                    processors[i].input <- walSubsetProcessorInputItem{histogramSamples: histogramShards[i]}
                    histogramShards[i] = nil // 清空缓冲区
                }
            }
            
            // 处理剩余样本
            samples = samples[m:]
        }
        // 将浮点直方图切片归还对象池
        h.wlReplayFloatHistogramsPool.Put(v)
        
    case []record.RefMetadata: // 处理元数据记录
        for _, m := range v {
            // 替换重复序列的ID
            if r, ok := multiRef[m.Ref]; ok {
                m.Ref = r
            }
            // 查找序列
            s := h.series.getByID(m.Ref)
            if s == nil {
                unknownMetadataRefs.Inc() // 统计未知序列引用
                missingSeries[m.Ref] = struct{}{}
                continue
            }
            // 更新序列元数据(类型、单位、帮助信息)
            s.meta = &metadata.Metadata{
                Type: record.ToMetricType(m.Type),
                Unit: m.Unit,
                Help: m.Help,
            }
        }
        // 将元数据切片归还对象池
        h.wlReplayMetadataPool.Put(v)
        
    default:
        // 遇到未知类型的记录,触发panic(通常表示代码与数据版本不兼容)
        panic(fmt.Errorf("unexpected decoded type: %T", d))
    }
}

// 将本流程中发现的未知序列合并到全局集合
unknownSeriesRefs.merge(missingSeries)

// 检查解码过程是否出错
if decodeErr != nil {
    return decodeErr
}

// 检查序列创建过程是否出错
if seriesCreationErr != nil {
    // 清空decoded通道,避免阻塞解码协程
    for range decoded {
    }
    return seriesCreationErr
}

// 通知所有处理器终止,并等待它们完成
for i := 0; i < concurrency; i++ {
    processors[i].closeAndDrain()
}
// 关闭示例输入通道
close(exemplarsInput)
// 等待所有处理协程完成
wg.Wait()

// 检查WAL读取是否有错误
if err := r.Err(); err != nil {
    return fmt.Errorf("read records: %w", err)
}

// 若存在未知序列引用,打印告警日志并更新监控指标
if unknownSampleRefs.Load()+unknownExemplarRefs.Load()+unknownHistogramRefs.Load()+unknownMetadataRefs.Load()+unknownTombstoneRefs.Load() > 0 {
    h.logger.Warn(
        "Unknown series references",
        "series", unknownSeriesRefs.count(),
        "samples", unknownSampleRefs.Load(),
        "exemplars", unknownExemplarRefs.Load(),
        "histograms", unknownHistogramRefs.Load(),
        "metadata", unknownMetadataRefs.Load(),
        "tombstones", unknownTombstoneRefs.Load(),
    )

    // 更新监控指标(仅在值大于0时)
    counterAddNonZero(h.metrics.walReplayUnknownRefsTotal, float64(unknownSeriesRefs.count()), "series")
    counterAddNonZero(h.metrics.walReplayUnknownRefsTotal, float64(unknownSampleRefs.Load()), "samples")
    counterAddNonZero(h.metrics.walReplayUnknownRefsTotal, float64(unknownExemplarRefs.Load()), "exemplars")
    counterAddNonZero(h.metrics.walReplayUnknownRefsTotal, float64(unknownHistogramRefs.Load()), "histograms")
    counterAddNonZero(h.metrics.walReplayUnknownRefsTotal, float64(unknownMetadataRefs.Load()), "metadata")
    counterAddNonZero(h.metrics.walReplayUnknownRefsTotal, float64(unknownTombstoneRefs.Load()), "tombstones")
}

// 若存在内存映射块重叠,打印信息日志
if count := mmapOverlappingChunks.Load(); count > 0 {
    h.logger.Info("Overlapping m-map chunks on duplicate series records", "count", count)
}

// 所有处理完成,返回nil表示成功
return nil

三、代码解读

3.1、第一部分:版权声明及代码导入

// Copyright 2021 The Prometheus Authors
// 版权所有 2021 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// 根据Apache许可证2.0版(以下简称"许可证")授权;
// you may not use this file except in compliance with the License.
// 除非遵守许可证,否则您不得使用此文件。
// You may obtain a copy of the License at
// 您可以在以下位置获取许可证副本:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// 除非适用法律要求或书面同意,否则根据许可证分发的软件
// 按"原样"分发,不附带任何明示或暗示的担保或条件。
// 请参阅许可证以了解管理权限和限制的具体语言。

package tsdb
// 声明所属包为tsdb(时序数据库)

import (
	"errors"
	"fmt"
	"maps"
	"math"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/prometheus/client_golang/prometheus" // Prometheus的监控指标库
	"go.uber.org/atomic" // 原子操作库

	"github.com/prometheus/prometheus/model/exemplar" // 样本相关模型
	"github.com/prometheus/prometheus/model/histogram" // 直方图模型
	"github.com/prometheus/prometheus/model/labels" // 标签模型
	"github.com/prometheus/prometheus/model/metadata" // 元数据模型
	"github.com/prometheus/prometheus/storage" // 存储接口
	"github.com/prometheus/prometheus/tsdb/chunkenc" // 块编码
	"github.com/prometheus/prometheus/tsdb/chunks" // 块相关
	"github.com/prometheus/prometheus/tsdb/encoding" // 编码工具
	tsdb_errors "github.com/prometheus/prometheus/tsdb/errors" // 自定义错误
	"github.com/prometheus/prometheus/tsdb/fileutil" // 文件工具
	"github.com/prometheus/prometheus/tsdb/record" // 记录相关
	"github.com/prometheus/prometheus/tsdb/tombstones" // 墓碑(数据删除标记)
	"github.com/prometheus/prometheus/tsdb/wlog" // 写日志(WAL相关)
)

3.2、第二部分:结构体定义与工具函数

https://github.com/prometheus/prometheus/blob/v3.4.2/tsdb/head_wal.go#L48 ~ #L72

// histogramRecord 结合了RefHistogramSample和RefFloatHistogramSample,以简化WAL重放。
// 关键点:用于统一处理两种直方图样本,减少重放逻辑的复杂性
type histogramRecord struct {
	ref chunks.HeadSeriesRef // 系列引用
	t   int64 // 时间戳
	h   *histogram.Histogram // 直方图
	fh  *histogram.FloatHistogram // 浮点直方图
}

// seriesRefSet 用于跟踪系列引用的集合,带并发安全锁
type seriesRefSet struct {
	refs map[chunks.HeadSeriesRef]struct{} // 存储系列引用的集合
	mtx  sync.Mutex // 互斥锁,保证并发安全
}

// merge 将另一个系列引用集合合并到当前集合中
func (s *seriesRefSet) merge(other map[chunks.HeadSeriesRef]struct{}) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	maps.Copy(s.refs, other) // 使用maps.Copy高效合并
}

// count 返回集合中系列引用的数量
func (s *seriesRefSet) count() int {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	return len(s.refs)
}

// counterAddNonZero 当值大于0时,才向Prometheus计数器添加值
// 关键点:避免无效的0值更新,减少指标噪音
func counterAddNonZero(v *prometheus.CounterVec, value float64, lvs ...string) {
	if value > 0 {
		v.WithLabelValues(lvs...).Add(value)
	}
}

3.3、第三部分:WAL 加载函数(loadWAL)—— 初始化部分

https://github.com/prometheus/prometheus/blob/v3.4.2/tsdb/head_wal.go#L78

// loadWAL 从WAL读取器加载数据,恢复Head的状态
// 参数说明:
// - r: WAL读取器
// - syms: 标签符号表,用于编码/解码标签
// - multiRef: 多引用映射,处理重复系列
// - mmappedChunks: 内存映射块
// - oooMmappedChunks: 乱序内存映射块
// - lastSegment: 最后一个段索引
func (h *Head) loadWAL(r *wlog.Reader, syms *labels.SymbolTable, multiRef map[chunks.HeadSeriesRef]chunks.HeadSeriesRef, mmappedChunks, oooMmappedChunks map[chunks.HeadSeriesRef][]*mmappedChunk, lastSegment int) (err error) {
	// 跟踪被其他记录引用但缺失的系列记录数量
	unknownSeriesRefs := &seriesRefSet{refs: make(map[chunks.HeadSeriesRef]struct{}), mtx: sync.Mutex{}}
	// 跟踪引用未知系列的不同记录类型的数量,用于错误报告
	var unknownSampleRefs atomic.Uint64
	var unknownExemplarRefs atomic.Uint64
	var unknownHistogramRefs atomic.Uint64
	var unknownMetadataRefs atomic.Uint64
	var unknownTombstoneRefs atomic.Uint64
	// 跟踪具有重叠内存映射块的系列记录数量
	var mmapOverlappingChunks atomic.Uint64

	// 启动工作线程,每个线程处理系列ID空间分区中的样本
	var (
		wg             sync.WaitGroup // 等待组,用于同步工作线程
		concurrency    = h.opts.WALReplayConcurrency // 并发数,由配置决定
		processors     = make([]walSubsetProcessor, concurrency) // 处理器数组
		exemplarsInput chan record.RefExemplar //  exemplar输入通道

		shards          = make([][]record.RefSample, concurrency) // 样本分片
		histogramShards = make([][]histogramRecord, concurrency) // 直方图分片

		decoded                      = make(chan interface{}, 10) // 解码后的数据通道
		decodeErr, seriesCreationErr error // 解码错误和系列创建错误
	)

	// 延迟函数:在函数退出时处理资源清理
	defer func() {
		// 若发生损坏错误或系列创建错误,确保终止所有工作线程后再退出
		_, ok := err.(*wlog.CorruptionErr)
		if ok || seriesCreationErr != nil {
			for i := 0; i < concurrency; i++ {
				processors[i].closeAndDrain()
			}
			close(exemplarsInput)
			wg.Wait()
		}
	}()

	// 启动并发的样本处理器工作线程
	wg.Add(concurrency)
	for i := 0; i < concurrency; i++ {
		processors[i].setup() // 初始化处理器

		go func(wp *walSubsetProcessor) {
			// 处理WAL样本,返回缺失的系列、未知样本数等
			missingSeries, unknownSamples, unknownHistograms, overlapping := wp.processWALSamples(h, mmappedChunks, oooMmappedChunks)
			unknownSeriesRefs.merge(missingSeries)
			unknownSampleRefs.Add(unknownSamples)
			mmapOverlappingChunks.Add(overlapping)
			unknownHistogramRefs.Add(unknownHistograms)
			wg.Done()
		}(&processors[i])
	}

	// 启动exemplar处理工作线程
	wg.Add(1)
	exemplarsInput = make(chan record.RefExemplar, 300)
	go func(input <-chan record.RefExemplar) {
		missingSeries := make(map[chunks.HeadSeriesRef]struct{})
		var err error
		defer wg.Done()
		for e := range input {
			// 根据引用获取系列
			ms := h.series.getByID(e.Ref)
			if ms == nil {
				unknownExemplarRefs.Inc()
				missingSeries[e.Ref] = struct{}{}
				continue
			}
			// 重放WAL时不应出现乱序exemplar,若非此类错误则记录警告
			err = h.exemplars.AddExemplar(ms.labels(), exemplar.Exemplar{Ts: e.T, Value: e.V, Labels: e.Labels})
			if err != nil && errors.Is(err, storage.ErrOutOfOrderExemplar) {
				h.logger.Warn("重放WAL的exemplar记录时出现意外错误", "err", err)
			}
		}
		unknownSeriesRefs.merge(missingSeries)
	}(exemplarsInput)

3.4、第四部分:WAL 加载函数(loadWAL)—— 解码协程与多类型数据(series / samples / tombstones 等)处理分发

https://github.com/prometheus/prometheus/blob/v3.4.2/tsdb/head_wal.go#L155

	// 启动解码 goroutine,将 WAL 记录解码并发送到 decoded 通道
	go func() {
		defer close(decoded)  // 退出时关闭 decoded 通道
		var err error
		dec := record.NewDecoder(syms)  // 创建记录解码器
		for r.Next() {  // 读取下一条 WAL 记录
			switch dec.Type(r.Record()) {  // 根据记录类型处理
			case record.Series:  // 系列记录
				series := h.wlReplaySeriesPool.Get()[:0]  // 从对象池获取系列切片
				series, err = dec.Series(r.Record(), series)  // 解码系列记录
				if err != nil {
					decodeErr = &wlog.CorruptionErr{  // 记录损坏错误
						Err:     fmt.Errorf("decode series: %w", err),
						Segment: r.Segment(),
						Offset:  r.Offset(),
					}
					return
				}
				decoded <- series  // 将解码后的系列发送到通道
			case record.Samples:  // 样本记录
				samples := h.wlReplaySamplesPool.Get()[:0]  // 从对象池获取样本切片
				samples, err = dec.Samples(r.Record(), samples)  // 解码样本记录
				if err != nil {
					decodeErr = &wlog.CorruptionErr{  // 记录损坏错误
						Err:     fmt.Errorf("decode samples: %w", err),
						Segment: r.Segment(),
						Offset:  r.Offset(),
					}
					return
				}
				decoded <- samples  // 将解码后的样本发送到通道
			case record.Tombstones:  // 墓碑记录(删除标记)
				tstones := h.wlReplaytStonesPool.Get()[:0]  // 从对象池获取墓碑切片
				tstones, err = dec.Tombstones(r.Record(), tstones)  // 解码墓碑记录
				if err != nil {
					decodeErr = &wlog.CorruptionErr{  // 记录损坏错误
						Err:     fmt.Errorf("decode tombstones: %w", err),
						Segment: r.Segment(),
						Offset:  r.Offset(),
					}
					return
				}
				decoded <- tstones  // 将解码后的墓碑发送到通道
			case record.Exemplars:  // Exemplar 记录
				exemplars := h.wlReplayExemplarsPool.Get()[:0]  // 从对象池获取 exemplar 切片
				exemplars, err = dec.Exemplars(r.Record(), exemplars)  // 解码 exemplar 记录
				if err != nil {
					decodeErr = &wlog.CorruptionErr{  // 记录损坏错误
						Err:     fmt.Errorf("decode exemplars: %w", err),
						Segment: r.Segment(),
						Offset:  r.Offset(),
					}
					return
				}
				decoded <- exemplars  // 将解码后的 exemplar 发送到通道
			case record.HistogramSamples, record.CustomBucketsHistogramSamples:  // 直方图记录
				hists := h.wlReplayHistogramsPool.Get()[:0]  // 从对象池获取直方图切片
				hists, err = dec.HistogramSamples(r.Record(), hists)  // 解码直方图记录
				if err != nil {
					decodeErr = &wlog.CorruptionErr{  // 记录损坏错误
						Err:     fmt.Errorf("decode histograms: %w", err),
						Segment: r.Segment(),
						Offset:  r.Offset(),
					}
					return
				}
				decoded <- hists  // 将解码后的直方图发送到通道
			case record.FloatHistogramSamples, record.CustomBucketsFloatHistogramSamples:  // 浮点直方图记录
				hists := h.wlReplayFloatHistogramsPool.Get()[:0]  // 从对象池获取浮点直方图切片
				hists, err = dec.FloatHistogramSamples(r.Record(), hists)  // 解码浮点直方图记录
				if err != nil {
					decodeErr = &wlog.CorruptionErr{  // 记录损坏错误
						Err:     fmt.Errorf("decode float histograms: %w", err),
						Segment: r.Segment(),
						Offset:  r.Offset(),
					}
					return
				}
				decoded <- hists  // 将解码后的浮点直方图发送到通道
			case record.Metadata:  // 元数据记录
				meta := h.wlReplayMetadataPool.Get()[:0]  // 从对象池获取元数据切片
				meta, err := dec.Metadata(r.Record(), meta)  // 解码元数据记录
				if err != nil {
					decodeErr = &wlog.CorruptionErr{  // 记录损坏错误
						Err:     fmt.Errorf("decode metadata: %w", err),
						Segment: r.Segment(),
						Offset:  r.Offset(),
					}
					return
				}
				decoded <- meta  // 将解码后的元数据发送到通道
			default:  // 未知类型,忽略
				// Noop.
			}
		}
	}()

	// 记录总是从最旧到最新重放
	missingSeries := make(map[chunks.HeadSeriesRef]struct{})
Outer:
	for d := range decoded {  // 从 decoded 通道读取解码后的数据
		switch v := d.(type) {  // 根据数据类型处理
		case []record.RefSeries:  // 系列数据
			for _, walSeries := range v {  // 遍历每个系列
				// 获取或创建具有指定 ID 的系列
				mSeries, created, err := h.getOrCreateWithID(walSeries.Ref, walSeries.Labels.Hash(), walSeries.Labels, false)
				if err != nil {
					seriesCreationErr = err  // 记录系列创建错误
					break Outer
				}

				// 更新最后一个系列 ID
				if chunks.HeadSeriesRef(h.lastSeriesID.Load()) < walSeries.Ref {
					h.lastSeriesID.Store(uint64(walSeries.Ref))
				}
				if !created {  // 如果系列已存在,记录映射关系
					multiRef[walSeries.Ref] = mSeries.ref
					// 为重复系列设置 WAL 过期时间,使其在后续检查点中保留
					h.setWALExpiry(walSeries.Ref, lastSegment)
				}

				// 将系列分配给对应的处理器
				idx := uint64(mSeries.ref) % uint64(concurrency)
				processors[idx].input <- walSubsetProcessorInputItem{walSeriesRef: walSeries.Ref, existingSeries: mSeries}
			}
			h.wlReplaySeriesPool.Put(v)  // 将系列切片放回对象池
		case []record.RefSample:  // 样本数据
			samples := v
			minValidTime := h.minValidTime.Load()  // 获取最小有效时间戳
			// 将样本分成 5000 个或更少的块,避免大内存占用
			for len(samples) > 0 {
				m := 5000
				if len(samples) < m {
					m = len(samples)
				}
				// 初始化每个分片的缓冲区
				for i := 0; i < concurrency; i++ {
					if shards[i] == nil {
						shards[i] = processors[i].reuseBuf()
					}
				}
				// 将样本分配到对应的分片
				for _, sam := range samples[:m] {
					if sam.T < minValidTime {
						continue  // 忽略早于最小有效时间的样本
					}
					if r, ok := multiRef[sam.Ref]; ok {  // 处理重复引用
						sam.Ref = r
					}
					mod := uint64(sam.Ref) % uint64(concurrency)  // 计算分片索引
					shards[mod] = append(shards[mod], sam)  // 添加到分片
				}
				// 将每个分片的样本发送到对应的处理器
				for i := 0; i < concurrency; i++ {
					if len(shards[i]) > 0 {
						processors[i].input <- walSubsetProcessorInputItem{samples: shards[i]}
						shards[i] = nil  // 清空分片
					}
				}
				samples = samples[m:]  // 处理剩余样本
			}
			h.wlReplaySamplesPool.Put(v)  // 将样本切片放回对象池
		case []tombstones.Stone:  // 墓碑数据
			for _, s := range v {  // 遍历每个墓碑
				for _, itv := range s.Intervals {  // 遍历每个时间间隔
					if itv.Maxt < h.minValidTime.Load() {
						continue  // 忽略早于最小有效时间的墓碑
					}
					// 处理重复引用
					if r, ok := multiRef[chunks.HeadSeriesRef(s.Ref)]; ok {
						s.Ref = storage.SeriesRef(r)
					}
					// 检查系列是否存在
					if m := h.series.getByID(chunks.HeadSeriesRef(s.Ref)); m == nil {
						unknownTombstoneRefs.Inc()  // 累加未知墓碑引用计数
						missingSeries[chunks.HeadSeriesRef(s.Ref)] = struct{}{}  // 记录缺失的系列
						continue
					}
					h.tombstones.AddInterval(s.Ref, itv)  // 添加时间间隔到墓碑
				}
			}
			h.wlReplaytStonesPool.Put(v)  // 将墓碑切片放回对象池
		case []record.RefExemplar:  // Exemplar 数据
			for _, e := range v {  // 遍历每个 exemplar
				if e.T < h.minValidTime.Load() {
					continue  // 忽略早于最小有效时间的 exemplar
				}
				// 处理重复引用
				if r, ok := multiRef[e.Ref]; ok {
					e.Ref = r
				}
				exemplarsInput <- e  // 发送到 exemplar 处理通道
			}
			h.wlReplayExemplarsPool.Put(v)  // 将 exemplar 切片放回对象池
		case []record.RefHistogramSample:  // 直方图数据
			samples := v
			minValidTime := h.minValidTime.Load()  // 获取最小有效时间戳
			// 将直方图样本分成 5000 个或更少的块
			for len(samples) > 0 {
				m := 5000
				if len(samples) < m {
					m = len(samples)
				}
				// 初始化每个分片的缓冲区
				for i := 0; i < concurrency; i++ {
					if histogramShards[i] == nil {
						histogramShards[i] = processors[i].reuseHistogramBuf()
					}
				}
				// 将直方图样本分配到对应的分片
				for _, sam := range samples[:m] {
					if sam.T < minValidTime {
						continue  // 忽略早于最小有效时间的样本
					}
					if r, ok := multiRef[sam.Ref]; ok {  // 处理重复引用
						sam.Ref = r
					}
					mod := uint64(sam.Ref) % uint64(concurrency)  // 计算分片索引
					histogramShards[mod] = append(histogramShards[mod], histogramRecord{ref: sam.Ref, t: sam.T, h: sam.H})  // 添加到分片
				}
				// 将每个分片的直方图样本发送到对应的处理器
				for i := 0; i < concurrency; i++ {
					if len(histogramShards[i]) > 0 {
						processors[i].input <- walSubsetProcessorInputItem{histogramSamples: histogramShards[i]}
						histogramShards[i] = nil  // 清空分片
					}
				}
				samples = samples[m:]  // 处理剩余样本
			}
			h.wlReplayHistogramsPool.Put(v)  // 将直方图切片放回对象池
		case []record.RefFloatHistogramSample:  // 浮点直方图数据
			samples := v
			minValidTime := h.minValidTime.Load()  // 获取最小有效时间戳
			// 将浮点直方图样本分成 5000 个或更少的块
			for len(samples) > 0 {
				m := 5000
				if len(samples) < m {
					m = len(samples)
				}
				// 初始化每个分片的缓冲区
				for i := 0; i < concurrency; i++ {
					if histogramShards[i] == nil {
						histogramShards[i] = processors[i].reuseHistogramBuf()
					}
				}
				// 将浮点直方图样本分配到对应的分片
				for _, sam := range samples[:m] {
					if sam.T < minValidTime {
						continue  // 忽略早于最小有效时间的样本
					}
					if r, ok := multiRef[sam.Ref]; ok {  // 处理重复引用
						sam.Ref = r
					}
					mod := uint64(sam.Ref) % uint64(concurrency)  // 计算分片索引
					histogramShards[mod] = append(histogramShards[mod], histogramRecord{ref: sam.Ref, t: sam.T, fh: sam.FH})  // 添加到分片
				}
				// 将每个分片的浮点直方图样本发送到对应的处理器
				for i := 0; i < concurrency; i++ {
					if len(histogramShards[i]) > 0 {
						processors[i].input <- walSubsetProcessorInputItem{histogramSamples: histogramShards[i]}
						histogramShards[i] = nil  // 清空分片
					}
				}
				samples = samples[m:]  // 处理剩余样本
			}
			h.wlReplayFloatHistogramsPool.Put(v)  // 将浮点直方图切片放回对象池
		case []record.RefMetadata:  // 元数据
			for _, m := range v {  // 遍历每个元数据
				if r, ok := multiRef[m.Ref]; ok {  // 处理重复引用
					m.Ref = r
				}
				s := h.series.getByID(m.Ref)  // 获取对应的系列
				if s == nil {  // 系列不存在
					unknownMetadataRefs.Inc()  // 累加未知元数据引用计数
					missingSeries[m.Ref] = struct{}{}  // 记录缺失的系列
					continue
				}
				// 更新系列的元数据
				s.meta = &metadata.Metadata{
					Type: record.ToMetricType(m.Type),
					Unit: m.Unit,
					Help: m.Help,
				}
			}
			h.wlReplayMetadataPool.Put(v)  // 将元数据切片放回对象池
		default:  // 未知类型, panic
			panic(fmt.Errorf("unexpected decoded type: %T", d))
		}
	}
	unknownSeriesRefs.merge(missingSeries)  // 合并缺失的系列

	// 检查解码错误
	if decodeErr != nil {
		return decodeErr
	}
	// 检查系列创建错误
	if seriesCreationErr != nil {
		// 清空通道以解除 goroutine 的阻塞
		for range decoded {
		}
		return seriesCreationErr
	}

	// 向每个工作线程发送终止信号,并等待它们关闭输出通道
	for i := 0; i < concurrency; i++ {
		processors[i].closeAndDrain()
	}
	close(exemplarsInput)  // 关闭 exemplar 输入通道
	wg.Wait()  // 等待所有工作线程完成

	// 检查读取错误
	if err := r.Err(); err != nil {
		return fmt.Errorf("read records: %w", err)
	}

	// 报告未知引用的统计信息
	if unknownSampleRefs.Load()+unknownExemplarRefs.Load()+unknownHistogramRefs.Load()+unknownMetadataRefs.Load()+unknownTombstoneRefs.Load() > 0 {
		h.logger.Warn(
			"Unknown series references",
			"series", unknownSeriesRefs.count(),
			"samples", unknownSampleRefs.Load(),
			"exemplars", unknownExemplarRefs.Load(),
			"histograms", unknownHistogramRefs.Load(),
			"metadata", unknownMetadataRefs.Load(),
			"tombstones", unknownTombstoneRefs.Load(),
		)

		// 更新 Prometheus 指标
		counterAddNonZero(h.metrics.walReplayUnknownRefsTotal, float64(unknownSeriesRefs.count()), "series")
		counterAddNonZero(h.metrics.walReplayUnknownRefsTotal, float64(unknownSampleRefs.Load()), "samples")
		counterAddNonZero(h.metrics.walReplayUnknownRefsTotal, float64(unknownExemplarRefs.Load()), "exemplars")
		counterAddNonZero(h.metrics.walReplayUnknownRefsTotal, float64(unknownHistogramRefs.Load()), "histograms")
		counterAddNonZero(h.metrics.walReplayUnknownRefsTotal, float64(unknownMetadataRefs.Load()), "metadata")
		counterAddNonZero(h.metrics.walReplayUnknownRefsTotal, float64(unknownTombstoneRefs.Load()), "tombstones")
	}
	// 报告重叠的内存映射块
	if count := mmapOverlappingChunks.Load(); count > 0 {
		h.logger.Info("Overlapping m-map chunks on duplicate series records", "count", count)
	}
	return nil
}

3.5、第五部分:WAL 重放放函数 —— 内存映射块重置逻辑(resetSeriesWithMMappedChunks 实现)

https://github.com/prometheus/prometheus/blob/v3.4.2/tsdb/head_wal.go#L483

// resetSeriesWithMMappedChunks 仅在 WAL 重放期间使用,用于重置系列的内存映射块
func (h *Head) resetSeriesWithMMappedChunks(mSeries *memSeries, mmc, oooMmc []*mmappedChunk, walSeriesRef chunks.HeadSeriesRef) (overlapped bool) {
    // 检查当前系列引用与 WAL 中的系列引用是否一致,不一致可能存在重复系列
    if mSeries.ref != walSeriesRef {
        // 当新旧内存映射块都存在时,检查时间范围是否重叠
        if len(mSeries.mmappedChunks) > 0 && len(mmc) > 0 {
            if overlapsClosedInterval(
                // 旧块的最小时间(取第一个块的 minTime)
                mSeries.mmappedChunks[0].minTime,
                // 旧块的最大时间(取最后一个块的 maxTime)
                mSeries.mmappedChunks[len(mSeries.mmappedChunks)-1].maxTime,
                // 新块的最小时间(取第一个块的 minTime)
                mmc[0].minTime,
                // 新块的最大时间(取最后一个块的 maxTime)
                mmc[len(mmc)-1].maxTime,
            ) {
                // 记录重叠日志,包含系列标签、新旧引用及时间范围
                h.logger.Debug(
                    "M-mapped chunks overlap on a duplicate series record",
                    "series", mSeries.labels().String(),
                    "oldref", mSeries.ref,
                    "oldmint", mSeries.mmappedChunks[0].minTime,
                    "oldmaxt", mSeries.mmappedChunks[len(mSeries.mmappedChunks)-1].maxTime,
                    "newref", walSeriesRef,
                    "newmint", mmc[0].minTime,
                    "newmaxt", mmc[len(mmc)-1].maxTime,
                )
                overlapped = true // 标记为重叠状态
            }
        }
    }

    // 更新块指标:新增块数量(主块 + 乱序块)
    h.metrics.chunksCreated.Add(float64(len(mmc) + len(oooMmc)))
    // 更新块指标:移除的旧主块数量
    h.metrics.chunksRemoved.Add(float64(len(mSeries.mmappedChunks)))
    // 更新块指标:总块数变化(新增 - 移除的旧主块)
    h.metrics.chunks.Add(float64(len(mmc) + len(oooMmc) - len(mSeries.mmappedChunks)))

    // 若存在旧乱序块,更新指标:移除旧乱序块数量
    if mSeries.ooo != nil {
        h.metrics.chunksRemoved.Add(float64(len(mSeries.ooo.oooMmappedChunks)))
        h.metrics.chunks.Sub(float64(len(mSeries.ooo.oooMmappedChunks)))
    }

    // 用新的主内存映射块替换旧块
    mSeries.mmappedChunks = mmc
    // 处理乱序块:若新乱序块为空,清空系列的乱序字段;否则初始化或更新乱序块
    if len(oooMmc) == 0 {
        mSeries.ooo = nil
    } else {
        if mSeries.ooo == nil {
            mSeries.ooo = &memSeriesOOOFields{} // 初始化乱序字段结构体
        }
        // 用新乱序块覆盖旧乱序块
        *mSeries.ooo = memSeriesOOOFields{oooMmappedChunks: oooMmc}
    }
    // 缓存主块的最大时间,用于后续样本追加时快速判断是否有效(跳过无效追加)
    if len(mmc) == 0 {
        mSeries.mmMaxTime = math.MinInt64 // 无主块时设为最小时间
    } else {
        mSeries.mmMaxTime = mmc[len(mmc)-1].maxTime // 取最后一个主块的最大时间
        // 更新全局主块的时间范围(最小和最大时间)
        h.updateMinMaxTime(mmc[0].minTime, mSeries.mmMaxTime)
    }
    // 若存在新乱序块,计算其最小和最大时间并更新全局乱序块时间范围
    if len(oooMmc) != 0 {
        // 初始化乱序块的最小时间为最大整数,最大时间为最小整数
        mint, maxt := int64(math.MaxInt64), int64(math.MinInt64)
        // 遍历所有乱序块,找到实际的最小和最大时间(乱序块时间无序)
        for _, ch := range oooMmc {
            if ch.minTime < mint {
                mint = ch.minTime
            }
            if ch.maxTime > maxt {
                maxt = ch.maxTime
            }
        }
        // 更新全局乱序块的时间范围
        h.updateMinOOOMaxOOOTime(mint, maxt)
    }

    // WAL 重放的样本已压缩为内存映射块,因此重置头部块(未压缩的临时块)相关字段
    mSeries.nextAt = 0          // 重置下一个样本的时间戳
    mSeries.headChunks = nil    // 清空头部块链表
    mSeries.app = nil           // 清空块追加器
    return                      // 返回是否存在块重叠的状态
}

3.6、第六部分:WAL 加载函数 ——WAL 子集处理器(walSubsetProcessor)结构与方法实现

https://github.com/prometheus/prometheus/blob/v3.4.2/tsdb/head_wal.go#L554

该组件是 WAL 重放并行化的关键,通过分治策略将大量 WAL 记录分配到多个处理器并发处理,并通过缓冲区复用优化性能,确保重放过程高效稳定。

// walSubsetProcessor 用于在 WAL 重放时并行处理部分系列数据的处理器,负责分发和处理样本、直方图等记录
type walSubsetProcessor struct {
	input            chan walSubsetProcessorInputItem  // 输入通道,接收待处理的 WAL 记录项
	output           chan []record.RefSample           // 输出通道,返回处理后的样本数据(用于缓冲区复用)
	histogramsOutput chan []histogramRecord            // 输出通道,返回处理后的直方图数据(用于缓冲区复用)
}

// walSubsetProcessorInputItem 表示 WAL 子集处理器的输入项,包含多种待处理的数据类型
type walSubsetProcessorInputItem struct {
	samples          []record.RefSample   // 待处理的普通样本数据
	histogramSamples []histogramRecord    // 待处理的直方图样本数据
	existingSeries   *memSeries           // 已存在的内存系列(用于更新或重置)
	walSeriesRef     chunks.HeadSeriesRef // WAL 中记录的系列引用(用于关联系列)
}

// setup 初始化 WAL 子集处理器的通道,设置缓冲区大小为 300,用于并发处理时的流量控制
func (wp *walSubsetProcessor) setup() {
	wp.input = make(chan walSubsetProcessorInputItem, 300)
	wp.output = make(chan []record.RefSample, 300)
	wp.histogramsOutput = make(chan []histogramRecord, 300)
}

// closeAndDrain 关闭输入通道并清空输出通道,确保资源释放,避免 goroutine 泄漏
func (wp *walSubsetProcessor) closeAndDrain() {
	close(wp.input)          // 关闭输入通道,阻止新数据进入
	for range wp.output {    // 清空输出通道中的残留数据
	}
	for range wp.histogramsOutput {  // 清空直方图输出通道中的残留数据
	}
}

// reuseBuf 尝试从输出通道获取一个已有的样本缓冲区并重置长度,用于复用内存减少分配
// 若输出通道无数据,则返回 nil(将在后续处理中创建新缓冲区)
func (wp *walSubsetProcessor) reuseBuf() []record.RefSample {
	select {
	case buf := <-wp.output:  // 从输出通道获取之前的缓冲区
		return buf[:0]        // 重置缓冲区长度为 0(保留底层数组复用)
	default:                  // 若通道为空,返回 nil
	}
	return nil
}

// reuseHistogramBuf 尝试从直方图输出通道获取一个已有的直方图缓冲区并重置长度,用于复用内存
// 若通道无数据,则返回 nil(后续处理中创建新缓冲区)
func (wp *walSubsetProcessor) reuseHistogramBuf() []histogramRecord {
	select {
	case buf := <-wp.histogramsOutput:  // 从直方图输出通道获取之前的缓冲区
		return buf[:0]                  // 重置缓冲区长度为 0(保留底层数组复用)
	default:                            // 若通道为空,返回 nil
	}
	return nil
}

关键代码解读

3.6.1、walSubsetProcessor 结构体设计

该结构体是 WAL 重放时并行处理的核心组件,通过三个通道实现数据的输入、处理结果输出和缓冲区复用:

        • input 通道接收待处理的各类数据(样本、直方图、系列信息等),缓冲区大小 300 可平衡并发处理中的数据积压与内存占用。
        • output 和 histogramsOutput 通道分别用于回收处理后的样本和直方图缓冲区,支持内存复用,减少频繁分配 / 释放带来的性能开销。

3.6.2、walSubsetProcessorInputItem 数据类型

作为处理器的输入载体,它整合了多种 WAL 记录类型:

        • 普通样本(samples)和直方图样本(histogramSamples)是主要处理对象;
        • existingSeries 和 walSeriesRef 用于关联已存在的内存系列,支持系列数据的更新或重置(如之前分析的 resetSeriesWithMMappedChunks 函数)。
        • 这种设计使处理器能统一处理不同类型的 WAL 记录,简化并行分发逻辑。

3.6.3、缓冲区复用机制

reuseBuf 和 reuseHistogramBuf 方法通过从输出通道回收旧缓冲区并重置长度,实现内存复用:

        • 在高并发场景下,频繁创建和销毁切片会导致 GC 压力,复用缓冲区可显著提升性能。
        • 利用 select 的 default 分支,确保在通道为空时不阻塞,直接返回 nil 以创建新缓冲区,兼顾效率与灵活性。

3.6.4、资源释放逻辑

closeAndDrain 方法在处理器关闭时清理资源:

        • 关闭 input 通道防止新数据写入;
        • 遍历清空输出通道,确保所有残留数据被消费,避免持有未释放的内存,防止 goroutine 泄漏(若通道未清空,发送方可能因缓冲区满而阻塞)。

3.7、第七部分:WAL 子集处理器的样本处理方法(processWALSamples)实现

// processWALSamples 处理输入通道中的样本数据,将其添加到 Head 中,并将缓冲区发送到输出通道以供复用
// 早于 minValidTime 时间戳的样本会被丢弃
func (wp *walSubsetProcessor) processWALSamples(h *Head, mmappedChunks, oooMmappedChunks map[chunks.HeadSeriesRef][]*mmappedChunk) (map[chunks.HeadSeriesRef]struct{}, uint64, uint64, uint64) {
	// 延迟关闭输出通道,确保所有处理完成后释放资源
	defer close(wp.output)
	defer close(wp.histogramsOutput)

	// 记录缺失的系列引用、未知样本引用数、未知直方图引用数和重叠的内存映射块数
	missingSeries := make(map[chunks.HeadSeriesRef]struct{})
	var unknownSampleRefs, unknownHistogramRefs, mmapOverlappingChunks uint64

	// 加载当前有效的最小时间戳(早于该时间的样本会被丢弃)
	minValidTime := h.minValidTime.Load()
	// 初始化样本时间范围的最小和最大值(用于后续更新 Head 的全局时间范围)
	mint, maxt := int64(math.MaxInt64), int64(math.MinInt64)
	// 构建块追加选项,包含块映射器、块时间范围和每个块的样本数配置
	appendChunkOpts := chunkOpts{
		chunkDiskMapper: h.chunkDiskMapper,
		chunkRange:      h.chunkRange.Load(),
		samplesPerChunk: h.opts.SamplesPerChunk,
	}

	// 循环读取输入通道中的数据项,处理每一批样本或直方图
	for in := range wp.input {
		// 如果输入项包含已存在的系列(existingSeries),则用内存映射块重置该系列
		if in.existingSeries != nil {
			// 获取该系列对应的内存映射块和乱序内存映射块
			mmc := mmappedChunks[in.walSeriesRef]
			oooMmc := oooMmappedChunks[in.walSeriesRef]
			// 重置系列并检查是否有重叠的内存映射块,若有则递增计数
			if h.resetSeriesWithMMappedChunks(in.existingSeries, mmc, oooMmc, in.walSeriesRef) {
				mmapOverlappingChunks++
			}
			// 处理完系列重置后,继续下一个输入项
			continue
		}

		// 处理普通样本数据
		for _, s := range in.samples {
			// 根据样本的系列引用获取对应的内存系列
			ms := h.series.getByID(s.Ref)
			if ms == nil {
				// 若系列不存在,记录未知引用并跳过该样本
				unknownSampleRefs++
				missingSeries[s.Ref] = struct{}{}
				continue
			}
			// 若样本时间早于或等于系列的内存映射最大时间,说明已存在于映射块中,跳过
			if s.T <= ms.mmMaxTime {
				continue
			}
			// 将样本追加到系列中,若创建了新块则更新指标并尝试内存映射该块
			if _, chunkCreated := ms.append(s.T, s.V, 0, appendChunkOpts); chunkCreated {
				h.metrics.chunksCreated.Inc()
				h.metrics.chunks.Inc()
				_ = ms.mmapChunks(h.chunkDiskMapper)
			}
			// 更新当前处理的样本时间范围
			if s.T > maxt {
				maxt = s.T
			}
			if s.T < mint {
				mint = s.T
			}
		}
		// 将处理完的样本缓冲区发送到输出通道供复用(非阻塞,通道满则丢弃)
		select {
		case wp.output <- in.samples:
		default:
		}

		// 处理直方图样本数据
		for _, s := range in.histogramSamples {
			// 早于最小有效时间的直方图样本直接丢弃
			if s.t < minValidTime {
				continue
			}
			// 根据直方图的系列引用获取对应的内存系列
			ms := h.series.getByID(s.ref)
			if ms == nil {
				// 若系列不存在,记录未知引用并跳过该直方图
				unknownHistogramRefs++
				missingSeries[s.ref] = struct{}{}
				continue
			}
			// 若直方图时间早于或等于系列的内存映射最大时间,说明已存在于映射块中,跳过
			if s.t <= ms.mmMaxTime {
				continue
			}
			// 标记是否创建了新块
			var chunkCreated bool
			// 根据直方图类型(普通/浮点)追加到系列中
			if s.h != nil {
				_, chunkCreated = ms.appendHistogram(s.t, s.h, 0, appendChunkOpts)
			} else {
				_, chunkCreated = ms.appendFloatHistogram(s.t, s.fh, 0, appendChunkOpts)
			}
			// 若创建了新块,更新块相关指标
			if chunkCreated {
				h.metrics.chunksCreated.Inc()
				h.metrics.chunks.Inc()
			}
			// 更新当前处理的直方图时间范围
			if s.t > maxt {
				maxt = s.t
			}
			if s.t < mint {
				mint = s.t
			}
		}

		// 将处理完的直方图缓冲区发送到输出通道供复用(非阻塞,通道满则丢弃)
		select {
		case wp.histogramsOutput <- in.histogramSamples:
		default:
		}
	}
	// 根据处理的样本和直方图时间范围,更新 Head 的全局最小和最大时间
	h.updateMinMaxTime(mint, maxt)

	// 返回处理过程中收集的缺失系列、未知引用数和重叠块数
	return missingSeries, unknownSampleRefs, unknownHistogramRefs, mmapOverlappingChunks
}

3.8、第八部分:WBL(Write-Ahead Log 扩展日志)加载处理方法(loadWBL)实现

// loadWBL 从 WBL 读取器加载数据,处理样本、直方图样本和内存映射标记,并将其应用到 Head 中
// 参数包括 WBL 读取器、符号表、系列引用映射以及最后一个内存映射块引用
func (h *Head) loadWBL(r *wlog.Reader, syms *labels.SymbolTable, multiRef map[chunks.HeadSeriesRef]chunks.HeadSeriesRef, lastMmapRef chunks.ChunkDiskMapperRef) (err error) {
	// 跟踪其他记录引用但缺失的系列引用
	unknownSeriesRefs := &seriesRefSet{refs: make(map[chunks.HeadSeriesRef]struct{}), mtx: sync.Mutex{}}
	// 跟踪引用未知系列的样本、直方图样本和内存映射标记的数量(用于错误报告)
	var unknownSampleRefs, unknownHistogramRefs, mmapMarkerUnknownRefs atomic.Uint64

	// 解析最后一个内存映射块引用的序列号和偏移量
	lastSeq, lastOff := lastMmapRef.Unpack()
	// 启动工作线程,每个线程处理系列 ID 空间分区中的样本
	var (
		wg          sync.WaitGroup
		concurrency = h.opts.WALReplayConcurrency // 并发度,由配置指定
		processors  = make([]wblSubsetProcessor, concurrency) // 每个工作线程的处理器实例

		shards          = make([][]record.RefSample, concurrency) // 样本分片,按系列 ID 哈希分配给不同处理器
		histogramShards = make([][]histogramRecord, concurrency)  // 直方图样本分片,同上

		decodedCh = make(chan interface{}, 10) // 用于传递解码后的记录
		decodeErr error                        // 解码过程中发生的错误
	)

	// 延迟处理:若发生 corruption 错误,确保所有工作线程终止后再退出,并包装错误标识
	defer func() {
		_, ok := err.(*wlog.CorruptionErr)
		if ok {
			err = &errLoadWbl{err: err} // 包装为 WBL 加载错误
			// 关闭所有处理器并释放资源
			for i := 0; i < concurrency; i++ {
				processors[i].closeAndDrain()
			}
			wg.Wait() // 等待所有工作线程结束
		}
	}()

	// 启动并发工作线程
	wg.Add(concurrency)
	for i := 0; i < concurrency; i++ {
		processors[i].setup() // 初始化处理器

		// 启动工作线程处理 WBL 样本
		go func(wp *wblSubsetProcessor) {
			// 处理样本并获取缺失的系列、未知样本和直方图数量
			missingSeries, unknownSamples, unknownHistograms := wp.processWBLSamples(h)
			unknownSeriesRefs.merge(missingSeries) // 合并缺失的系列引用
			unknownSampleRefs.Add(unknownSamples)  // 累加未知样本计数
			unknownHistogramRefs.Add(unknownHistograms) // 累加未知直方图计数
			wg.Done() // 标记工作线程完成
		}(&processors[i])
	}

	// 启动 goroutine 解码 WBL 记录并发送到 decodedCh 通道
	go func() {
		defer close(decodedCh) // 处理完成后关闭通道
		dec := record.NewDecoder(syms) // 创建记录解码器
		for r.Next() { // 循环读取 WBL 记录
			var err error
			rec := r.Record() // 获取当前记录
			switch dec.Type(rec) { // 根据记录类型解码
			case record.Samples:
				// 解码样本记录
				samples := h.wlReplaySamplesPool.Get()[:0] // 从对象池获取缓冲区
				samples, err = dec.Samples(rec, samples)
				if err != nil {
					decodeErr = &wlog.CorruptionErr{
						Err:     fmt.Errorf("decode samples: %w", err),
						Segment: r.Segment(),
						Offset:  r.Offset(),
					}
					return
				}
				decodedCh <- samples // 发送解码后的样本到通道
			case record.MmapMarkers:
				// 解码内存映射标记记录
				markers := h.wlReplayMmapMarkersPool.Get()[:0] // 从对象池获取缓冲区
				markers, err = dec.MmapMarkers(rec, markers)
				if err != nil {
					decodeErr = &wlog.CorruptionErr{
						Err:     fmt.Errorf("decode mmap markers: %w", err),
						Segment: r.Segment(),
						Offset:  r.Offset(),
					}
					return
				}
				decodedCh <- markers // 发送解码后的标记到通道
			case record.HistogramSamples, record.CustomBucketsHistogramSamples:
				// 解码直方图样本记录
				hists := h.wlReplayHistogramsPool.Get()[:0] // 从对象池获取缓冲区
				hists, err = dec.HistogramSamples(rec, hists)
				if err != nil {
					decodeErr = &wlog.CorruptionErr{
						Err:     fmt.Errorf("decode histograms: %w", err),
						Segment: r.Segment(),
						Offset:  r.Offset(),
					}
					return
				}
				decodedCh <- hists // 发送解码后的直方图到通道
			case record.FloatHistogramSamples, record.CustomBucketsFloatHistogramSamples:
				// 解码浮点直方图样本记录
				hists := h.wlReplayFloatHistogramsPool.Get()[:0] // 从对象池获取缓冲区
				hists, err = dec.FloatHistogramSamples(rec, hists)
				if err != nil {
					decodeErr = &wlog.CorruptionErr{
						Err:     fmt.Errorf("decode float histograms: %w", err),
						Segment: r.Segment(),
						Offset:  r.Offset(),
					}
					return
				}
				decodedCh <- hists // 发送解码后的浮点直方图到通道
			default:
				// 忽略其他类型的记录
			}
		}
	}()

	// 记录总是从最旧到最新重放
	missingSeries := make(map[chunks.HeadSeriesRef]struct{})
	// 处理解码后的记录
	for d := range decodedCh {
		switch v := d.(type) {
		case []record.RefSample:
			// 处理普通样本
			samples := v
			// 将样本分块(每块不超过 5000 个),避免大缓冲区占用过多内存
			for len(samples) > 0 {
				m := 5000
				if len(samples) < m {
					m = len(samples)
				}
				// 初始化每个处理器的样本分片缓冲区
				for i := 0; i < concurrency; i++ {
					if shards[i] == nil {
						shards[i] = processors[i].reuseBuf() // 复用处理器的缓冲区
					}
				}
				// 按系列 ID 哈希分配样本到不同分片
				for _, sam := range samples[:m] {
					// 替换为映射后的系列引用(处理重复系列)
					if r, ok := multiRef[sam.Ref]; ok {
						sam.Ref = r
					}
					mod := uint64(sam.Ref) % uint64(concurrency) // 计算哈希值分配到对应处理器
					shards[mod] = append(shards[mod], sam)
				}
				// 将分片发送到对应处理器处理
				for i := 0; i < concurrency; i++ {
					if len(shards[i]) > 0 {
						processors[i].input <- wblSubsetProcessorInputItem{samples: shards[i]}
						shards[i] = nil // 清空分片,准备下一轮
					}
				}
				samples = samples[m:] // 处理剩余样本
			}
			h.wlReplaySamplesPool.Put(v) // 将缓冲区放回对象池
		case []record.RefMmapMarker:
			// 处理内存映射标记
			markers := v
			for _, rm := range markers {
				seq, off := rm.MmapRef.Unpack() // 解析标记的序列号和偏移量
				// 忽略在最后一个已知内存映射块之后的标记(尚未加载)
				if seq > lastSeq || (seq == lastSeq && off > lastOff) {
					continue
				}

				// 替换为映射后的系列引用(处理重复系列)
				if r, ok := multiRef[rm.Ref]; ok {
					rm.Ref = r
				}

				// 查找对应的内存系列
				ms := h.series.getByID(rm.Ref)
				if ms == nil {
					// 系列不存在,记录未知引用
					mmapMarkerUnknownRefs.Inc()
					missingSeries[rm.Ref] = struct{}{}
					continue
				}
				// 按系列 ID 哈希分配到对应处理器
				idx := uint64(ms.ref) % uint64(concurrency)
				processors[idx].input <- wblSubsetProcessorInputItem{mmappedSeries: ms}
			}
		case []record.RefHistogramSample:
			// 处理直方图样本
			samples := v
			// 分块处理(每块不超过 5000 个)
			for len(samples) > 0 {
				m := 5000
				if len(samples) < m {
					m = len(samples)
				}
				// 初始化每个处理器的直方图分片缓冲区
				for i := 0; i < concurrency; i++ {
					if histogramShards[i] == nil {
						histogramShards[i] = processors[i].reuseHistogramBuf() // 复用缓冲区
					}
				}
				// 按系列 ID 哈希分配直方图样本到不同分片
				for _, sam := range samples[:m] {
					// 替换为映射后的系列引用
					if r, ok := multiRef[sam.Ref]; ok {
						sam.Ref = r
					}
					mod := uint64(sam.Ref) % uint64(concurrency)
					histogramShards[mod] = append(histogramShards[mod], histogramRecord{ref: sam.Ref, t: sam.T, h: sam.H})
				}
				// 将分片发送到对应处理器处理
				for i := 0; i < concurrency; i++ {
					if len(histogramShards[i]) > 0 {
						processors[i].input <- wblSubsetProcessorInputItem{histogramSamples: histogramShards[i]}
						histogramShards[i] = nil // 清空分片
					}
				}
				samples = samples[m:] // 处理剩余样本
			}
			h.wlReplayHistogramsPool.Put(v) // 缓冲区放回对象池
		case []record.RefFloatHistogramSample:
			// 处理浮点直方图样本
			samples := v
			// 分块处理(每块不超过 5000 个)
			for len(samples) > 0 {
				m := 5000
				if len(samples) < m {
					m = len(samples)
				}
				// 初始化每个处理器的直方图分片缓冲区
				for i := 0; i < concurrency; i++ {
					if histogramShards[i] == nil {
						histogramShards[i] = processors[i].reuseHistogramBuf() // 复用缓冲区
					}
				}
				// 按系列 ID 哈希分配浮点直方图样本到不同分片
				for _, sam := range samples[:m] {
					// 替换为映射后的系列引用
					if r, ok := multiRef[sam.Ref]; ok {
						sam.Ref = r
					}
					mod := uint64(sam.Ref) % uint64(concurrency)
					histogramShards[mod] = append(histogramShards[mod], histogramRecord{ref: sam.Ref, t: sam.T, fh: sam.FH})
				}
				// 将分片发送到对应处理器处理
				for i := 0; i < concurrency; i++ {
					if len(histogramShards[i]) > 0 {
						processors[i].input <- wblSubsetProcessorInputItem{histogramSamples: histogramShards[i]}
						histogramShards[i] = nil // 清空分片
					}
				}
				samples = samples[m:] // 处理剩余样本
			}
			h.wlReplayFloatHistogramsPool.Put(v) // 缓冲区放回对象池
		default:
			// 遇到未预期的解码类型,触发 panic
			panic(fmt.Errorf("unexpected decodedCh type: %T", d))
		}
	}
	// 合并处理过程中发现的缺失系列引用
	unknownSeriesRefs.merge(missingSeries)

	// 检查解码过程中是否发生错误
	if decodeErr != nil {
		return decodeErr
	}

	// 通知所有工作线程终止并等待它们完成
	for i := 0; i < concurrency; i++ {
		processors[i].closeAndDrain()
	}
	wg.Wait()

	// 检查 WBL 读取器是否发生错误
	if err := r.Err(); err != nil {
		return fmt.Errorf("read records: %w", err)
	}

	// 若存在未知引用,记录警告日志并更新指标
	if unknownSampleRefs.Load()+unknownHistogramRefs.Load()+mmapMarkerUnknownRefs.Load() > 0 {
		h.logger.Warn(
			"Unknown series references for ooo WAL replay",
			"series", unknownSeriesRefs.count(),
			"samples", unknownSampleRefs.Load(),
			"histograms", unknownHistogramRefs.Load(),
			"mmap_markers", mmapMarkerUnknownRefs.Load(),
		)

		// 仅在计数非零时更新指标
		counterAddNonZero(h.metrics.wblReplayUnknownRefsTotal, float64(unknownSeriesRefs.count()), "series")
		counterAddNonZero(h.metrics.wblReplayUnknownRefsTotal, float64(unknownSampleRefs.Load()), "samples")
		counterAddNonZero(h.metrics.wblReplayUnknownRefsTotal, float64(unknownHistogramRefs.Load()), "histograms")
		counterAddNonZero(h.metrics.wblReplayUnknownRefsTotal, float64(mmapMarkerUnknownRefs.Load()), "mmap_markers")
	}

	return nil
}

3.9、第九部分:WBL 相关错误类型定义

// 定义WBL加载错误类型
type errLoadWbl struct {
	err error
}

// 实现Error接口,返回错误信息
func (e errLoadWbl) Error() string {
	return e.err.Error()
}

// 返回底层错误原因
func (e errLoadWbl) Cause() error {
	return e.err
}

// 实现Unwrap接口,用于错误解包
func (e errLoadWbl) Unwrap() error {
	return e.err
}  

3.10、第十部分:WBL 子集处理器

3.10.1、wbl 子集处理器定义

// WBL子集处理器,用于并行处理WBL中的样本数据
type wblSubsetProcessor struct {
	input            chan wblSubsetProcessorInputItem  // 输入通道,接收待处理的数据项
	output           chan []record.RefSample           // 输出通道,用于样本缓冲区重用
	histogramsOutput chan []histogramRecord            // 输出通道,用于直方图缓冲区重用
}

// WBL子集处理器的输入数据项
type wblSubsetProcessorInputItem struct {
	mmappedSeries    *memSeries                        // 内存映射的序列
	samples          []record.RefSample                // 样本数据
	histogramSamples []histogramRecord                  // 直方图样本数据
}

3.10.2、WBL 子集处理器处理器方法

// 初始化处理器的通道
func (wp *wblSubsetProcessor) setup() {
	wp.output = make(chan []record.RefSample, 300)
	wp.histogramsOutput = make(chan []histogramRecord, 300)
	wp.input = make(chan wblSubsetProcessorInputItem, 300)
}

// 关闭处理器并清空通道
func (wp *wblSubsetProcessor) closeAndDrain() {
	close(wp.input)
	// 清空输出通道
	for range wp.output {
	}
	for range wp.histogramsOutput {
	}
}

// 重用样本缓冲区,如果输出通道有缓冲区则返回,否则返回nil
func (wp *wblSubsetProcessor) reuseBuf() []record.RefSample {
	select {
	case buf := <-wp.output:
		return buf[:0]
	default:
	}
	return nil
}

// 重用直方图缓冲区,如果输出通道有缓冲区则返回,否则返回nil
func (wp *wblSubsetProcessor) reuseHistogramBuf() []histogramRecord {
	select {
	case buf := <-wp.histogramsOutput:
		return buf[:0]
	default:
	}
	return nil
}

3.11、第十一部分:WBL 样本处理逻辑

// 处理WBL样本,将样本添加到头部并将缓冲区传递到输出通道以供重用
func (wp *wblSubsetProcessor) processWBLSamples(h *Head) (map[chunks.HeadSeriesRef]struct{}, uint64, uint64) {
	defer close(wp.output)
	defer close(wp.histogramsOutput)

	missingSeries := make(map[chunks.HeadSeriesRef]struct{}) // 记录缺失的序列引用
	var unknownSampleRefs, unknownHistogramRefs uint64        // 记录未知的样本和直方图引用数量

	oooCapMax := h.opts.OutOfOrderCapMax.Load()              // 加载乱序样本容量上限配置
	// 不检查乱序样本的最小有效时间
	mint, maxt := int64(math.MaxInt64), int64(math.MinInt64)
	
	// 处理输入通道中的所有数据项
	for in := range wp.input {
		// 如果存在内存映射序列且有乱序数据,清除头部块
		if in.mmappedSeries != nil && in.mmappedSeries.ooo != nil {
			// 到目前为止所有样本都已内存映射,因此清除headChunk
			// 注意:如果由于块大小参数更改导致一些样本进入内存映射块,此处不处理
			in.mmappedSeries.ooo.oooHeadChunk = nil
			continue
		}
		
		// 处理普通样本
		for _, s := range in.samples {
			ms := h.series.getByID(s.Ref)
			if ms == nil {
				unknownSampleRefs++
				missingSeries[s.Ref] = struct{}{}
				continue
			}
			// 将样本插入序列
			ok, chunkCreated, _ := ms.insert(s.T, s.V, nil, nil, h.chunkDiskMapper, oooCapMax, h.logger)
			if chunkCreated {
				h.metrics.chunksCreated.Inc()
				h.metrics.chunks.Inc()
			}
			if ok {
				// 更新时间范围
				if s.T < mint {
					mint = s.T
				}
				if s.T > maxt {
					maxt = s.T
				}
			}
		}
		// 将处理完的样本缓冲区发送到输出通道供重用
		select {
		case wp.output <- in.samples:
		default:
		}
		
		// 处理直方图样本
		for _, s := range in.histogramSamples {
			ms := h.series.getByID(s.ref)
			if ms == nil {
				unknownHistogramRefs++
				missingSeries[s.ref] = struct{}{}
				continue
			}
			var chunkCreated bool
			var ok bool
			// 根据直方图类型插入数据
			if s.h != nil {
				ok, chunkCreated, _ = ms.insert(s.t, 0, s.h, nil, h.chunkDiskMapper, oooCapMax, h.logger)
			} else {
				ok, chunkCreated, _ = ms.insert(s.t, 0, nil, s.fh, h.chunkDiskMapper, oooCapMax, h.logger)
			}
			if chunkCreated {
				h.metrics.chunksCreated.Inc()
				h.metrics.chunks.Inc()
			}
			if ok {
				// 更新时间范围
				if s.t > maxt {
					maxt = s.t
				}
				if s.t < mint {
					mint = s.t
				}
			}
		}
		// 将处理完的直方图缓冲区发送到输出通道供重用
		select {
		case wp.histogramsOutput <- in.histogramSamples:
		default:
		}
	}

	// 更新乱序样本的最小和最大时间
	h.updateMinOOOMaxOOOTime(mint, maxt)

	return missingSeries, unknownSampleRefs, unknownHistogramRefs
}

3.13、第十三部分:块快照记录类型定义

// 块快照记录类型常量
const (
	chunkSnapshotRecordTypeSeries     uint8 = 1  // 序列记录类型
	chunkSnapshotRecordTypeTombstones uint8 = 2  // 墓碑记录类型
	chunkSnapshotRecordTypeExemplars  uint8 = 3  // 示例记录类型
)

// 块快照记录结构,包含序列的相关信息
type chunkSnapshotRecord struct {
	ref                     chunks.HeadSeriesRef           // 序列引用
	lset                    labels.Labels                  // 标签集
	mc                      *memChunk                      // 内存块
	lastValue               float64                        // 最后一个值
	lastHistogramValue      *histogram.Histogram           // 最后一个直方图值
	lastFloatHistogramValue *histogram.FloatHistogram      // 最后一个浮点直方图值
}

3.14、第十四部分:内存序列编码为快照记录

// 将内存序列编码为快照记录
func (s *memSeries) encodeToSnapshotRecord(b []byte) []byte {
	buf := encoding.Encbuf{B: b}

	buf.PutByte(chunkSnapshotRecordTypeSeries)              // 写入记录类型
	buf.PutBE64(uint64(s.ref))                              // 写入序列引用
	record.EncodeLabels(&buf, s.labels())                   // 写入标签
	buf.PutBE64int64(0)                                     // 向后兼容,原为chunkRange,现已不用

	s.Lock()
	if s.headChunks == nil {
		buf.PutUvarint(0)                                   // 没有头部块
	} else {
		enc := s.headChunks.chunk.Encoding()
		buf.PutUvarint(1)                                   // 有头部块
		buf.PutBE64int64(s.headChunks.minTime)               // 写入最小时间
		buf.PutBE64int64(s.headChunks.maxTime)               // 写入最大时间
		buf.PutByte(byte(enc))                               // 写入编码类型
		buf.PutUvarintBytes(s.headChunks.chunk.Bytes())      // 写入块数据

		// 根据编码类型处理最后一个值
		switch enc {
		case chunkenc.EncXOR:
			// 向后兼容旧的sampleBuf,包含最后4个样本
			for i := 0; i < 3; i++ {
				buf.PutBE64int64(0)
				buf.PutBEFloat64(0)
			}
			buf.PutBE64int64(0)
			buf.PutBEFloat64(s.lastValue)
		case chunkenc.EncHistogram:
			record.EncodeHistogram(&buf, s.lastHistogramValue)
		default: // chunkenc.FloatHistogram
			record.EncodeFloatHistogram(&buf, s.lastFloatHistogramValue)
		}
	}
	s.Unlock()

	return buf.Get()
}

3.15、第十五部分:从块快照解码序列

// 从块快照解码序列
func decodeSeriesFromChunkSnapshot(d *record.Decoder, b []byte) (csr chunkSnapshotRecord, err error) {
	dec := encoding.Decbuf{B: b}

	// 验证记录类型
	if flag := dec.Byte(); flag != chunkSnapshotRecordTypeSeries {
		return csr, fmt.Errorf("invalid record type %x", flag)
	}

	csr.ref = chunks.HeadSeriesRef(dec.Be64())                // 解码序列引用
	csr.lset = d.DecodeLabels(&dec)                           // 解码标签集

	_ = dec.Be64int64() // 原为chunkRange,现已不用
	if dec.Uvarint() == 0 {
		return // 没有头部块
	}

	// 解码内存块信息
	csr.mc = &memChunk{}
	csr.mc.minTime = dec.Be64int64()
	csr.mc.maxTime = dec.Be64int64()
	enc := chunkenc.Encoding(dec.Byte())

	// 复制块数据(底层字节将在以后重用)
	chunkBytes := dec.UvarintBytes()
	chunkBytesCopy := make([]byte, len(chunkBytes))
	copy(chunkBytesCopy, chunkBytes)

	// 从数据创建块
	chk, err := chunkenc.FromData(enc, chunkBytesCopy)
	if err != nil {
		return csr, fmt.Errorf("chunk from data: %w", err)
	}
	csr.mc.chunk = chk

	// 根据编码类型解码最后一个值
	switch enc {
	case chunkenc.EncXOR:
		// 向后兼容旧的sampleBuf
		for i := 0; i < 3; i++ {
			_ = dec.Be64int64()
			_ = dec.Be64Float64()
		}
		_ = dec.Be64int64()
		csr.lastValue = dec.Be64Float64()
	case chunkenc.EncHistogram:
		csr.lastHistogramValue = &histogram.Histogram{}
		record.DecodeHistogram(&dec, csr.lastHistogramValue)
	default: // chunkenc.FloatHistogram
		csr.lastFloatHistogramValue = &histogram.FloatHistogram{}
		record.DecodeFloatHistogram(&dec, csr.lastFloatHistogramValue)
	}

	// 检查解码错误
	err = dec.Err()
	if err != nil && len(dec.B) > 0 {
		err = fmt.Errorf("unexpected %d bytes left in entry", len(dec.B))
	}

	return
}

3.16、墓碑数据快照编解码

// 将墓碑数据编码为快照记录
func encodeTombstonesToSnapshotRecord(tr tombstones.Reader) ([]byte, error) {
	buf := encoding.Encbuf{}

	buf.PutByte(chunkSnapshotRecordTypeTombstones)             // 写入记录类型
	b, err := tombstones.Encode(tr)                            // 编码墓碑数据
	if err != nil {
		return nil, fmt.Errorf("encode tombstones: %w", err)
	}
	buf.PutUvarintBytes(b)                                     // 写入编码后的墓碑数据

	return buf.Get(), nil
}

// 从快照记录解码墓碑数据
func decodeTombstonesSnapshotRecord(b []byte) (tombstones.Reader, error) {
	dec := encoding.Decbuf{B: b}

	// 验证记录类型
	if flag := dec.Byte(); flag != chunkSnapshotRecordTypeTombstones {
		return nil, fmt.Errorf("invalid record type %x", flag)
	}

	// 解码墓碑数据
	tr, err := tombstones.Decode(dec.UvarintBytes())
	if err != nil {
		return tr, fmt.Errorf("decode tombstones: %w", err)
	}
	return tr, nil
}

3.17、块快照创建功能

// ChunkSnapshot创建头部中所有序列和墓碑的快照
// 如果快照创建成功,会删除旧的块快照
// 块快照存储在名为chunk_snapshot.N.M的目录中,使用WAL包写入
// N是快照期间存在的最后一个WAL段,M是段N中已写入数据的偏移量
// 快照首先包含所有序列(每个在单独的记录中且未排序),然后是墓碑(单个记录),最后是示例(>=1个记录)
// 示例按照写入循环缓冲区的顺序排列
func (h *Head) ChunkSnapshot() (*ChunkSnapshotStats, error) {
	if h.wal == nil {
		// 如果没有存储WAL,创建快照没有意义
		h.logger.Warn("skipping chunk snapshotting as WAL is disabled")
		return &ChunkSnapshotStats{}, nil
	}
	h.chunkSnapshotMtx.Lock()
	defer h.chunkSnapshotMtx.Unlock()

	stats := &ChunkSnapshotStats{}

	// 获取最后一个WAL段和偏移量
	wlast, woffset, err := h.wal.LastSegmentAndOffset()
	if err != nil && !errors.Is(err, record.ErrNotFound) {
		return stats, fmt.Errorf("get last wal segment and offset: %w", err)
	}

	// 查找最后一个块快照
	_, cslast, csoffset, err := LastChunkSnapshot(h.opts.ChunkDirRoot)
	if err != nil && !errors.Is(err, record.ErrNotFound) {
		return stats, fmt.Errorf("find last chunk snapshot: %w", err)
	}

	// 如果WAL没有新数据,无需创建快照
	if wlast == cslast && woffset == csoffset {
		return stats, nil
	}

	snapshotName := chunkSnapshotDir(wlast, woffset)

	cpdir := filepath.Join(h.opts.ChunkDirRoot, snapshotName)
	cpdirtmp := cpdir + ".tmp"  // 临时目录,用于原子操作
	stats.Dir = cpdir

	// 创建临时目录
	if err := os.MkdirAll(cpdirtmp, 0o777); err != nil {
		return stats, fmt.Errorf("create chunk snapshot dir: %w", err)
	}
	// 打开块快照
	cp, err := wlog.New(nil, nil, cpdirtmp, h.wal.CompressionType())
	if err != nil {
		return stats, fmt.Errorf("open chunk snapshot: %w", err)
	}

	// 确保错误返回时不会留下临时文件
	defer func() {
		cp.Close()
		os.RemoveAll(cpdirtmp)
	}()

	var (
		buf  []byte          // 缓冲区
		recs [][]byte        // 记录集合
	)
	// 将所有序列添加到快照
	stripeSize := h.series.size
	for i := 0; i < stripeSize; i++ {
		h.series.locks[i].RLock()

		for _, s := range h.series.series[i] {
			start := len(buf)
			buf = s.encodeToSnapshotRecord(buf)
			if len(buf[start:]) == 0 {
				continue // 内容已被丢弃
			}
			recs = append(recs, buf[start:])
			// 每10MB刷新一次记录
			if len(buf) > 10*1024*1024 {
				if err := cp.Log(recs...); err != nil {
					h.series.locks[i].RUnlock()
					return stats, fmt.Errorf("flush records: %w", err)
				}
				buf, recs = buf[:0], recs[:0]
			}
		}
		stats.TotalSeries += len(h.series.series[i])

		h.series.locks[i].RUnlock()
	}

	// 将墓碑添加到快照
	tombstonesReader, err := h.Tombstones()
	if err != nil {
		return stats, fmt.Errorf("get tombstones: %w", err)
	}
	rec, err := encodeTombstonesToSnapshotRecord(tombstonesReader)
	if err != nil {
		return stats, fmt.Errorf("encode tombstones: %w", err)
	}
	recs = append(recs, rec)
	// 刷新剩余的序列记录和墓碑
	if err := cp.Log(recs...); err != nil {
		return stats, fmt.Errorf("flush records: %w", err)
	}
	buf = buf[:0]

	// 将示例添加到快照
	// 批量记录,每个记录最多包含10000个示例
	// 假设每个示例100字节(高估),约1MB
	maxExemplarsPerRecord := 10000
	batch := make([]record.RefExemplar, 0, maxExemplarsPerRecord)
	enc := record.Encoder{}
	// 刷新示例批次的函数
	flushExemplars := func() error {
		if len(batch) == 0 {
			return nil
		}
		buf = buf[:0]
		encbuf := encoding.Encbuf{B: buf}
		encbuf.PutByte(chunkSnapshotRecordTypeExemplars)
		enc.EncodeExemplarsIntoBuffer(batch, &encbuf)
		if err := cp.Log(encbuf.Get()); err != nil {
			return fmt.Errorf("log exemplars: %w", err)
		}
		buf, batch = buf[:0], batch[:0]
		return nil
	}
	// 迭代所有示例并添加到快照
	err = h.exemplars.IterateExemplars(func(seriesLabels labels.Labels, e exemplar.Exemplar) error {
		if len(batch) >= maxExemplarsPerRecord {
			if err := flushExemplars(); err != nil {
				return fmt.Errorf("flush exemplars: %w", err)
			}
		}

		// 查找示例对应的序列
		ms := h.series.getByHash(seriesLabels.Hash(), seriesLabels)
		if ms == nil {
			// 示例可能引用了一些旧序列,丢弃这些示例
			return nil
		}
		batch = append(batch, record.RefExemplar{
			Ref:    ms.ref,
			T:      e.Ts,
			V:      e.Value,
			Labels: e.Labels,
		})
		return nil
	})
	if err != nil {
		return stats, fmt.Errorf("iterate exemplars: %w", err)
	}

	// 刷新剩余的示例
	if err := flushExemplars(); err != nil {
		return stats, fmt.Errorf("flush exemplars at the end: %w", err)
	}

	// 关闭块快照
	if err := cp.Close(); err != nil {
		return stats, fmt.Errorf("close chunk snapshot: %w", err)
	}
	// 原子重命名临时目录
	if err := fileutil.Replace(cpdirtmp, cpdir); err != nil {
		return stats, fmt.Errorf("rename chunk snapshot directory: %w", err)
	}

	// 删除旧的块快照
	if err := DeleteChunkSnapshots(h.opts.ChunkDirRoot, wlast, woffset); err != nil {
		// 剩余的旧块快照不会导致后续问题,只会占用磁盘空间
		// 由于存在更高版本的块快照,它们会被忽略
		h.logger.Error("delete old chunk snapshots", "err", err)
	}
	return stats, nil
}

3.18、块快照目录工具函数

// 生成块快照目录名
func chunkSnapshotDir(wlast, woffset int) string {
	return fmt.Sprintf(chunkSnapshotPrefix+"%06d.%010d", wlast, woffset)
}

// 执行块快照创建
func (h *Head) performChunkSnapshot() error {
	h.logger.Info("creating chunk snapshot")
	startTime := time.Now()
	stats, err := h.ChunkSnapshot()
	elapsed := time.Since(startTime)
	if err == nil {
		h.logger.Info("chunk snapshot complete", "duration", elapsed.String(), "num_series", stats.TotalSeries, "dir", stats.Dir)
	}
	if err != nil {
		return fmt.Errorf("chunk snapshot: %w", err)
	}
	return nil
}

// ChunkSnapshotStats返回创建的块快照的统计信息
type ChunkSnapshotStats struct {
	TotalSeries int    // 总序列数
	Dir         string // 快照目录
}

3.19、块快照查找与删除

// LastChunkSnapshot返回最近的块快照的目录名和索引
// 如果目录中没有块快照,返回ErrNotFound
func LastChunkSnapshot(dir string) (string, int, int, error) {
	files, err := os.ReadDir(dir)
	if err != nil {
		return "", 0, 0, err
	}
	maxIdx, maxOffset := -1, -1
	maxFileName := ""
	for i := 0; i < len(files); i++ {
		fi := files[i]

		if !strings.HasPrefix(fi.Name(), chunkSnapshotPrefix) {
			continue
		}
		if !fi.IsDir() {
			return "", 0, 0, fmt.Errorf("chunk snapshot %s is not a directory", fi.Name())
		}

		// 解析快照目录名
		splits := strings.Split(fi.Name()[len(chunkSnapshotPrefix):], ".")
		if len(splits) != 2 {
			// 块快照格式不正确,忽略
			continue
		}

		idx, err := strconv.Atoi(splits[0])
		if err != nil {
			continue
		}

		offset, err := strconv.Atoi(splits[1])
		if err != nil {
			continue
		}

		// 找到最新的快照
		if idx > maxIdx || (idx == maxIdx && offset > maxOffset) {
			maxIdx, maxOffset = idx, offset
			maxFileName = filepath.Join(dir, fi.Name())
		}
	}
	if maxFileName == "" {
		return "", 0, 0, record.ErrNotFound
	}
	return maxFileName, maxIdx, maxOffset, nil
}

// DeleteChunkSnapshots删除目录中低于给定索引的所有块快照
func DeleteChunkSnapshots(dir string, maxIndex, maxOffset int) error {
	files, err := os.ReadDir(dir)
	if err != nil {
		return err
	}

	errs := tsdb_errors.NewMulti()
	for _, fi := range files {
		if !strings.HasPrefix(fi.Name(), chunkSnapshotPrefix) {
			continue
		}

		// 解析快照目录名
		splits := strings.Split(fi.Name()[len(chunkSnapshotPrefix):], ".")
		if len(splits) != 2 {
			continue
		}

		idx, err := strconv.Atoi(splits[0])
		if err != nil {
			continue
		}

		offset, err := strconv.Atoi(splits[1])
		if err != nil {
			continue
		}

		// 删除旧快照
		if idx < maxIndex || (idx == maxIndex && offset < maxOffset) {
			if err := os.RemoveAll(filepath.Join(dir, fi.Name())); err != nil {
				errs.Add(err)
			}
		}
	}
	return errs.Err()
}

3.20、块快照加载功能

// loadChunkSnapshot重放块快照并从快照恢复Head状态
// 如果返回任何错误,调用者有责任清除Head的内容
func (h *Head) loadChunkSnapshot() (int, int, map[chunks.HeadSeriesRef]*memSeries, error) {
	// 查找最近的块快照
	dir, snapIdx, snapOffset, err := LastChunkSnapshot(h.opts.ChunkDirRoot)
	if err != nil {
		if errors.Is(err, record.ErrNotFound) {
			return snapIdx, snapOffset, nil, nil
		}
		return snapIdx, snapOffset, nil, fmt.Errorf("find last chunk snapshot: %w", err)
	}

	start := time.Now()
	// 打开块快照的段读取器
	sr, err := wlog.NewSegmentsReader(dir)
	if err != nil {
		return snapIdx, snapOffset, nil, fmt.Errorf("open chunk snapshot: %w", err)
	}
	defer func() {
		if err := sr.Close(); err != nil {
			h.logger.Warn("error while closing the wal segments reader", "err", err)
		}
	}()

	var (
		numSeries        = 0                                  // 序列数量
		unknownRefs      = int64(0)                           // 未知引用数量
		concurrency      = h.opts.WALReplayConcurrency        // 并发数
		wg               sync.WaitGroup                       // 等待组
		recordChan       = make(chan chunkSnapshotRecord, 5*concurrency) // 记录通道
		shardedRefSeries = make([]map[chunks.HeadSeriesRef]*memSeries, concurrency) // 分片的序列引用映射
		errChan          = make(chan error, concurrency)      // 错误通道
		refSeries        map[chunks.HeadSeriesRef]*memSeries  // 序列引用映射
		exemplarBuf      []record.RefExemplar                 // 示例缓冲区
		syms             = labels.NewSymbolTable()            // 整个快照的新符号表
		dec              = record.NewDecoder(syms)            // 解码器
	)

	// 启动并发处理器
	wg.Add(concurrency)
	for i := 0; i < concurrency; i++ {
		go func(idx int, rc <-chan chunkSnapshotRecord) {
			defer wg.Done()
			defer func() {
				// 如果有错误,清空通道以解除主线程阻塞
				for range rc {
				}
			}()

			shardedRefSeries[idx] = make(map[chunks.HeadSeriesRef]*memSeries)
			localRefSeries := shardedRefSeries[idx]

			// 处理记录通道中的所有记录
			for csr := range rc {
				series, _, err := h.getOrCreateWithID(csr.ref, csr.lset.Hash(), csr.lset, false)
				if err != nil {
					errChan <- err
					return
				}
				localRefSeries[csr.ref] = series
				// 更新最后一个序列ID
				for {
					seriesID := uint64(series.ref)
					lastSeriesID := h.lastSeriesID.Load()
					if lastSeriesID >= seriesID || h.lastSeriesID.CompareAndSwap(lastSeriesID, seriesID) {
						break
					}
				}

				if csr.mc == nil {
					continue
				}
				// 恢复序列的块信息
				series.nextAt = csr.mc.maxTime // 这将在追加时创建新块
				series.headChunks = csr.mc
				series.lastValue = csr.lastValue
				series.lastHistogramValue = csr.lastHistogramValue
				series.lastFloatHistogramValue = csr.lastFloatHistogramValue

				// 恢复块的追加器
				app, err := series.headChunks.chunk.Appender()
				if err != nil {
					errChan <- err
					return
				}
				series.app = app

				// 更新时间范围
				h.updateMinMaxTime(csr.mc.minTime, csr.mc.maxTime)
			}
		}(i, recordChan)
	}

	// 读取快照记录并处理
	r := wlog.NewReader(sr)
	var loopErr error
Outer:
	for r.Next() {
		select {
		case err := <-errChan:
			errChan <- err
			break Outer
		default:
		}

		rec := r.Record()
		switch rec[0] {
		case chunkSnapshotRecordTypeSeries:
			numSeries++
			// 解码序列记录
			csr, err := decodeSeriesFromChunkSnapshot(&dec, rec)
			if err != nil {
				loopErr = fmt.Errorf("decode series record: %w", err)
				break Outer
			}
			recordChan <- csr

		case chunkSnapshotRecordTypeTombstones:
			// 解码墓碑记录
			tr, err := decodeTombstonesSnapshotRecord(rec)
			if err != nil {
				loopErr = fmt.Errorf("decode tombstones: %w", err)
				break Outer
			}

			// 应用墓碑数据
			if err = tr.Iter(func(ref storage.SeriesRef, ivs tombstones.Intervals) error {
				h.tombstones.AddInterval(ref, ivs...)
				return nil
			}); err != nil {
				loopErr = fmt.Errorf("iterate tombstones: %w", err)
				break Outer
			}

		case chunkSnapshotRecordTypeExemplars:
			// 示例在快照的末尾,此时所有序列都已加载
			if len(refSeries) == 0 {
				close(recordChan)
				wg.Wait()

				// 合并分片的序列引用映射
				refSeries = make(map[chunks.HeadSeriesRef]*memSeries, numSeries)
				for _, shard := range shardedRefSeries {
					for k, v := range shard {
						refSeries[k] = v
					}
				}
			}

			// 如果禁用示例存储,跳过
			if !h.opts.EnableExemplarStorage || h.opts.MaxExemplars.Load() <= 0 {
				continue Outer
			}

			decbuf := encoding.Decbuf{B: rec[1:]}

			// 解码示例数据
			exemplarBuf = exemplarBuf[:0]
			exemplarBuf, err = dec.ExemplarsFromBuffer(&decbuf, exemplarBuf)
			if err != nil {
				loopErr = fmt.Errorf("exemplars from buffer: %w", err)
				break Outer
			}

			// 添加示例到头部
			for _, e := range exemplarBuf {
				ms, ok := refSeries[e.Ref]
				if !ok {
					unknownRefs++
					continue
				}

				if err := h.exemplars.AddExemplar(ms.labels(), exemplar.Exemplar{
					Labels: e.Labels,
					Value:  e.V,
					Ts:     e.T,
				}); err != nil {
					loopErr = fmt.Errorf("add exemplar: %w", err)
					break Outer
				}
			}

		default:
			// 不支持的快照记录类型
			loopErr = fmt.Errorf("unsupported snapshot record type 0b%b", rec[0])
			break Outer
		}
	}
	// 如果没有示例记录,在此处构建映射
	if len(refSeries) == 0 {
		close(recordChan)
		wg.Wait()
	}

	// 收集错误
	close(errChan)
	merr := tsdb_errors.NewMulti()
	if loopErr != nil {
		merr.Add(fmt.Errorf("decode loop: %w", loopErr))
	}
	for err := range errChan {
		merr.Add(fmt.Errorf("record processing: %w", err))
	}
	if err := merr.Err(); err != nil {
		return -1, -1, nil, err
	}

	// 检查读取错误
	if err := r.Err(); err != nil {
		return -1, -1, nil, fmt.Errorf("read records: %w", err)
	}

	// 构建序列引用映射(如果尚未构建)
	if len(refSeries) == 0 {
		refSeries = make(map[chunks.HeadSeriesRef]*memSeries, numSeries)
		for _, shard := range shardedRefSeries {
			for k, v := range shard {
				refSeries[k] = v
			}
		}
	}

	// 记录快照加载信息
	elapsed := time.Since(start)
	h.logger.Info("chunk snapshot loaded", "dir", dir, "num_series", numSeries, "duration", elapsed.String())
	if unknownRefs > 0 {
		h.logger.Warn("unknown series references during chunk snapshot replay", "count", unknownRefs)
	}

	return snapIdx, snapOffset, refSeries, nil
}
posted @ 2025-09-28 10:48  左扬  阅读(53)  评论(0)    收藏  举报