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更新