Thanos源码专题【左扬精讲】——Thanos Receive 组件(release-0.26)源码阅读和分析(详解 cmd/receive.go)
Thanos Receive 组件(release-0.26)源码阅读和分析(详解 cmd/receive.go)
https://github.com/thanos-io/thanos/blob/v0.26.0/cmd/thanos/receive.go
// Copyright (c) The Thanos Authors.
// Licensed under the Apache License 2.0.
package main
import (
"context" // context上下文包
"io/ioutil" // ioutil包用于读写文件
"os" // os包提供了一些基本的操作系统功能
"path" // path包提供了处理文件路径的实用函数
"time" // time包提供了时间的处理
extflag "github.com/efficientgo/tools/extkingpin" // extkingpin包提供了扩展的命令行参数解析功能
"github.com/go-kit/log" // log包提供了日志记录的功能
"github.com/go-kit/log/level" // level包提供了日志级别的功能
grpc_logging "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" // grpc_logging包提供了gRPC日志记录的功能
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/tags" // tags包提供了gRPC标签记录的功能
"github.com/oklog/run" // run包提供了并发执行多个goroutine的功能
"github.com/opentracing/opentracing-go" // opentracing包提供了分布式追踪的功能
"github.com/pkg/errors" // errors包提供了错误处理的功能
"github.com/prometheus/client_golang/prometheus" // prometheus包提供了Prometheus监控的功能
"github.com/prometheus/client_golang/prometheus/promauto" // promauto包提供了自动注册Prometheus指标的功能
"github.com/prometheus/common/model" // model包提供了Prometheus数据模型的功能
"github.com/prometheus/prometheus/model/labels" // labels包提供了Prometheus标签的功能
"github.com/prometheus/prometheus/tsdb" // tsdb包提供了Prometheus存储库的功能
"github.com/thanos-io/thanos/pkg/block/metadata" // metadata包提供了Prometheus块元数据的功能
"github.com/thanos-io/thanos/pkg/component" // component包提供了组件的功能
"github.com/thanos-io/thanos/pkg/exemplars" // exemplars包提供了Prometheus exemplar的功能
"github.com/thanos-io/thanos/pkg/extgrpc" // extgrpc包提供了gRPC扩展的功能
"github.com/thanos-io/thanos/pkg/extkingpin" // extkingpin包提供了扩展的命令行参数解析功能
"github.com/thanos-io/thanos/pkg/extprom" // extprom包提供了Prometheus扩展的功能
"github.com/thanos-io/thanos/pkg/info" // info包提供了Thanos组件的信息功能
"github.com/thanos-io/thanos/pkg/info/infopb" // infopb包提供了Thanos组件信息的功能
"github.com/thanos-io/thanos/pkg/logging" // logging包提供了日志记录的功能
"github.com/thanos-io/thanos/pkg/objstore" // objstore包提供了对象存储的功能
"github.com/thanos-io/thanos/pkg/objstore/client" // client包提供了对象存储客户端的功能
"github.com/thanos-io/thanos/pkg/prober" // prober包提供了探针的功能
"github.com/thanos-io/thanos/pkg/receive" // receive包提供了Thanos接收器功能
"github.com/thanos-io/thanos/pkg/runutil" // runutil包提供了运行时工具的功能
grpcserver "github.com/thanos-io/thanos/pkg/server/grpc" // grpcserver包提供了gRPC服务器功能
httpserver "github.com/thanos-io/thanos/pkg/server/http" // httpserver包提供了HTTP服务器功能
"github.com/thanos-io/thanos/pkg/store" // store包提供了Thanos存储功能
"github.com/thanos-io/thanos/pkg/store/labelpb" // labelpb包提供了标签协议的功能
"github.com/thanos-io/thanos/pkg/tls" // tls包提供了TLS功能的封装
)
// registerReceive 函数用于注册接收命令,以便通过Prometheus的远程写入API接收数据并将其写入本地tsdb。
func registerReceive(app *extkingpin.App) {
// 创建一个接收命令,接收Prometheus的远程写入API请求并将其写入本地tsdb。
cmd := app.Command(component.Receive.String(), "Accept Prometheus remote write API requests and write to local tsdb.")
// 初始化接收配置
conf := &receiveConfig{}
conf.registerFlag(cmd)
// 设置接收命令的初始化逻辑
cmd.Setup(func(g *run.Group, logger log.Logger, reg *prometheus.Registry, tracer opentracing.Tracer, _ <-chan struct{}, _ bool) error {
// 解析标签字符串
lset, err := parseFlagLabels(conf.labelStrs)
if err != nil {
return errors.Wrap(err, "parse labels")
}
// 检查租户标签名称格式是否有效
if !model.LabelName.IsValid(model.LabelName(conf.tenantLabelName)) {
return errors.Errorf("unsupported format for tenant label name, got %s", conf.tenantLabelName)
}
// 检查是否配置了外部标签
if len(lset) == 0 {
// 返回错误,提示没有配置外部标签,这对于接收组件来说是必须的。如果需要,请使用`--receive.external-labels`标志配置它们。
return errors.New("no external labels configured for receive, uniquely identifying external labels must be configured (ideally with `receive_` prefix); see https://thanos.io/tip/thanos/storage.md#external-labels for details.")
}
// 解析请求日志配置
tagOpts, grpcLogOpts, err := logging.ParsegRPCOptions("", conf.reqLogConfig)
if err != nil {
return errors.Wrap(err, "error while parsing config for request logging")
}
// 配置TSDB选项
tsdbOpts := &tsdb.Options{
MinBlockDuration: int64(time.Duration(*conf.tsdbMinBlockDuration) / time.Millisecond), // 最小块持续时间
MaxBlockDuration: int64(time.Duration(*conf.tsdbMaxBlockDuration) / time.Millisecond), // 最大块持续时间
RetentionDuration: int64(time.Duration(*conf.retention) / time.Millisecond), // 保留持续时间
NoLockfile: conf.noLockFile, // 是否禁用锁文件
WALCompression: conf.walCompression, // WAL压缩是否启用
AllowOverlappingBlocks: conf.tsdbAllowOverlappingBlocks, // 是否允许重叠块
MaxExemplars: conf.tsdbMaxExemplars, // 最大示例数
EnableExemplarStorage: true, // 是否启用示例存储
}
// 确定运行模式
// Are we running in IngestorOnly, RouterOnly or RouterIngestor mode?
receiveMode := conf.determineMode()
// 运行接收逻辑
return runReceive(
g, // run.Group实例,用于并发执行多个goroutine
logger, // 日志记录器
reg, // Prometheus注册表
tracer, // opentracing跟踪器
grpcLogOpts, tagOpts, // gRPC日志选项和标签选项
tsdbOpts, // TSDB选项
lset, // 标签集
component.Receive, // 组件名称
metadata.HashFunc(conf.hashFunc), // 哈希函数
receiveMode, // 接收模式
conf, // 接收配置
)
})
}
// runReceive 函数用于启动一个接收组件,处理接收到的数据并将其存储到本地TSDB或上传到对象存储中。
//
// 参数:
// - g: run.Group 对象,用于管理生命周期。
// - logger: log.Logger 对象,用于记录日志。
// - reg: prometheus.Registry 对象,用于注册指标。
// - tracer: opentracing.Tracer 对象,用于分布式追踪。
// - grpcLogOpts: grpc_logging.Option 切片,用于配置 gRPC 日志。
// - tagOpts: tags.Option 切片,用于配置标签选项。
// - tsdbOpts: tsdb.Options 对象,用于配置 TSDB 选项。
// - lset: labels.Labels 对象,用于配置标签集。
// - comp: component.SourceStoreAPI 对象,用于访问源存储 API。
// - hashFunc: metadata.HashFunc 对象,用于计算哈希值。
// - receiveMode: receive.ReceiverMode 对象,表示接收模式。
// - conf: *receiveConfig 对象,包含接收组件的配置信息。
//
// 返回值:
// - error: 如果函数执行过程中出现错误,则返回错误信息;否则返回 nil。
func runReceive(
g *run.Group, // run.Group 对象,用于管理生命周期
logger log.Logger, // 日志记录器
reg *prometheus.Registry, // Prometheus 注册表
tracer opentracing.Tracer, // 分布式追踪器
grpcLogOpts []grpc_logging.Option, // gRPC 日志选项
tagOpts []tags.Option, // 标签选项
tsdbOpts *tsdb.Options, // TSDB 选项
lset labels.Labels, // 标签集合
comp component.SourceStoreAPI, // 组件源存储 API
hashFunc metadata.HashFunc, // 哈希函数
receiveMode receive.ReceiverMode, // 接收模式
conf *receiveConfig, // 接收配置对象
) error {
logger = log.With(logger, "component", "receive") // 添加组件标签到日志记录器
level.Info(logger).Log("mode", receiveMode, "msg", "running receive") // 记录接收模式和运行状态
rwTLSConfig, err := tls.NewServerConfig(log.With(logger, "protocol", "HTTP"), conf.rwServerCert, conf.rwServerKey, conf.rwServerClientCA) // 创建接收器TLS配置
if err != nil {
return err
}
// 创建 dialOpts,用于配置 gRPC 客户端, 包括日志记录器、注册表和追踪器等配置信息。
dialOpts, err := extgrpc.StoreClientGRPCOpts(
logger, // 日志记录器
reg, // Prometheus 注册表
tracer, // 分布式追踪器
*conf.grpcCert != "", // 是否使用 gRPC 证书
*conf.grpcClientCA == "", // 是否使用 gRPC 客户端 CA
conf.rwClientCert, // 客户端证书
conf.rwClientKey, // 客户端密钥
conf.rwClientServerCA, // 服务器 CA
conf.rwClientServerName, // 服务器名称
)
if err != nil {
return err
}
// 创建对象存储客户端
var bkt objstore.Bucket
// 创建对象存储客户端配置内容,用于后续创建对象存储客户端实例。
confContentYaml, err := conf.objStoreConfig.Content()
if err != nil {
return err
}
// Has this thanos receive instance been configured to ingest metrics into a local TSDB?
// thanos receive 实例是否配置为将指标摄取到本地 TSDB?
enableIngestion := receiveMode == receive.IngestorOnly || receiveMode == receive.RouterIngestor
// 判断是否启用上传功能,即是否有有效的对象存储配置内容。
upload := len(confContentYaml) > 0
// enableIngestion 判断是否启用摄取功能,即是否配置了本地 TSDB 或对象存储。
if enableIngestion {
// upload 判断是否启用上传功能,即是否有有效的对象存储配置内容。
if upload {
// tsdbOpts.MinBlockDuration 和 tsdbOpts.MaxBlockDuration 必须相等,否则无法启用上传功能。
if tsdbOpts.MinBlockDuration != tsdbOpts.MaxBlockDuration {
// conf.ignoreBlockSize 标志用于忽略 min/max block duration 配置不一致的警告。
if !conf.ignoreBlockSize {
// 报错信息,提示 TSDB 的最大时间和最小时间不一致。需要禁用压缩功能(tsdb.min-block-duration = tsdb.max-block-duration)。
return errors.Errorf("found that TSDB Max time is %d and Min time is %d. "+
"Compaction needs to be disabled (tsdb.min-block-duration = tsdb.max-block-duration)", tsdbOpts.MaxBlockDuration, tsdbOpts.MinBlockDuration)
}
// 告警信息,提示 min/max block duration 配置不一致的警告。如果需要忽略此警告,请使用 --ignore-block-size 标志。
level.Warn(logger).Log("msg", "flag to ignore min/max block duration flags differing is being used. If the upload of a 2h block fails and a tsdb compaction happens that block may be missing from your Thanos bucket storage.")
}
// The background shipper continuously scans the data directory and uploads
// new blocks to object storage service.
// 后台上传器持续扫描数据目录,并将新块上传到对象存储服务。
bkt, err = client.NewBucket(logger, confContentYaml, reg, comp.String())
if err != nil {
return err
}
} else {
// 如果没有配置支持的存储桶,则禁用上传功能。
level.Info(logger).Log("msg", "no supported bucket was configured, uploads will be disabled")
}
}
// TODO(brancz): remove after a couple of versions
// Migrate non-multi-tsdb capable storage to multi-tsdb disk layout.
// migrateLegacyStorage 函数用于迁移旧存储格式到新的多租户磁盘布局。如果迁移失败,则返回错误信息。
if err := migrateLegacyStorage(logger, conf.dataDir, conf.defaultTenantID); err != nil {
// 迁移旧存储格式到新的多租户磁盘布局失败,返回错误信息。
return errors.Wrapf(err, "migrate legacy storage in %v to default tenant %v", conf.dataDir, conf.defaultTenantID)
}
// receive.NewMultiTSDB 函数创建一个新的 MultiTSDB 实例,用于处理多租户的 TSDB 数据。
dbs := receive.NewMultiTSDB(
conf.dataDir, // 数据目录路径
logger, // 日志记录器
reg, // 注册表
tsdbOpts, // TSDB 配置选项
lset, // 标签集
conf.tenantLabelName, // 租户标签名称
bkt, // 存储桶
conf.allowOutOfOrderUpload, // 是否允许乱序上传
hashFunc, // 哈希函数
)
// receive.NewWriter 函数创建一个新的 Writer 实例,用于处理接收到的数据并将其写入到 MultiTSDB 中。
writer := receive.NewWriter(log.With(logger, "component", "receive-writer"), dbs)
// receive.NewHandler 函数创建一个新的 Handler 实例,用于处理接收到的数据并将其转发到其他节点。
webHandler := receive.NewHandler(log.With(logger, "component", "receive-handler"), &receive.Options{
Writer: writer, // Writer 实例,用于处理接收到的数据并将其写入到 MultiTSDB 中。
ListenAddress: conf.rwAddress, // ListenAddress 配置项,指定了接收数据的地址。
Registry: reg, // 注册表
Endpoint: conf.endpoint, // Endpoint 配置项,指定了接收数据的端点。
TenantHeader: conf.tenantHeader, // TenantHeader 配置项,指定了租户的头部信息。
DefaultTenantID: conf.defaultTenantID, // DefaultTenantID 配置项,指定了默认的租户 ID。
ReplicaHeader: conf.replicaHeader, // ReplicaHeader 配置项,指定了副本的头部信息。
ReplicationFactor: conf.replicationFactor, // ReplicationFactor 配置项,指定了副本的数量。
ReceiverMode: receiveMode, // ReceiverMode 配置项,指定了接收数据的模式。
Tracer: tracer, // Tracer 配置项,指定了追踪器。
TLSConfig: rwTLSConfig, // TLSConfig 配置项,指定了 TLS 配置。
DialOpts: dialOpts, // DialOpts 配置项,指定了拨号选项。
ForwardTimeout: time.Duration(*conf.forwardTimeout), // ForwardTimeout 配置项,指定了转发超时时间。
})
grpcProbe := prober.NewGRPC() // 创建一个新的 GRPCProbe 实例,用于监控 gRPC 服务器的健康状态。
httpProbe := prober.NewHTTP() // 创建一个新的 HTTPProbe 实例,用于监控 HTTP 服务器的健康状态。
statusProber := prober.Combine( // 创建一个新的 CombineProbe 实例,用于组合多个 Probe 实例。
httpProbe, // 创建一个新的 HTTPProbe 实例,用于监控 HTTP 服务器的健康状态。
grpcProbe, // 创建一个新的 GRPCProbe 实例,用于监控 gRPC 服务器的健康状态。
prober.NewInstrumentation(comp, logger, extprom.WrapRegistererWithPrefix("thanos_", reg)), // 创建一个新的 InstrumentationProbe 实例,用于监控 Prometheus 的指标。
)
// Start all components while we wait for TSDB to open but only load
// initial config and mark ourselves as ready after it completed.
// reloadGRPCServer signals when - (1)TSDB is ready and the Store gRPC server can start.
// (2) The Hashring files have changed if tsdb ingestion is disabled.
reloadGRPCServer := make(chan struct{}, 1) // reloadGRPCServer 信号用于通知 TSDB 已准备好,并且 Store gRPC 服务器可以启动。
// hashringChangedChan signals when TSDB needs to be flushed and updated due to hashring config change.
hashringChangedChan := make(chan struct{}, 1) // hashringChangedChan 信号用于通知 TSDB 需要刷新和更新,因为哈希环配置发生了变化。
// uploadC signals when new blocks should be uploaded.
uploadC := make(chan struct{}, 1) // uploadC 信号用于通知新的块应该被上传。
// uploadDone signals when uploading has finished.
uploadDone := make(chan struct{}, 1) // uploadDone 信号用于通知上传已完成。
// enableIngestion 当为 true 时,表示启用 TSDB 数据摄取。
if enableIngestion {
level.Debug(logger).Log("msg", "setting up tsdb")
{
// startTSDBAndUpload 函数用于启动 TSDB 并上传数据。
if err := startTSDBAndUpload(g, logger, reg, dbs, reloadGRPCServer, uploadC, hashringChangedChan, upload, uploadDone, statusProber, bkt); err != nil {
return err
}
}
}
// 启动哈希环的设置。hashringChangedChan 信号用于通知 TSDB 需要刷新和更新,因为哈希环配置发生了变化。
level.Debug(logger).Log("msg", "setting up hashring")
{
// setupHashring 函数用于设置哈希环。
if err := setupHashring(g, logger, reg, conf, hashringChangedChan, webHandler, statusProber, reloadGRPCServer, enableIngestion); err != nil {
return err
}
}
// 启动 HTTP 服务器的设置。
level.Debug(logger).Log("msg", "setting up http server")
{
// srv 是一个 HTTPServer 实例,用于监听和服务的启动。
srv := httpserver.New(
logger, // logger 是一个日志记录器,用于记录日志信息。
reg, // reg 是一个 Prometheus 注册表,用于注册和收集指标。
comp, // comp 是一个组件,用于提供存储和检索数据的功能。
httpProbe, // httpProbe 是一个 HTTP 探针,用于检查 HTTP 服务器的健康状态。
httpserver.WithListen(*conf.httpBindAddr), // WithListen 用于设置 HTTP 服务器的监听地址。
httpserver.WithGracePeriod(time.Duration(*conf.httpGracePeriod)), // WithGracePeriod 用于设置优雅关闭的持续时间。
httpserver.WithTLSConfig(*conf.httpTLSConfig), // WithTLSConfig 用于设置 HTTP 服务器的 TLS 配置。
)
g.Add(func() error {
statusProber.Healthy()
return srv.ListenAndServe()
}, func(err error) {
statusProber.NotReady(err)
defer statusProber.NotHealthy(err)
srv.Shutdown(err)
})
}
// 启动 gRPC 服务器的设置。
level.Debug(logger).Log("msg", "setting up grpc server")
{
// setupAndRunGRPCServer 函数用于设置和运行 gRPC 服务器。
if err := setupAndRunGRPCServer(g, logger, reg, tracer, conf, reloadGRPCServer, comp, dbs, webHandler, grpcLogOpts,
tagOpts, grpcProbe, httpProbe.IsReady); err != nil {
return err
}
}
// 启动 receive http handler,用于处理 HTTP 请求。
level.Debug(logger).Log("msg", "setting up receive http handler")
{
g.Add(
func() error {
return errors.Wrap(webHandler.Run(), "error starting web server")
},
func(err error) {
webHandler.Close()
},
)
}
level.Info(logger).Log("msg", "starting receiver")
return nil
}
// setupAndRunGRPCServer sets up the configuration for the gRPC server.
// It also sets up a handler for reloading the server if tsdb reloads.
// setupAndRunGRPCServer 函数用于设置并运行 gRPC 服务器。
//
// 参数:
// - g *run.Group: 运行组,用于管理协程和错误处理。
// - logger log.Logger: 日志记录器,用于记录日志。
// - reg *prometheus.Registry: Prometheus 注册中心,用于注册和导出 Prometheus 指标。
// - tracer opentracing.Tracer: OpenTracing 追踪器,用于追踪请求。
// - conf *receiveConfig: 接收配置,包含 gRPC 相关的配置。
// - reloadGRPCServer chan struct{}: 通道,当需要重新加载 gRPC 服务器时发送信号。
// - comp component.SourceStoreAPI: SourceStoreAPI 组件,用于处理数据源存储。
// - dbs *receive.MultiTSDB: MultiTSDB 实例,包含多个 TSDB 实例。
// - webHandler *receive.Handler: HTTP 处理器,用于处理 HTTP 请求。
// - grpcLogOpts []grpc_logging.Option: gRPC 日志选项。
// - tagOpts []tags.Option: 标签选项。
// - grpcProbe *prober.GRPCProbe: gRPC 探测器,用于探测 gRPC 服务器的状态。
// - isReady func() bool: 判断服务器是否就绪的函数。
//
// 返回值:
// - error: 如果发生错误,则返回错误信息;否则返回 nil。
func setupAndRunGRPCServer(g *run.Group,
logger log.Logger, // 日志记录器
reg *prometheus.Registry, // Prometheus 注册中心
tracer opentracing.Tracer, // OpenTracing 追踪器,用于追踪请求。
conf *receiveConfig, // 接收配置,包含 gRPC 相关的配置。
reloadGRPCServer chan struct{}, // 通道,当需要重新加载 gRPC 服务器时发送信号。
comp component.SourceStoreAPI, // SourceStoreAPI 组件,用于处理数据源存储。
dbs *receive.MultiTSDB, // MultiTSDB 实例,包含多个 TSDB 实例。
webHandler *receive.Handler, // HTTP 处理器,用于处理 HTTP 请求。
grpcLogOpts []grpc_logging.Option, // gRPC 日志选项。
tagOpts []tags.Option, // 标签选项。
grpcProbe *prober.GRPCProbe, // gRPC 探测器,用于探测 gRPC 服务器的状态。
isReady func() bool, // 判断服务器是否就绪的函数。
) error {
var s *grpcserver.Server
// startGRPCListening 是一个通道,用于在接收到信号时重新启动 gRPC 服务器。
// startGRPCListening re-starts the gRPC server once it receives a signal.
startGRPCListening := make(chan struct{})
g.Add(func() error {
// 启动 gRPC 服务器。
defer close(startGRPCListening)
// 设置 gRPC 服务器的 TLS 配置。
tlsCfg, err := tls.NewServerConfig(log.With(logger, "protocol", "gRPC"), *conf.grpcCert, *conf.grpcKey, *conf.grpcClientCA)
if err != nil {
// 如果设置 TLS 配置失败,则返回错误。
return errors.Wrap(err, "setup gRPC server")
}
// 遍历 reloadGRPCServer 通道,直到收到信号。
for range reloadGRPCServer {
// 如果 gRPC 服务器已经启动,则关闭它。
if s != nil {
// 关闭 gRPC 服务器的优雅退出。
s.Shutdown(errors.New("reload hashrings"))
}
// 创建一个新的 gRPC 服务器。
mts := store.NewMultiTSDBStore(
logger, // 日志记录器。
reg, // 注册表。
comp, // 组件。
dbs.TSDBStores, // TSDB 存储器。
)
// store.ReadWriteTSDBStore 实现了 StoreServer 和 WriteableStoreServer 接口。
rw := store.ReadWriteTSDBStore{
StoreServer: mts, // 实现了 StoreServer 和 WriteableStoreServer 接口的实例。
WriteableStoreServer: webHandler, // HTTP 处理器,用于处理写入请求。
}
// info.NewInfoServer 创建一个新的 InfoServer 实例。
infoSrv := info.NewInfoServer(
component.Receive.String(), // 组件名称。
info.WithLabelSetFunc(func() []labelpb.ZLabelSet { return mts.LabelSet() }), // 标签集函数。
info.WithStoreInfoFunc(func() *infopb.StoreInfo { // 存储信息函数。
if isReady() {
minTime, maxTime := mts.TimeRange()
return &infopb.StoreInfo{
MinTime: minTime,
MaxTime: maxTime,
}
}
return nil
}),
// WithExemplarsInfoFunc 创建一个新的 ExemplarsInfoFunc 实例。
info.WithExemplarsInfoFunc(),
)
// grpcserver.New 创建一个新的 gRPC 服务器。
s = grpcserver.New(logger, &receive.UnRegisterer{Registerer: reg}, tracer, grpcLogOpts, tagOpts, comp, grpcProbe,
grpcserver.WithServer(store.RegisterStoreServer(rw)), // 注册 StoreServer。
grpcserver.WithServer(store.RegisterWritableStoreServer(rw)), // 注册 WritableStoreServer。
grpcserver.WithServer(exemplars.RegisterExemplarsServer(exemplars.NewMultiTSDB(dbs.TSDBExemplars))), // 注册 ExemplarsServer。
grpcserver.WithServer(info.RegisterInfoServer(infoSrv)), // 注册 InfoServer。
grpcserver.WithListen(*conf.grpcBindAddr), // 监听地址。
grpcserver.WithGracePeriod(time.Duration(*conf.grpcGracePeriod)), // 优雅关闭时间。
grpcserver.WithTLSConfig(tlsCfg), // TLS 配置。
grpcserver.WithMaxConnAge(*conf.grpcMaxConnAge), // 最大连接时间。
)
// 启动 gRPC 服务器的优雅关闭。
startGRPCListening <- struct{}{}
}
if s != nil {
// 在 gRPC 服务器关闭时,我们需要优雅地关闭所有数据库。
s.Shutdown(err)
}
return nil
}, func(error) {})
// 我们需要在数据库发生变化时能够启动和停止 gRPC 服务器,因此需要为它创建一个独立的运行组。
// We need to be able to start and stop the gRPC server
// whenever the DB changes, thus it needs its own run group.
g.Add(func() error {
for range startGRPCListening {
level.Info(logger).Log("msg", "listening for StoreAPI and WritableStoreAPI gRPC", "address", *conf.grpcBindAddr)
if err := s.ListenAndServe(); err != nil {
return errors.Wrap(err, "serve gRPC")
}
}
return nil
}, func(error) {})
return nil
}
// setupHashring sets up the hashring configuration provided.
// If no hashring is provided, we setup a single node hashring with local endpoint.
// setupHashring 用于设置并初始化 Hashring。
//
// 参数:
// - g: *run.Group,用于管理运行的任务组。
// - logger: log.Logger,用于记录日志。
// - reg: *prometheus.Registry,用于注册 Prometheus 指标。
// - conf: *receiveConfig,接收配置。
// - hashringChangedChan: chan struct{},当 Hashring 发生变化时发送信号的通道。
// - webHandler: *receive.Handler,用于处理 Web 请求的处理器。
// - statusProber: prober.Probe,用于探测服务状态的探针。
// - reloadGRPCServer: chan struct{},当 gRPC 服务器需要重新加载时发送信号的通道。
// - enableIngestion: bool,是否启用数据摄取功能。
//
// 返回值:
// - error,如果设置过程中发生错误则返回相应的错误信息,否则返回 nil。
func setupHashring(g *run.Group,
logger log.Logger,
reg *prometheus.Registry,
conf *receiveConfig,
hashringChangedChan chan struct{},
webHandler *receive.Handler,
statusProber prober.Probe,
reloadGRPCServer chan struct{},
enableIngestion bool,
) error {
// Note: the hashring configuration watcher
// is the sender and thus closes the chan.
// In the single-node case, which has no configuration
// watcher, we close the chan ourselves.
updates := make(chan receive.Hashring, 1)
// 配置文件的路径
// 初始化配置观察者时给定的 Hashrings 配置文件路径
if conf.hashringsFilePath != "" {
cw, err := receive.NewConfigWatcher(log.With(logger, "component", "config-watcher"), reg, conf.hashringsFilePath, *conf.refreshInterval)
if err != nil {
return errors.Wrap(err, "failed to initialize config watcher")
}
// 在运行观察者之前检查 hashring 配置
if err := cw.ValidateConfig(); err != nil {
cw.Stop()
close(updates)
return errors.Wrap(err, "failed to validate hashring configuration file")
}
ctx, cancel := context.WithCancel(context.Background())
g.Add(func() error {
level.Info(logger).Log("msg", "the hashring initialized with config watcher.")
// 从配置观察者获取 Hashring
return receive.HashringFromConfigWatcher(ctx, updates, cw)
}, func(error) {
cancel()
})
} else {
var (
ring receive.Hashring
err error
)
// 通过内容初始化配置
// The Hashrings config file content given initialize configuration from content.
if len(conf.hashringsFileContent) > 0 {
ring, err = receive.HashringFromConfig(conf.hashringsFileContent)
if err != nil {
close(updates)
return errors.Wrap(err, "failed to validate hashring configuration file")
}
level.Info(logger).Log("msg", "the hashring initialized directly with the given content through the flag.")
} else {
level.Info(logger).Log("msg", "the hashring file is not specified use single node hashring.")
// 使用单个节点 Hashring
ring = receive.SingleNodeHashring(conf.endpoint)
}
cancel := make(chan struct{})
g.Add(func() error {
defer close(updates)
// 将 Hashring 发送到更新通道
updates <- ring
<-cancel
return nil
}, func(error) {
close(cancel)
})
}
cancel := make(chan struct{})
g.Add(func() error {
if enableIngestion {
defer close(hashringChangedChan)
}
for {
select {
case h, ok := <-updates:
if !ok {
return nil
}
// 更新 web 处理器的 Hashring
webHandler.Hashring(h)
msg := "hashring has changed; server is not ready to receive web requests"
// 设置状态为未就绪
statusProber.NotReady(errors.New(msg))
level.Info(logger).Log("msg", msg)
if enableIngestion {
// 发送信号给 tsdb 重新加载,然后重启 gRPC 服务器
// send a signal to tsdb to reload, and then restart the gRPC server.
hashringChangedChan <- struct{}{}
} else {
// 不需要 tsdb 重新加载,直接重启 gRPC 服务器
level.Info(logger).Log("msg", "server has reloaded, ready to start accepting requests")
statusProber.Ready()
reloadGRPCServer <- struct{}{}
}
case <-cancel:
return nil
}
}
}, func(err error) {
close(cancel)
},
)
return nil
}
// startTSDBAndUpload starts up the multi-tsdb and sets up the rungroup to flush the tsdb and reload on hashring change.
// It also uploads the tsdb to object store if upload is enabled.
// startTSDBAndUpload 函数启动TSDB并上传数据。
//
// 参数:
// - g: 运行组,用于管理并行执行的函数。
// - logger: 日志记录器,用于记录日志信息。
// - reg: Prometheus注册表,用于注册Prometheus指标。
// - dbs: MultiTSDB对象,表示多个TSDB的集合。
// - reloadGRPCServer: 通道,当TSDB重新加载完成时发送信号。
// - uploadC: 通道,当需要上传数据时发送信号。
// - hashringChangedChan: 通道,当hashring变化时发送信号。
// - upload: 布尔值,指示是否需要上传数据。
// - uploadDone: 通道,当上传完成时发送信号。
// - statusProber: 探测器,用于探测服务状态。
// - bkt: 对象存储桶,用于存储上传的数据。
//
// 返回值:
// - error: 如果发生错误,则返回错误信息;否则返回nil。
func startTSDBAndUpload(g *run.Group,
logger log.Logger,
reg *prometheus.Registry,
dbs *receive.MultiTSDB,
reloadGRPCServer chan struct{},
uploadC chan struct{},
hashringChangedChan chan struct{},
upload bool,
uploadDone chan struct{},
statusProber prober.Probe,
bkt objstore.Bucket,
) error {
// 设置日志组件
log.With(logger, "component", "storage")
// 创建Prometheus计数器
dbUpdatesStarted := promauto.With(reg).NewCounter(prometheus.CounterOpts{
Name: "thanos_receive_multi_db_updates_attempted_total",
Help: "Number of Multi DB attempted reloads with flush and potential upload due to hashring changes",
})
dbUpdatesCompleted := promauto.With(reg).NewCounter(prometheus.CounterOpts{
Name: "thanos_receive_multi_db_updates_completed_total",
Help: "Number of Multi DB completed reloads with flush and potential upload due to hashring changes",
})
// 删除存储锁文件
level.Debug(logger).Log("msg", "removing storage lock files if any")
// 删除存储锁文件,如果删除失败则返回错误
if err := dbs.RemoveLockFilesIfAny(); err != nil {
return errors.Wrap(err, "remove storage lock files")
}
// TSDBs 重新加载逻辑,监听hashring变化
cancel := make(chan struct{})
g.Add(func() error {
defer close(reloadGRPCServer)
defer close(uploadC)
// 在退出前确保WAL被刷新且DBs被关闭
defer func() {
level.Info(logger).Log("msg", "shutting down storage")
if err := dbs.Flush(); err != nil {
level.Error(logger).Log("err", err, "msg", "failed to flush storage")
} else {
level.Info(logger).Log("msg", "storage is flushed successfully")
}
if err := dbs.Close(); err != nil {
level.Error(logger).Log("err", err, "msg", "failed to close storage")
return
}
level.Info(logger).Log("msg", "storage is closed")
}()
for {
select {
case <-cancel:
return nil
case _, ok := <-hashringChangedChan:
if !ok {
return nil
}
dbUpdatesStarted.Inc()
level.Info(logger).Log("msg", "updating storage")
if err := dbs.Flush(); err != nil {
return errors.Wrap(err, "flushing storage")
}
if err := dbs.Open(); err != nil {
return errors.Wrap(err, "opening storage")
}
if upload {
uploadC <- struct{}{}
<-uploadDone
}
statusProber.Ready()
level.Info(logger).Log("msg", "storage started, and server is ready to receive web requests")
dbUpdatesCompleted.Inc()
reloadGRPCServer <- struct{}{}
}
}
}, func(err error) {
close(cancel)
})
// 如果需要上传,则执行上传逻辑
if upload {
logger := log.With(logger, "component", "uploader")
upload := func(ctx context.Context) error {
level.Debug(logger).Log("msg", "upload phase starting")
start := time.Now()
uploaded, err := dbs.Sync(ctx)
if err != nil {
level.Warn(logger).Log("msg", "upload failed", "elapsed", time.Since(start), "err", err)
return err
}
level.Debug(logger).Log("msg", "upload phase done", "uploaded", uploaded, "elapsed", time.Since(start))
return nil
}
{
level.Info(logger).Log("msg", "upload enabled, starting initial sync")
if err := upload(context.Background()); err != nil {
return errors.Wrap(err, "initial upload failed")
}
level.Info(logger).Log("msg", "initial sync done")
}
{
ctx, cancel := context.WithCancel(context.Background())
g.Add(func() error {
// 确保我们正确地清理一切
defer func() {
runutil.CloseWithLogOnErr(logger, bkt, "bucket client")
}()
// 在退出前确保所有块都被上传
defer func() {
<-uploadC // 由存储例程在完成时关闭
level.Info(logger).Log("msg", "uploading the final cut block before exiting")
ctx, cancel := context.WithCancel(context.Background())
uploaded, err := dbs.Sync(ctx)
if err != nil {
cancel()
level.Error(logger).Log("msg", "the final upload failed", "err", err)
return
}
cancel()
level.Info(logger).Log("msg", "the final cut block was uploaded", "uploaded", uploaded)
}()
defer close(uploadDone)
// 在循环中运行上传器
tick := time.NewTicker(30 * time.Second)
defer tick.Stop()
for {
select {
case <-ctx.Done():
return nil
case <-uploadC:
// 按需上传
if err := upload(ctx); err != nil {
level.Warn(logger).Log("msg", "on demand upload failed", "err", err)
}
uploadDone <- struct{}{}
case <-tick.C:
if err := upload(ctx); err != nil {
level.Warn(logger).Log("msg", "recurring upload failed", "err", err)
}
}
}
}, func(error) {
cancel()
})
}
}
return nil
}
// migrateLegacyStorage 函数用于将遗留的存储迁移到新的多租户存储布局。
//
// 参数:
//
// logger log.Logger: 日志记录器,用于记录迁移过程中的信息。
// dataDir string: 数据目录的路径。
// defaultTenantID string: 默认租户ID。
//
// 返回值:
//
// error: 如果迁移过程中发生错误,则返回错误信息;否则返回nil。
func migrateLegacyStorage(logger log.Logger, dataDir, defaultTenantID string) error {
// 将默认租户的数据目录设置为dataDir和defaultTenantID的拼接
defaultTenantDataDir := path.Join(dataDir, defaultTenantID)
// 检查默认租户的数据目录是否存在
if _, err := os.Stat(defaultTenantDataDir); !os.IsNotExist(err) {
// 如果默认租户的数据目录已经存在,则不进行存储迁移
level.Info(logger).Log("msg", "default tenant data dir already present, not attempting to migrate storage")
return nil
}
// 检查dataDir是否存在
if _, err := os.Stat(dataDir); os.IsNotExist(err) {
// 如果dataDir不存在,则不进行数据迁移
level.Info(logger).Log("msg", "no existing storage found, no data migration attempted")
return nil
}
// 输出日志,表示发现了遗留存储,并计划进行迁移
level.Info(logger).Log("msg", "found legacy storage, migrating to multi-tsdb layout with default tenant", "defaultTenantID", defaultTenantID)
// 读取dataDir目录下的所有文件和目录
files, err := ioutil.ReadDir(dataDir)
if err != nil {
// 如果读取dataDir失败,则包装错误并返回
return errors.Wrapf(err, "read legacy data dir: %v", dataDir)
}
// 创建默认租户的数据目录
if err := os.MkdirAll(defaultTenantDataDir, 0750); err != nil {
// 如果创建默认租户的数据目录失败,则包装错误并返回
return errors.Wrapf(err, "create default tenant data dir: %v", defaultTenantDataDir)
}
// 遍历dataDir目录下的所有文件和目录
for _, f := range files {
// 从dataDir移动文件到默认租户的数据目录
from := path.Join(dataDir, f.Name())
to := path.Join(defaultTenantDataDir, f.Name())
if err := os.Rename(from, to); err != nil {
// 如果移动文件失败,则包装错误并返回
return errors.Wrapf(err, "migrate file from %v to %v", from, to)
}
}
return nil
}
type receiveConfig struct {
httpBindAddr *string // 接收请求的HTTP绑定地址
httpGracePeriod *model.Duration // HTTP优雅关闭的持续时间
httpTLSConfig *string // HTTP TLS配置
grpcBindAddr *string // 接收请求的gRPC绑定地址
grpcGracePeriod *model.Duration // gRPC优雅关闭的持续时间
grpcCert *string // gRPC证书
grpcKey *string // gRPC私钥
grpcClientCA *string // gRPC客户端CA证书
grpcMaxConnAge *time.Duration // gRPC最大连接年龄
rwAddress string // 接收请求的地址
rwServerCert string // 读写服务器证书
rwServerKey string // 读写服务器私钥
rwServerClientCA string // 读写服务器客户端CA证书
rwClientCert string // 读写客户端证书
rwClientKey string // 读写客户端私钥
rwClientServerCA string // 读写客户端服务器CA证书
rwClientServerName string // 读写客户端服务器名称
dataDir string // 数据目录
labelStrs []string // 标签字符串
objStoreConfig *extflag.PathOrContent // 对象存储配置
retention *model.Duration // 数据保留时间
hashringsFilePath string // 哈希环文件路径
hashringsFileContent string // 哈希环文件内容
refreshInterval *model.Duration // 刷新间隔
endpoint string // 端点
tenantHeader string // 租户头部信息
tenantLabelName string // 租户标签名称
defaultTenantID string // 默认租户ID
replicaHeader string // 副本头部信息
replicationFactor uint64 // 复制因子
forwardTimeout *model.Duration // 转发超时时间
tsdbMinBlockDuration *model.Duration // 最小块持续时间
tsdbMaxBlockDuration *model.Duration // 最大块持续时间
tsdbAllowOverlappingBlocks bool // 允许重叠块
tsdbMaxExemplars int64 // 最大示例数
walCompression bool // WAL压缩
noLockFile bool // 无锁文件
hashFunc string // 哈希函数
ignoreBlockSize bool // 忽略块大小
allowOutOfOrderUpload bool // 允许无序上传
reqLogConfig *extflag.PathOrContent // 请求日志配置
}
// registerFlag 方法用于在指定的命令(cmd)上注册多个配置标志位,这些标志位用于配置接收组件的行为。
func (rc *receiveConfig) registerFlag(cmd extkingpin.FlagClause) {
// 注册HTTP相关标志位
rc.httpBindAddr, rc.httpGracePeriod, rc.httpTLSConfig = extkingpin.RegisterHTTPFlags(cmd)
// 注册gRPC相关标志位
rc.grpcBindAddr, rc.grpcGracePeriod, rc.grpcCert, rc.grpcKey, rc.grpcClientCA, rc.grpcMaxConnAge = extkingpin.RegisterGRPCFlags(cmd)
// 注册远程写入地址标志位
cmd.Flag("remote-write.address", "远程写入地址").
Default("0.0.0.0:19291").StringVar(&rc.rwAddress)
// 注册远程写入服务器TLS证书标志位
cmd.Flag("remote-write.server-tls-cert", "HTTP服务器的TLS证书,留空则禁用TLS").Default("").StringVar(&rc.rwServerCert)
// 注册远程写入服务器TLS密钥标志位
cmd.Flag("remote-write.server-tls-key", "HTTP服务器的TLS密钥,留空则禁用TLS").Default("").StringVar(&rc.rwServerKey)
// 注册远程写入服务器TLS客户端CA标志位
cmd.Flag("remote-write.server-tls-client-ca", "TLS CA以验证客户端。如果没有指定客户端CA,则服务器端不进行客户端验证。(tls.NoClientCert)").Default("").StringVar(&rc.rwServerClientCA)
// 注册远程写入客户端TLS证书标志位
cmd.Flag("remote-write.client-tls-cert", "用于标识此客户端到服务器的TLS证书").Default("").StringVar(&rc.rwClientCert)
// 注册远程写入客户端TLS密钥标志位
cmd.Flag("remote-write.client-tls-key", "客户端证书的TLS密钥").Default("").StringVar(&rc.rwClientKey)
// 注册远程写入客户端TLS CA证书标志位
cmd.Flag("remote-write.client-tls-ca", "用于验证服务器的TLS CA证书").Default("").StringVar(&rc.rwClientServerCA)
// 注册远程写入客户端服务器名称标志位
cmd.Flag("remote-write.client-server-name", "要验证返回的TLS证书上的主机名的服务器名称。请参阅https://tools.ietf.org/html/rfc4366#section-3.1").Default("").StringVar(&rc.rwClientServerName)
// 注册TSDB数据目录标志位
cmd.Flag("tsdb.path", "TSDB的数据目录").
Default("./data").StringVar(&rc.dataDir)
// 注册外部标签标志位
cmd.Flag("label", "要宣布的外部标签。当处理多个tsdb实例时,此标志将在未来被移除").PlaceHolder("key=\"value\"").StringsVar(&rc.labelStrs)
// 注册通用对象存储标志位
rc.objStoreConfig = extkingpin.RegisterCommonObjStoreFlags(cmd, "", false)
// 注册TSDB保留时间标志位
rc.retention = extkingpin.ModelDuration(cmd.Flag("tsdb.retention", "在本地存储上保留原始样本的时间。0d - 禁用此保留").Default("15d"))
// 注册哈希环文件路径标志位
cmd.Flag("receive.hashrings-file", "包含哈希环配置的文件的路径。初始化一个监视器以监视更改并动态更新哈希环").PlaceHolder("<path>").StringVar(&rc.hashringsFilePath)
// 注册哈希环内容标志位
cmd.Flag("receive.hashrings", "哈希环配置内容的替代文件标志位(优先级较低)").PlaceHolder("<content>").StringVar(&rc.hashringsFileContent)
// 注册哈希环文件刷新间隔标志位
rc.refreshInterval = extkingpin.ModelDuration(cmd.Flag("receive.hashrings-file-refresh-interval", "重新读取哈希环配置文件的刷新间隔(用作回退)").
Default("5m"))
// 注册本地接收节点端点标志位
cmd.Flag("receive.local-endpoint", "本地接收节点的端点。用于在哈希环配置中标识本地节点").StringVar(&rc.endpoint)
// 注册租户HTTP头标志位
cmd.Flag("receive.tenant-header", "确定写入请求的租户的HTTP头").Default(receive.DefaultTenantHeader).StringVar(&rc.tenantHeader)
// 注册默认租户ID标志位
cmd.Flag("receive.default-tenant-id", "当没有通过头提供租户时使用的默认租户ID").Default(receive.DefaultTenant).StringVar(&rc.defaultTenantID)
// 注册租户标签名称标志位
cmd.Flag("receive.tenant-label-name", "租户将通过其声明的标签名称").Default(receive.DefaultTenantLabel).StringVar(&rc.tenantLabelName)
// 注册副本HTTP头标志位
cmd.Flag("receive.replica-header", "指定写入请求的副本号的HTTP头").Default(receive.DefaultReplicaHeader).StringVar(&rc.replicaHeader)
// 注册复制因子标志位
cmd.Flag("receive.replication-factor", "要复制多少次传入的写入请求").Default("1").Uint64Var(&rc.replicationFactor)
// 注册转发请求超时标志位
rc.forwardTimeout = extkingpin.ModelDuration(cmd.Flag("receive-forward-timeout", "每个转发请求的超时时间").Default("5s").Hidden())
// 注册TSDB最小块持续时间标志位
rc.tsdbMinBlockDuration = extkingpin.ModelDuration(cmd.Flag("tsdb.min-block-duration", "本地TSDB块的最小持续时间").Default("2h").Hidden())
// 注册TSDB最大块持续时间标志位
rc.tsdbMaxBlockDuration = extkingpin.ModelDuration(cmd.Flag("tsdb.max-block-duration", "本地TSDB块的最大持续时间").Default("2h").Hidden())
// 注册TSDB允许重叠块标志位
cmd.Flag("tsdb.allow-overlapping-blocks", "允许重叠块,从而启用垂直压缩和垂直查询合并").Default("false").BoolVar(&rc.tsdbAllowOverlappingBlocks)
// 注册TSDB WAL压缩标志位
cmd.Flag("tsdb.wal-compression", "压缩TSDB WAL").Default("true").BoolVar(&rc.walCompression)
// 注册TSDB无锁文件标志位
cmd.Flag("tsdb.no-lockfile", "不在TSDB数据目录中创建锁文件。在任何情况下,锁文件都将在下次启动时删除").Default("false").BoolVar(&rc.noLockFile)
// 注册TSDB最大示例标志位
cmd.Flag("tsdb.max-exemplars",
"启用对示例的支持并设置每个租户将存储的最大示例数。如果示例存储已满(存储的示例数等于max-exemplars),则摄入新示例将从存储中逐出最旧的示例。此标志的0(或更小)值禁用示例存储").
Default("0").Int64Var(&rc.tsdbMaxExemplars)
// 注册哈希函数标志位
cmd.Flag("hash-func", "指定在计算生成文件的哈希值时使用的哈希函数。如果没有指定函数,则不会发生。这允许避免两次下载某些文件,尽管会有一些性能开销。可能的值为: \"\", \"SHA256\"。").
Default("").EnumVar(&rc.hashFunc, "SHA256", "")
// 注册忽略不等块大小标志位
cmd.Flag("shipper.ignore-unequal-block-size", "如果为true,则接收不需要将最小和最大块大小标志位设置为相同的值。仅当您希望保持较长的保留时间和压缩启用时,才使用此选项,因为在最坏的情况下,它可能导致Thanos存储桶存储中约2小时的数据丢失。").Default("false").Hidden().BoolVar(&rc.ignoreBlockSize)
// 注册允许无序上传标志位
cmd.Flag("shipper.allow-out-of-order-uploads",
"如果为true,则发货方将跳过给定迭代中失败的块上传并在以后重试。这意味着一些较新的块可能比旧块更早地上传。这可能会触发没有这些块的压缩,并因此导致重叠情况。如果启用了垂直压缩并且希望尽快上传块而不关心顺序,请将其设置为true。").
Default("false").Hidden().BoolVar(&rc.allowOutOfOrderUpload)
// 注册请求日志配置标志位
rc.reqLogConfig = extkingpin.RegisterRequestLoggingFlags(cmd)
}
// determineMode returns the ReceiverMode that this receiver is configured to run in.
// This is used to configure this Receiver's forwarding and ingesting behavior at runtime.
// determineMode 函数用于确定接收器的模式
//
// 参数:
//
// rc *receiveConfig: 指向receiveConfig结构体的指针,包含接收配置信息
//
// 返回值:
//
// receive.ReceiverMode: 返回接收器的模式,可以是RouterIngestor、RouterOnly或IngestorOnly
func (rc *receiveConfig) determineMode() receive.ReceiverMode {
// 用户是否提供了某种哈希环配置?
// Has the user provided some kind of hashring configuration?
hashringSpecified := rc.hashringsFileContent != "" || rc.hashringsFilePath != ""
// 用户是否指定了 --receive.local-endpoint 标志?
// Has the user specified the --receive.local-endpoint flag?
localEndpointSpecified := rc.endpoint != ""
switch {
case hashringSpecified && localEndpointSpecified:
// 如果用户提供了哈希环配置并指定了本地端点,则返回 RouterIngestor
// If the user has provided a hashring configuration and specified a local endpoint, return RouterIngestor
return receive.RouterIngestor
case hashringSpecified && !localEndpointSpecified:
// 注意:如果哈希环包含指向自身的地址且未指定本地端点,则会创建无限循环/分叉炸弹
// Be careful - if the hashring contains an address that routes to itself and does not specify a local
// endpoint - you've just created an infinite loop / fork bomb :)
// 如果用户提供了哈希环配置但未指定本地端点,则返回 RouterOnly
// If the user has provided a hashring configuration but not a local endpoint, return RouterOnly
return receive.RouterOnly
default:
// 未提供哈希环配置,因此我们会在本地接收所有指标
// hashring configuration has not been provided so we ingest all metrics locally.
// 如果用户未提供哈希环配置,则返回 IngestorOnly
// If the user has not provided a hashring configuration, return IngestorOnly
return receive.IngestorOnly
}
}

浙公网安备 33010602011771号