Thanos源码专题【左扬精讲】——Thanos Sidecar 组件(release-0.26)源码阅读和分析(第二章——详解 cmd/sidecar.go )

Thanos Sidecar 组件(release-0.26)源码阅读和分析(第二章——cmd/sidecar.go 代码解析与设计思想)

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

  将从架构设计、核心模块实现、Golang语法精要三个维度深入剖析Thanos Sidecar v0.26源码。

  通过源码分析可见,Thanos Sidecar 很好的诠释了云原生基础设施组件的设计要义:高效、可靠、可观测。其代码实现中大量运用了Golang的并发原语和接口特性,是学习分布式系统开发的优秀范本。

一、主函数架构设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
func runSidecar(...) error {
    // 初始化HTTP客户端(用于与Prometheus通信)
    httpClient, err := httpconfig.NewHTTPClient(...)
     
    // 创建元数据管理器(核心状态机)
    m := &promMetadata{
        client: promclient.NewWithTracingClient(...),
        mint:   conf.limitMinTime.PrometheusTimestamp(),
        maxt:   math.MaxInt64
    }
     
    // 初始化双模式探针系统
    grpcProbe := prober.NewGRPC()
    httpProbe := prober.NewHTTP()
    statusProber := prober.Combine(...)
     
    // 启动HTTP Server(含健康检查端点)
    srv := httpserver.New(...)
    g.Add(func() error {
        statusProber.Healthy()
        return srv.ListenAndServe()
    }, ...)
     
    // 启动gRPC Server(StoreAPI实现)
    s := grpcserver.New(...)
    g.Add(func() error {
        return s.ListenAndServe()
    }, ...)
     
    // 启动块上传Shipper
    if uploads {
        bkt := client.NewBucket(...)
        s := shipper.New(...)
        g.Add(func() error {
            return runutil.Repeat(30*time.Second, ...)
        }, ...)
    }
}

解读

1.1、分层式架构

    • 通信层:HTTP/gRPC双协议支持

    • 状态层:promMetadata集中管理Prometheus元数据

    • 服务层:独立模块处理上传/查询/健康检查

1.2、生命周期管理

1
g.Add(func() error { /* 启动逻辑 */ }, func(error) { /* 关闭逻辑 */ })
    • 使用run.Group实现优雅关闭

    • 每个组件注册独立的启动/清理函数

1.3、探针状态机

1
2
3
4
5
statusProber := prober.Combine(
    httpProbe,
    grpcProbe,
    instrumentationProbe
)
    • 组合式探针设计

    • 各子探针独立维护状态

二、Prometheus 元数据管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
type promMetadata struct {
    mtx    sync.Mutex
    mint   int64  // 最小时间戳
    maxt   int64  // 最大时间戳
    labels labels.Labels // 外部标签
}
 
// 并发安全的状态更新
func (s *promMetadata) UpdateLabels(ctx context.Context) error {
    s.mtx.Lock()
    defer s.mtx.Unlock()
     
    elset, err := s.client.ExternalLabels(ctx, s.promURL)
    s.labels = elset
    return err
}
 
// 时间范围更新
func (s *promMetadata) UpdateTimestamps(mint, maxt int64) {
    s.mtx.Lock()
    defer s.mtx.Unlock()
     
    if mint < s.limitMinTime.PrometheusTimestamp() {
        mint = s.limitMinTime.PrometheusTimestamp()
    }
    s.mint, s.maxt = mint, maxt
}

2.1、互斥锁应用

    • sync.Mutex保证并发访问安全

    • defer确保锁必然释放

2.2、时间处理

1
mint := conf.limitMinTime.PrometheusTimestamp()
    • 自定义TimeOrDurationValue类型处理时间输入

    • 支持绝对时间(RFC3339)和相对时间(如-1h)

2.3、标签系统

1
labels.Labels{{Name: "cluster", Value: "us-east-1"}}
    • 使用Prometheus官方labels包

    • 标签哈希快速比较

三、block 上传机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Shipper初始化
s := shipper.New(
    logger,
    reg,
    conf.tsdb.path,  // Prometheus数据目录
    bkt,             // 对象存储客户端
    m.Labels,        // 外部标签注入
    metadata.SidecarSource,
    conf.shipper.uploadCompacted,
)
 
// 定时同步循环
runutil.Repeat(30*time.Second, ctx.Done(), func() error {
    uploaded, err := s.Sync(ctx)
    minTime, _, _ := s.Timestamps()
    m.UpdateTimestamps(minTime, math.MaxInt64)
    return nil
})

3.1、增量上传

    • 通过shipper.Sync()识别新块

    • 依赖本地目录的mtime判断

3.2、元数据增强

1
2
3
4
5
6
meta := metadata.Meta{
    Thanos: metadata.Thanos{
        Labels:  m.Labels(),
        Source:  metadata.SidecarSource,
    }
}
    • 注入集群标签实现全局唯一

3.3、错误恢复

1
2
3
runutil.Retry(2*time.Second, ctx.Done(), func() error {
    return checkLabelsExist()
})
    • 指数退避重试机制
    • 上下文感知的超时控制

四、gRPC StoreAPI 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 注册StoreServer
grpcserver.WithServer(store.RegisterStoreServer(
    store.NewPrometheusStore(
        logger,
        reg,
        promclient.NewWithTracingClient(...),
        conf.prometheus.url,
        component.Sidecar,
        m.Labels,     // 动态标签获取
        m.Timestamps, // 动态时间范围
        m.Version,    // Prometheus版本
    )
))
 
// 流式传输实现
func (s *PrometheusStore) Series(req *storepb.SeriesRequest, srv storepb.Store_SeriesServer) error {
    for _, series := range query.Series {
        resp := &storepb.SeriesResponse{
            Series: convertSeries(series),
        }
        if err := srv.Send(resp); err != nil {
            return status.Error(codes.Aborted, "send failed")
        }
    }
    return nil
} 

1、零拷贝转换

1
labelpb.ZLabelSet{Labels: labelpb.ZLabelsFromPromLabels(series.Labels)}
    • 避免内存复制

    • 复用Prometheus内部数据结构

2、流控机制

1
2
3
// 客户端控制的分页请求
req.SkipChunks = true
req.MaxResolutionWindow = 5 * time.Minute

3、并发安全

1
2
3
4
5
func (s *PrometheusStore) LabelSet() []labelpb.ZLabelSet {
    return []labelpb.ZLabelSet{
        {Labels: labelpb.ZLabelsFromPromLabels(s.labels())},
    }
}
    • 通过闭包获取最新标签

    • 无锁读取当前状态

五、Golang 高级特性应用及设计思想

5.1、接口解耦

1
2
3
4
5
6
7
8
type Bucket interface {
    Upload(ctx context.Context, name string, r io.Reader) error
    Delete(ctx context.Context, name string) error
    // ...
}
 
// 对象存储实现可插拔
bkt, _ := client.NewBucket(..., confContentYaml, ...)

5.2、函数式选项模式

1
2
3
4
5
6
7
8
9
10
11
12
13
type ServerOption func(*server)
 
func WithListen(addr string) ServerOption {
    return func(s *server) {
        s.addr = addr
    }
}
 
// 使用方式
srv := NewServer(
    WithListen("0.0.0.0:10901"),
    WithGracePeriod(5*time.Second),
)

5.3、原子状态管理

1
2
3
4
5
6
7
8
type HTTPProbe struct {
    ready   atomic.Uint32
    healthy atomic.Uint32
}
 
func (p *HTTPProbe) Ready() {
    p.ready.Store(1) // 无锁原子操作
}

5.4、Context上下文

1
2
3
4
5
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
 
// 级联取消所有子操作
s.client.ExternalLabels(ctx, ...)

5.5、微服务化架构

sidecar.go中,Thanos 通过接口隔离模块化封装实现了微服务化架构。以下从几个典型模块详细说明:

5.5.1、对象存储模块(Bcket Interface)

5.5.1.1、接口定义
1
2
3
4
5
6
7
// 所有对象存储实现必须满足的接口
type Bucket interface {
    Upload(ctx context.Context, name string, r io.Reader) error
    Delete(ctx context.Context, name string) error
    Iter(ctx context.Context, dir string, f func(string) error) error
    // ...
}
5.5.1.2、模块封装
1
2
3
4
5
6
7
// 创建具体存储实例(隐藏实现细节)
bkt, err := client.NewBucket(
    logger,
    confContentYaml, // 配置内容(YAML格式)
    reg,             // Prometheus 注册表
    component.Sidecar.String(),
)
5.5.1.3、接口交互
1
2
3
4
5
6
7
8
9
10
// Shipper 模块仅依赖接口
s := shipper.New(
    logger,
    reg,
    conf.tsdb.path,
    bkt, // 传入 Bucket 接口实现
    m.Labels,
    metadata.SidecarSource,
    conf.shipper.uploadCompacted,
)
5.5.1.4、设计亮点
      1. 更换存储后端只需修改配置,无需改动 Shipper 代码

      2. 接口方法明确,各方法职责单一(SRP原则)

5.5.2、元数据管理模块(promMetadata)

5.5.2.1、封装结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type promMetadata struct {
    mtx    sync.Mutex
    labels labels.Labels
    mint   int64
    maxt   int64
    // ...
}
 
// 对外暴露只读接口
func (s *promMetadata) Labels() labels.Labels {
    s.mtx.Lock()
    defer s.mtx.Unlock()
    return s.labels
}
 
// 内部实现状态更新
func (s *promMetadata) UpdateLabels(ctx context.Context) error {
    // 通过 client 获取最新标签
    elset, err := s.client.ExternalLabels(ctx, s.promURL)
    // ...
}
5.5.2.2、接口交互
1
2
3
4
5
6
7
8
9
10
11
// StoreAPI 通过闭包动态获取最新状态
store.NewPrometheusStore(
    logger,
    reg,
    c,
    conf.prometheus.url,
    component.Sidecar,
    m.Labels,  // 方法引用(非直接访问字段)
    m.Timestamps,
    m.Version,
)
5.5.2.3、设计亮点
      1. 状态访问通过方法代理,实现线程安全
      2. 对外隐藏同步锁等并发控制细节

5.5.3、健康检查模块(Prober Interface)

5.5.3.1、接口定义
1
2
3
4
5
6
7
8
9
10
11
type Prober interface {
    Healthy()
    NotReady(error)
    // ...
}
 
// 具体实现
type HTTPProbe struct {
    healthy atomic.Uint32
    ready   atomic.Uint32
}
5.5.3.2、模块封装
1
2
3
4
5
6
7
8
9
10
// 初始化独立探针组件
grpcProbe := prober.NewGRPC()
httpProbe := prober.NewHTTP()
 
// 组合式探针
statusProber := prober.Combine(
    httpProbe,
    grpcProbe,
    prober.NewInstrumentation(...),
)
5.5.3.3、接口交互
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// HTTP Server 依赖 Probe 接口
srv := httpserver.New(
    logger,
    reg,
    comp,
    httpProbe, // 注入具体实现
    httpserver.WithListen(...),
)
 
// 状态更新(其他模块触发)
func validatePrometheus(...) {
    if err := check(); err != nil {
        statusProber.NotReady(err) // 调用接口方法
    }
}
5.5.3.4、设计亮点
        • 各探针实现独立状态机

        • 组合模式实现探针聚合

5.5.4、跨模块交互(数据上传流程)

5.5.4.1、模块边界
        • Shipper 不直接访问 Prometheus 接口

        • promMetadata 不感知上传逻辑

5.5.4.2、接口交互
1
2
3
4
5
6
7
8
9
10
11
// Shipper 调用元数据接口
meta := metadata.Meta{
    Thanos: metadata.Thanos{
        Labels: m.Labels(), // 方法调用
    },
}
 
// 调用存储接口
if err := bkt.Upload(ctx, path, reader); err != nil {
    level.Error(logger).Log("msg", "upload failed", "err", err)
}

5.5.5、关键设计模式

5.5.5.1、依赖注入
1
2
3
4
5
6
7
8
9
10
// 在初始化时注入依赖
func NewPrometheusStore(
    logger log.Logger,
    reg prometheus.Registerer,
    client promclient.Client, // 抽象接口
    url *url.URL,
    component component.Component,
    labelsFunc func() labels.Labels, // 函数式依赖
    // ...
) (*PrometheusStore, error)
5.5.5.2、接口隔离
1
2
3
4
5
6
7
8
9
// StoreAPI 服务定义
type StoreServer interface {
    Series(*SeriesRequest, Store_SeriesServer) error
    LabelNames(context.Context, *LabelNamesRequest) (*LabelNamesResponse, error)
    LabelValues(context.Context, *LabelValuesRequest) (*LabelValuesResponse, error)
}
 
// 实现类无需显式声明实现接口
storepb.RegisterStoreServer(gRPCServer, store)
5.5.5.3、观察者模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Reloader 监控配置变化
rl := reloader.New(
    log.With(logger, "component", "reloader"),
    extprom.WrapRegistererWithPrefix("thanos_sidecar_", reg),
    &reloader.Options{
        ReloadURL:     reloader.ReloadURLFromBase(conf.prometheus.url),
        CfgFile:       conf.reloader.confFile,
        WatchedDirs:   conf.reloader.ruleDirectories,
    },
)
 
// 注册到全局执行组
g.Add(func() error {
    return rl.Watch(ctx)
}, ...)

5.5.6、面向接口编程思想

5.5.6.1、可测试性
1
2
3
4
5
6
7
// 测试时可用 Mock Bucket
type mockBucket struct {
    objstore.Bucket
}
func (m *mockBucket) Upload(ctx context.Context, name string, r io.Reader) error {
    return nil // 模拟成功
}  
5.5.6.2、可维护性
        • 修改存储实现只需改动 client.NewBucket 部分

        • 添加新协议不影响现有模块

5.5.6.3、可扩展性
1
2
3
4
5
6
// 轻松添加新组件
g.Add(func() error {
    return newFeature.Run(ctx)
}, func(err error) {
    newFeature.Shutdown()
})

 

5.6、最终一致性

块上传采用异步批处理,容忍临时故障

5.7、资源效率

流式传输降低内存消耗

对象存储客户端复用TCP连接

5.8、可观测性

1
promauto.With(reg).NewGaugeVec(...)

深度集成Prometheus指标系统

5.9、Kubernetes原生

健康检查端点符合K8s探针规范

配置热加载支持ConfigMap更新

posted @   左扬  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
历史上的今天:
2024-01-22 《PMBOK指南第六版》第4章 项目整合管理 -> 监控项目工作
levels of contents
点击右上角即可分享
微信分享提示