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、设计亮点
-
-
-
更换存储后端只需修改配置,无需改动 Shipper 代码
-
接口方法明确,各方法职责单一(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、设计亮点
-
-
- 状态访问通过方法代理,实现线程安全
- 对外隐藏同步锁等并发控制细节
-
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更新
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
2024-01-22 《PMBOK指南第六版》第4章 项目整合管理 -> 监控项目工作