Thanos源码专题【左扬精讲】——Thanos Query_frontend 组件(release-0.26)源码阅读和分析(详解 cmd/query_frontend.go )
Thanos Query_frontend 组件(release-0.26)源码阅读和分析(详解 cmd/query_frontend.go )
https://github.com/thanos-io/thanos/blob/v0.26.0/cmd/thanos/query_frontend.go
// Copyright (c) The Thanos Authors.
// Licensed under the Apache License 2.0.
package main
import (
"net/http"
"time"
"github.com/NYTimes/gziphandler"
cortexfrontend "github.com/cortexproject/cortex/pkg/frontend"
"github.com/cortexproject/cortex/pkg/frontend/transport"
"github.com/cortexproject/cortex/pkg/querier/queryrange"
cortexvalidation "github.com/cortexproject/cortex/pkg/util/validation"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/oklog/run"
"github.com/opentracing/opentracing-go"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/weaveworks/common/user"
"gopkg.in/yaml.v2"
extflag "github.com/efficientgo/tools/extkingpin"
"github.com/thanos-io/thanos/pkg/api"
"github.com/thanos-io/thanos/pkg/component"
"github.com/thanos-io/thanos/pkg/exthttp"
"github.com/thanos-io/thanos/pkg/extkingpin"
"github.com/thanos-io/thanos/pkg/extprom"
extpromhttp "github.com/thanos-io/thanos/pkg/extprom/http"
"github.com/thanos-io/thanos/pkg/logging"
"github.com/thanos-io/thanos/pkg/prober"
"github.com/thanos-io/thanos/pkg/queryfrontend"
httpserver "github.com/thanos-io/thanos/pkg/server/http"
"github.com/thanos-io/thanos/pkg/server/http/middleware"
"github.com/thanos-io/thanos/pkg/tracing"
)
type queryFrontendConfig struct {
http httpConfig // HTTP 服务配置
webDisableCORS bool // 是否禁用CORS
queryfrontend.Config // 查询前端配置
orgIdHeaders []string // 组织ID头部
}
// registerQueryFrontend 函数用于在 extkingpin 应用中注册查询前端组件的相关配置和命令。
//
// 参数:
// app: extkingpin.App 类型,表示要注册命令的应用实例。
func registerQueryFrontend(app *extkingpin.App) {
// 定义组件名称
comp := component.QueryFrontend
// 添加命令,用于启动查询前端服务。
cmd := app.Command(comp.String(), "Query frontend command implements a service deployed in front of queriers to improve query parallelization and caching.")
// 定义配置结构体
cfg := &queryFrontendConfig{
Config: queryfrontend.Config{
// 最大请求体大小为10 MiB
// Max body size is 10 MiB.
CortexHandlerConfig: &transport.HandlerConfig{
MaxBodySize: 10 * 1024 * 1024,
},
// 查询范围配置
QueryRangeConfig: queryfrontend.QueryRangeConfig{
Limits: &cortexvalidation.Limits{},
},
// 查询配置
LabelsConfig: queryfrontend.LabelsConfig{
Limits: &cortexvalidation.Limits{},
},
},
}
// 注册HTTP相关标志
cfg.http.registerFlag(cmd)
// --web.disable-cors,无论是否禁用CORS,默认都是允许的。
cmd.Flag("web.disable-cors", "Whether to disable CORS headers to be set by Thanos. By default Thanos sets CORS headers to be allowed by all.").
Default("false").BoolVar(&cfg.webDisableCORS)
// Query range tripperware flags.
// --query-range.align-range-with-step, 默认情况下,会将传入的查询范围与其步长对齐以更好地缓存。注意:Grafana仪表板默认会这样做。
cmd.Flag("query-range.align-range-with-step", "Mutate incoming queries to align their start and end with their step for better cache-ability. Note: Grafana dashboards do that by default.").
Default("true").BoolVar(&cfg.QueryRangeConfig.AlignRangeWithStep)
// --query-range.request-downsampled,默认情况下,查询范围请求将请求原始数据,而不是从缓存中获取。如果请求为空或未完成,则将请求下采样数据。
cmd.Flag("query-range.request-downsampled", "Make additional query for downsampled data in case of empty or incomplete response to range request.").
Default("true").BoolVar(&cfg.QueryRangeConfig.RequestDownsampled)
// --query-range.split-interval,默认情况下,会将查询范围请求拆分为24小时的间隔并在并行中执行。注意:Grafana仪表板默认会这样做。
cmd.Flag("query-range.split-interval", "Split query range requests by an interval and execute in parallel, it should be greater than 0 when query-range.response-cache-config is configured.").
Default("24h").DurationVar(&cfg.QueryRangeConfig.SplitQueriesByInterval)
// --query-range.max-retries,默认情况下,查询范围请求将重试3次。注意:Grafana仪表板默认会这样做。
cmd.Flag("query-range.max-retries-per-request", "Maximum number of retries for a single query range request; beyond this, the downstream error is returned.").
Default("5").IntVar(&cfg.QueryRangeConfig.MaxRetries)
// --query-range.max-query-length,默认情况下,查询范围请求的最大时间范围是1天。注册查询范围请求最大时间范围标志
cmd.Flag("query-range.max-query-length", "Limit the query time range (end - start time) in the query-frontend, 0 disables it.").
Default("0").DurationVar((*time.Duration)(&cfg.QueryRangeConfig.Limits.MaxQueryLength))
// --query-range.max-query-parallelism, 默认情况下,查询范围请求将并行执行14个。注册查询范围请求并行执行标志
cmd.Flag("query-range.max-query-parallelism", "Maximum number of query range requests will be scheduled in parallel by the Frontend.").
Default("14").IntVar(&cfg.QueryRangeConfig.Limits.MaxQueryParallelism)
// --query-range.response-cache-max-freshness, 默认情况下,查询范围请求将重试3次。注意:Grafana仪表板默认会这样做。
cmd.Flag("query-range.response-cache-max-freshness", "Most recent allowed cacheable result for query range requests, to prevent caching very recent results that might still be in flux.").
Default("1m").DurationVar((*time.Duration)(&cfg.QueryRangeConfig.Limits.MaxCacheFreshness))
// --query-range.partial-response: 设置查询范围请求的默认部分响应策略。
// 如果未在请求中指定 partial_response 参数,则启用此策略。
// 使用 --no-query-range.partial-response 可禁用该策略。
cmd.Flag("query-range.partial-response", "Enable partial response for query range requests if no partial_response param is specified. Use --no-query-range.partial-response to disable.").
Default("true").BoolVar(&cfg.QueryRangeConfig.PartialResponseStrategy)
// --query-range.response-cache-config, 注册查询范围请求响应缓存配置标志
cfg.QueryRangeConfig.CachePathOrContent = *extflag.RegisterPathOrContent(cmd, "query-range.response-cache-config", "YAML file that contains response cache configuration.", extflag.WithEnvSubstitution())
// Labels tripperware flags.
// --labels.split-interval,注册标签请求的拆分间隔标志,该标志用于将标签请求按间隔拆分并并行执行。
cmd.Flag("labels.split-interval", "Split labels requests by an interval and execute in parallel, it should be greater than 0 when labels.response-cache-config is configured.").
Default("24h").DurationVar(&cfg.LabelsConfig.SplitQueriesByInterval)
// --labels.max-retries-per-request, 注册标签请求的最大重试次数标志,默认情况下,标签请求将重试5次。
cmd.Flag("labels.max-retries-per-request", "Maximum number of retries for a single label/series API request; beyond this, the downstream error is returned.").
Default("5").IntVar(&cfg.LabelsConfig.MaxRetries)
// --labels.max-query-parallelism,注册标签请求最大并行数标志,默认情况下,标签请求将并行执行14个。
cmd.Flag("labels.max-query-parallelism", "Maximum number of labels requests will be scheduled in parallel by the Frontend.").
Default("14").IntVar(&cfg.LabelsConfig.Limits.MaxQueryParallelism)
// --labels.response-cache-max-freshness,注册标签请求的响应缓存最大新鲜度标志,该标志用于防止缓存非常新鲜的结果,这些结果可能仍在变化。
cmd.Flag("labels.response-cache-max-freshness", "Most recent allowed cacheable result for labels requests, to prevent caching very recent results that might still be in flux.").
Default("1m").DurationVar((*time.Duration)(&cfg.LabelsConfig.Limits.MaxCacheFreshness))
// --labels.partial-response, 注册标签请求的局部响应标志,默认情况下,标签请求将返回部分响应。使用 --no-labels.partial-response 可禁用该策略。
cmd.Flag("labels.partial-response", "Enable partial response for labels requests if no partial_response param is specified. --no-labels.partial-response for disabling.").
Default("true").BoolVar(&cfg.LabelsConfig.PartialResponseStrategy)
// --labels.default-time-range,注册标签请求的默认时间范围标志,该标志用于设置默认的标签请求时间范围。
cmd.Flag("labels.default-time-range", "The default metadata time range duration for retrieving labels through Labels and Series API when the range parameters are not specified.").
Default("24h").DurationVar(&cfg.DefaultTimeRange)
// --labels.response-cache-config, 注册标签请求的响应缓存配置标志,该标志用于设置标签请求的响应缓存配置。
cfg.LabelsConfig.CachePathOrContent = *extflag.RegisterPathOrContent(cmd, "labels.response-cache-config", "YAML file that contains response cache configuration.", extflag.WithEnvSubstitution())
// --cache-compression-type,注册缓存压缩类型标志,该标志用于设置在结果缓存中使用的压缩算法。支持的值包括 "snappy" 和 ""(禁用压缩)。
cmd.Flag("cache-compression-type", "Use compression in results cache. Supported values are: 'snappy' and '' (disable compression).").
Default("").StringVar(&cfg.CacheCompression)
// --query-frontend.downstream-url,注册查询前端的下游URL标志,该标志用于设置查询前端的下游URL。
cmd.Flag("query-frontend.downstream-url", "URL of downstream Prometheus Query compatible API.").
Default("http://localhost:9090").StringVar(&cfg.DownstreamURL)
// --query-frontend.downstream-tripper-config,注册查询前端的下游传输器配置标志,该标志用于设置查询前端的下游传输器配置。YAML文件包含传输器配置。如果下游URL是localhost或127.0.0.1,则强烈建议使用此标志。
cfg.DownstreamTripperConfig.CachePathOrContent = *extflag.RegisterPathOrContent(cmd, "query-frontend.downstream-tripper-config", "YAML file that contains downstream tripper configuration. If your downstream URL is localhost or 127.0.0.1 then it is highly recommended to increase max_idle_conns_per_host to at least 100.", extflag.WithEnvSubstitution())
// --query-frontend.compress-responses, 注册压缩HTTP响应标志
cmd.Flag("query-frontend.compress-responses", "Compress HTTP responses.").
Default("false").BoolVar(&cfg.CompressResponses)
// --query-frontend.log-queries-longer-than, 日志查询持续时间标志,该标志用于设置日志查询持续时间。设置为0将禁用此功能。设置为小于0的值将在所有查询上启用。
cmd.Flag("query-frontend.log-queries-longer-than", "Log queries that are slower than the specified duration. "+
"Set to 0 to disable. Set to < 0 to enable on all queries.").Default("0").DurationVar(&cfg.CortexHandlerConfig.LogQueriesLongerThan)
// --query-frontend.org-id-header, 注册组织ID头标志,该标志用于设置查询前端将转发到下游查询器的HTTP头的列表。默认情况下为空。
cmd.Flag("query-frontend.org-id-header", "Request header names used to identify the source of slow queries (repeated flag). "+
"The values of the header will be added to the org id field in the slow query log. "+
"If multiple headers match the request, the first matching arg specified will take precedence. "+
"If no headers match 'anonymous' will be used.").PlaceHolder("<http-header-name>").StringsVar(&cfg.orgIdHeaders)
// --query-frontend.forward-headers, 注册转发头标志,该标志用于设置查询前端将转发到下游查询器的HTTP头列表。默认情况下为空。
cmd.Flag("query-frontend.forward-header", "List of headers forwarded by the query-frontend to downstream queriers, default is empty").PlaceHolder("<http-header-name>").StringsVar(&cfg.ForwardHeaders)
// --log.request.decision, 注册请求日志记录决策标志,该标志用于设置查询前端的请求日志记录策略。默认情况下为空。 支持的值为:"LogFinishCall"。 支持的值为:"NoLogCall"、"LogFinishCall"和"LogStartAndFinishCall"。 默认情况下为空。 "NoLogCall": 禁用请求日志记录。"LogFinishCall": 日志请求的完成调用。"LogStartAndFinishCall": 日志请求的开始和结束调用。
cmd.Flag("log.request.decision", "Deprecation Warning - This flag would be soon deprecated, and replaced with `request.logging-config`. Request Logging for logging the start and end of requests. By default this flag is disabled. LogFinishCall : Logs the finish call of the requests. LogStartAndFinishCall : Logs the start and finish call of the requests. NoLogCall : Disable request logging.").Default("").EnumVar(&cfg.RequestLoggingDecision, "NoLogCall", "LogFinishCall", "LogStartAndFinishCall", "")
// 注册请求日志记录配置
reqLogConfig := extkingpin.RegisterRequestLoggingFlags(cmd)
// 设置命令执行逻辑
cmd.Setup(func(g *run.Group, logger log.Logger, reg *prometheus.Registry, tracer opentracing.Tracer, _ <-chan struct{}, _ bool) error {
// 解析请求日志记录配置
httpLogOpts, err := logging.ParseHTTPOptions(cfg.RequestLoggingDecision, reqLogConfig)
if err != nil {
return errors.Wrap(err, "error while parsing config for request logging")
}
// 运行查询前端
return runQueryFrontend(g, logger, reg, tracer, httpLogOpts, cfg, comp)
})
}
// 定义一个函数parseTransportConfiguration,它接受一个字节切片downstreamTripperConfContentYaml作为输入,并返回一个指向http.Transport的指针和一个error对象。这个函数的作用是解析传输配置。如果downstreamTripperConfContentYaml为空,则直接返回一个默认配置的http.Transport对象。否则,它会尝试将YAML格式的内容解析到tripperConfig结构体中,并使用这个配置创建一个http.Transport对象。如果解析失败,它会返回一个错误。
// parseTransportConfiguration 解析YAML格式的下游传输配置,并返回一个配置好的 http.Transport 对象。
//
// 参数:
//
// downstreamTripperConfContentYaml: YAML格式的下游传输配置内容
//
// 返回值:
//
// *http.Transport: 配置好的 http.Transport 对象
// error: 如果解析过程中出现错误,则返回错误信息
func parseTransportConfiguration(downstreamTripperConfContentYaml []byte) (*http.Transport, error) {
// 创建一个新的http.Transport实例,这里假设exthttp.NewTransport()是一个封装或扩展了标准http.NewTransport()的函数,可能添加了一些额外的配置或功能。这个新创建的实例被赋值给downstreamTripper变量。
downstreamTripper := exthttp.NewTransport()
// 检查输入的YAML配置内容是否非空。如果为空,则不应用任何配置,直接返回默认配置的http.Transport实例。
if len(downstreamTripperConfContentYaml) > 0 {
// 创建一个queryfrontend.DownstreamTripperConfig类型的实例,这个类型是一个结构体,预期包含了一些用于配置http.Transport的字段。这个实例被赋值给tripperConfig变量。
tripperConfig := &queryfrontend.DownstreamTripperConfig{}
// 使用yaml.UnmarshalStrict函数将YAML格式的配置内容解析到tripperConfig结构体中。如果解析过程中发生错误,则返回一个错误。
if err := yaml.UnmarshalStrict(downstreamTripperConfContentYaml, tripperConfig); err != nil {
// 如果解析YAML文件时出错,则使用errors.Wrap函数包装错误,并返回nil和错误信息。
return nil, errors.Wrap(err, "parsing downstream tripper config YAML file")
}
// 根据tripperConfig中的配置设置downstreamTripper的各项参数
if tripperConfig.IdleConnTimeout > 0 {
// 设置空闲连接超时时间
downstreamTripper.IdleConnTimeout = time.Duration(tripperConfig.IdleConnTimeout)
}
if tripperConfig.ResponseHeaderTimeout > 0 {
// 设置响应头超时时间
downstreamTripper.ResponseHeaderTimeout = time.Duration(tripperConfig.ResponseHeaderTimeout)
}
if tripperConfig.TLSHandshakeTimeout > 0 {
// 设置TLS握手超时时间
downstreamTripper.TLSHandshakeTimeout = time.Duration(tripperConfig.TLSHandshakeTimeout)
}
if tripperConfig.ExpectContinueTimeout > 0 {
// 设置期望继续超时时间
downstreamTripper.ExpectContinueTimeout = time.Duration(tripperConfig.ExpectContinueTimeout)
}
if tripperConfig.MaxIdleConns != nil {
// 设置最大空闲连接数
downstreamTripper.MaxIdleConns = *tripperConfig.MaxIdleConns
}
if tripperConfig.MaxIdleConnsPerHost != nil {
// 设置每主机最大空闲连接数
downstreamTripper.MaxIdleConnsPerHost = *tripperConfig.MaxIdleConnsPerHost
}
if tripperConfig.MaxConnsPerHost != nil {
// 设置每主机最大连接数
downstreamTripper.MaxConnsPerHost = *tripperConfig.MaxConnsPerHost
}
}
// 返回配置好的downstreamTripper对象
return downstreamTripper, nil
}
// runQueryFrontend 函数用于启动查询前端服务
func runQueryFrontend(
g *run.Group, // 运行组,用于管理服务的启动和停止
logger log.Logger, // 日志记录器,用于记录日志信息
reg *prometheus.Registry, // Prometheus注册表,用于注册和收集指标数据
tracer opentracing.Tracer, // 链路追踪器,用于追踪请求的执行过程
httpLogOpts []logging.Option, // HTTP日志选项,用于配置HTTP请求的日志记录行为
cfg *queryFrontendConfig, // 查询前端配置,包含各种服务运行所需的参数和选项
comp component.Component, // 组件,用于管理服务的启动和停止
) error {
// 获取查询范围缓存配置内容
queryRangeCacheConfContentYaml, err := cfg.QueryRangeConfig.CachePathOrContent.Content()
if err != nil {
return err
// 如果配置内容不为空,则初始化查询范围缓存配置
}
if len(queryRangeCacheConfContentYaml) > 0 {
// 初始化查询范围缓存配置
cacheConfig, err := queryfrontend.NewCacheConfig(logger, queryRangeCacheConfContentYaml)
if err != nil {
return errors.Wrap(err, "initializing the query range cache config")
}
// 设置查询范围缓存配置, cfg.QueryRangeConfig.ResultsCacheConfig 是一个指向 queryrange.ResultsCacheConfig 的指针,它包含了压缩和缓存配置信息。这里将查询范围缓存的压缩选项设置为 cfg.CacheCompression,并将缓存配置设置为 cacheConfig。
cfg.QueryRangeConfig.ResultsCacheConfig = &queryrange.ResultsCacheConfig{
Compression: cfg.CacheCompression,
CacheConfig: *cacheConfig,
}
}
// 获取标签缓存配置内容
labelsCacheConfContentYaml, err := cfg.LabelsConfig.CachePathOrContent.Content()
if err != nil {
return err
}
if len(labelsCacheConfContentYaml) > 0 {
// 初始化标签缓存配置
cacheConfig, err := queryfrontend.NewCacheConfig(logger, labelsCacheConfContentYaml)
if err != nil {
return errors.Wrap(err, "initializing the labels cache config")
}
// 设置标签缓存配置, cfg.LabelsConfig 是一个指向 queryfrontend.LabelsConfig 的指针,它包含了标签缓存的配置信息。这里将查询范围缓存的压缩选项设置为 cfg.CacheCompression,并将缓存配置设置为 cacheConfig。
cfg.LabelsConfig.ResultsCacheConfig = &queryrange.ResultsCacheConfig{
Compression: cfg.CacheCompression,
CacheConfig: *cacheConfig,
}
}
// 验证配置
if err := cfg.Validate(); err != nil {
return errors.Wrap(err, "error validating the config")
}
// 初始化tripperware, tripperware 是一个用于处理 HTTP 请求的中间件,它可以将请求传递给下游的 HTTP 服务。这里使用 queryfrontend.NewTripperware 函数初始化 tripperware,传入配置、注册表和日志记录器。这里将 cfg.Config 作为配置参数,reg 和 logger 分别作为注册表和日志记录器的参数。如果初始化失败,则返回错误信息。
tripperWare, err := queryfrontend.NewTripperware(cfg.Config, reg, logger)
if err != nil {
return errors.Wrap(err, "setup tripperwares")
}
// 获取下游传输配置内容, 获取下游传输配置内容,并将其解析为传输配置。如果解析失败,则返回错误信息。
downstreamTripperConfContentYaml, err := cfg.DownstreamTripperConfig.CachePathOrContent.Content()
if err != nil {
return err
}
// 解析下游传输配置, 使用 parseTransportConfiguration 函数将下游传输配置内容解析为 http.Transport 对象。如果解析失败,则返回错误信息。如果下游传输配置内容为空,则直接使用默认的 http.Transport 对象。
downstreamTripper, err := parseTransportConfiguration(downstreamTripperConfContentYaml)
if err != nil {
return err
}
// 创建下游往返器
roundTripper, err := cortexfrontend.NewDownstreamRoundTripper(cfg.DownstreamURL, downstreamTripper)
if err != nil {
return errors.Wrap(err, "setup downstream roundtripper")
}
// 将下游往返器包装到查询前端Tripperware中
roundTripper = tripperWare(roundTripper)
// 创建查询前端传输
handler := transport.NewHandler(*cfg.CortexHandlerConfig, roundTripper, logger, nil)
if cfg.CompressResponses {
handler = gziphandler.GzipHandler(handler)
}
// 创建HTTP探针
httpProbe := prober.NewHTTP()
statusProber := prober.Combine(
httpProbe,
prober.NewInstrumentation(comp, logger, extprom.WrapRegistererWithPrefix("thanos_", reg)),
)
// 配置HTTP调用的请求日志记录
logMiddleware := logging.NewHTTPServerMiddleware(logger, httpLogOpts...)
ins := extpromhttp.NewInstrumentationMiddleware(reg, nil)
// 启动HTTP服务器以收集指标
{
srv := httpserver.New(logger, reg, comp, httpProbe,
httpserver.WithListen(cfg.http.bindAddress),
httpserver.WithGracePeriod(time.Duration(cfg.http.gracePeriod)),
httpserver.WithTLSConfig(cfg.http.tlsConfig),
)
// 配置HTTP处理函数
instr := func(f http.HandlerFunc) http.HandlerFunc {
hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
orgId := extractOrgId(cfg, r)
name := "query-frontend"
if !cfg.webDisableCORS {
api.SetCORS(w)
}
tracing.HTTPMiddleware(
tracer,
name,
logger,
ins.NewHandler(
name,
gziphandler.GzipHandler(
middleware.RequestID(
logMiddleware.HTTPMiddleware(name, f),
),
),
),
// Cortex前端中间件需要orgID
).ServeHTTP(w, r.WithContext(user.InjectOrgID(r.Context(), orgId)))
})
return hf
}
srv.Handle("/", instr(handler.ServeHTTP))
// 添加服务器启动和关闭逻辑到组中
g.Add(func() error {
statusProber.Healthy()
return srv.ListenAndServe()
}, func(err error) {
statusProber.NotReady(err)
defer statusProber.NotHealthy(err)
srv.Shutdown(err)
})
}
level.Info(logger).Log("msg", "starting query frontend")
statusProber.Ready()
return nil
}
// extractOrgId 从HTTP请求中提取组织ID
//
// 参数:
//
// conf *queryFrontendConfig: 配置结构体指针,包含组织ID头部列表
// r *http.Request: HTTP请求结构体指针
//
// 返回值:
//
// string: 提取的组织ID字符串,如果未找到则返回"anonymous"
func extractOrgId(conf *queryFrontendConfig, r *http.Request) string {
// 遍历conf.orgIdHeaders切片中的每个元素。每个元素(这里命名为header)都是一个字符串,代表可能包含组织ID的HTTP请求头名称。
for _, header := range conf.orgIdHeaders {
// 通过r.Header.Get(header)方法从HTTP请求r的头部中获取名为header的值,并将这个值存储在变量headerVal中。r.Header是一个http.Header类型的映射,它允许通过键(这里是HTTP请求头的名称)来获取对应的值(这里是HTTP请求头的值)。
headerVal := r.Header.Get(header)
// 检查变量headerVal是否不为空字符串。如果不为空,意味着在当前的请求头中找到了一个有效的组织ID值。在这种情况下,直接返回这个值作为组织ID。
if headerVal != "" {
return headerVal
}
}
// 如果遍历完conf.orgIdHeaders中列出的所有请求头后,没有找到任何有效的组织ID值,函数返回字符串"anonymous"。这表示请求没有明确指定组织ID,因此默认将其视为匿名请求。
return "anonymous"
}

浙公网安备 33010602011771号