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的并发原语和接口特性,是学习分布式系统开发的优秀范本。
一、主函数架构设计
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、生命周期管理
g.Add(func() error { /* 启动逻辑 */ }, func(error) { /* 关闭逻辑 */ })
-
-
使用
run.Group实现优雅关闭 -
每个组件注册独立的启动/清理函数
-
1.3、探针状态机
statusProber := prober.Combine(
httpProbe,
grpcProbe,
instrumentationProbe
)
-
-
组合式探针设计
-
各子探针独立维护状态
-
二、Prometheus 元数据管理
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、时间处理
mint := conf.limitMinTime.PrometheusTimestamp()
-
-
自定义
TimeOrDurationValue类型处理时间输入 -
支持绝对时间(RFC3339)和相对时间(如-1h)
-
2.3、标签系统
labels.Labels{{Name: "cluster", Value: "us-east-1"}}
-
-
使用Prometheus官方labels包
-
标签哈希快速比较
-
三、block 上传机制
// 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、元数据增强
meta := metadata.Meta{
Thanos: metadata.Thanos{
Labels: m.Labels(),
Source: metadata.SidecarSource,
}
}
-
- 注入集群标签实现全局唯一
3.3、错误恢复
runutil.Retry(2*time.Second, ctx.Done(), func() error {
return checkLabelsExist()
})
-
- 指数退避重试机制
-
- 上下文感知的超时控制
四、gRPC StoreAPI 实现
// 注册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、零拷贝转换
labelpb.ZLabelSet{Labels: labelpb.ZLabelsFromPromLabels(series.Labels)}
-
-
避免内存复制
-
复用Prometheus内部数据结构
-
2、流控机制
// 客户端控制的分页请求 req.SkipChunks = true req.MaxResolutionWindow = 5 * time.Minute
3、并发安全
func (s *PrometheusStore) LabelSet() []labelpb.ZLabelSet {
return []labelpb.ZLabelSet{
{Labels: labelpb.ZLabelsFromPromLabels(s.labels())},
}
}
-
-
通过闭包获取最新标签
-
无锁读取当前状态
-
五、Golang 高级特性应用及设计思想
5.1、接口解耦
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、函数式选项模式
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、原子状态管理
type HTTPProbe struct {
ready atomic.Uint32
healthy atomic.Uint32
}
func (p *HTTPProbe) Ready() {
p.ready.Store(1) // 无锁原子操作
}
5.4、Context上下文
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、接口定义
// 所有对象存储实现必须满足的接口
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、模块封装
// 创建具体存储实例(隐藏实现细节)
bkt, err := client.NewBucket(
logger,
confContentYaml, // 配置内容(YAML格式)
reg, // Prometheus 注册表
component.Sidecar.String(),
)
5.5.1.3、接口交互
// Shipper 模块仅依赖接口
s := shipper.New(
logger,
reg,
conf.tsdb.path,
bkt, // 传入 Bucket 接口实现
m.Labels,
metadata.SidecarSource,
conf.shipper.uploadCompacted,
)
5.5.1.4、设计亮点
-
-
-
更换存储后端只需修改配置,无需改动 Shipper 代码
-
接口方法明确,各方法职责单一(SRP原则)
-
-
5.5.2、元数据管理模块(promMetadata)
5.5.2.1、封装结构体
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、接口交互
// StoreAPI 通过闭包动态获取最新状态
store.NewPrometheusStore(
logger,
reg,
c,
conf.prometheus.url,
component.Sidecar,
m.Labels, // 方法引用(非直接访问字段)
m.Timestamps,
m.Version,
)
5.5.2.3、设计亮点
-
-
- 状态访问通过方法代理,实现线程安全
- 对外隐藏同步锁等并发控制细节
-
5.5.3、健康检查模块(Prober Interface)
5.5.3.1、接口定义
type Prober interface {
Healthy()
NotReady(error)
// ...
}
// 具体实现
type HTTPProbe struct {
healthy atomic.Uint32
ready atomic.Uint32
}
5.5.3.2、模块封装
// 初始化独立探针组件
grpcProbe := prober.NewGRPC()
httpProbe := prober.NewHTTP()
// 组合式探针
statusProber := prober.Combine(
httpProbe,
grpcProbe,
prober.NewInstrumentation(...),
)
5.5.3.3、接口交互
// 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、接口交互
// 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、依赖注入
// 在初始化时注入依赖
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、接口隔离
// 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、观察者模式
// 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、可测试性
// 测试时可用 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、可扩展性
// 轻松添加新组件
g.Add(func() error {
return newFeature.Run(ctx)
}, func(err error) {
newFeature.Shutdown()
})
5.6、最终一致性
块上传采用异步批处理,容忍临时故障
5.7、资源效率
流式传输降低内存消耗
对象存储客户端复用TCP连接
5.8、可观测性
promauto.With(reg).NewGaugeVec(...)
深度集成Prometheus指标系统
5.9、Kubernetes原生
健康检查端点符合K8s探针规范
配置热加载支持ConfigMap更新

浙公网安备 33010602011771号