使用到的第三方包
- 借鉴prom,采用kingpin进行command、flag解析
- 使用github.com/oklog/run 包来启动一组协程
- http server、grpc server、动态发现位于下游的实现STORE API的组件
- github.com/go-kit/kit 微服务框架
最佳实践
- 1、prom是有状态的节点,我们把基础prom和stroe当作数据源
- 2、ruler也是数据源,可以根据数据中心位置启用一组或者多组供快速预计算
- 3、query是无状态的,我们可以无限扩展它来满足不同的需求。例如,调度要求响应必须快
query
架构
启动参数
- thanos的代码采用标准目录结构构建
- 入口统一 同一个二进制文件 格式都是thanos开头,每个组件在都会注册,根据解析command可知道本次运行的是哪个组件
thanos query \
--log.level=debug \
--query.auto-downsampling \
--grpc-address=0.0.0.0:10901 \
--http-address=0.0.0.0:9090 \
--query.partial-response \
--query.replica-label=prometheus_replica \
--query.replica-label=rule_replica \
--store=dnssrv+_grpc._tcp.prometheus-headless.thanos.svc.cluster.local \
--store=dnssrv+_grpc._tcp.thanos-rule.thanos.svc.cluster.local \
--store=dnssrv+_grpc._tcp.thanos-store.thanos.svc.cluster.local
注意:
1、partial-response 一定要加上
这个flag设立的目的是遵照数据一致性原则
如果1、2、3中有某个返回查询数据为空或者超时(默认是2分钟)的情况下,这次整体的结果是否还要保留:
开启的情况下 , 有点儿部分人成虎的感觉
不开启,则必须要求每个上游都要有结果才行,真一致性
2、deduplicate
不去重的情况下,会看到相同数据了具体来自哪个数据源,尤其是prom和ruler源
去重开启,程序后台会对每次响应的数据源做个打分,选择优秀的源作为本次的gRPC对象
代码逻辑
- 1、main方法。创建app对象,app对象包含了所有Thanos组件的启动函数,但真正启动时只从map中取出一个函数进行启动,取出哪个函数取决于启动命令。
func main() {
app := extkingpin.NewApp(kingpin.New(filepath.Base(os.Args[0]), "A block storage based long-term storage for Prometheus").Version(version.Print("thanos")))
// 把所有组件的启动逻辑都放进app对象中的setups列表中
registerSidecar(app)
registerStore(app)
registerQuery(app)
registerRule(app)
registerCompact(app)
registerTools(app)
registerReceive(app)
registerQueryFrontend(app)
// 根据命令行的信息,从app对象的setups列表中取出一个组件逻辑
cmd, setup := app.Parse()
logger := logging.NewLogger(*logLevel, *logFormat, *debugName)
var g run.Group
var tracer opentracing.Tracer
/*
tracing相关的代码
*/
reloadCh := make(chan struct{
}, 1)
// 启动特定的一个组件(sidecar、query、store等组件中的一种),底层还是执行g.Add(...)
if err := setup(&g, logger, metrics, tracer, reloadCh, *logLevel == "debug"); err != nil {
os.Exit(1)
}
// 监听来自系统的杀死信号.
{
cancel := make(chan struct{
})
g.Add(func() error {
return interrupt(logger, cancel)
}, func(error) {
close(cancel)
})
}
// 监听来配置重载的信号
{
cancel := make(chan struct{
})
g.Add(func() error {
return reload(logger, cancel, reloadCh)
}, func(error) {
close(cancel)
})
}
// 阻塞地等待所有协程中的退出
// 有一个协程返回,其他协程也会返回
if err := g.Run(); err != nil {
level.Error(logger).Log("err", fmt.Sprintf("%+v", errors.Wrapf(err, "%s command failed", cmd)))
os.Exit(1)
}
// 到达此处,说明整个程序结束了。
level.Info(logger).Log("msg", "exiting")
}
- 2、registerQuery函数
func registerQuery(app *extkingpin.App) {
cmd := app.Command(comp.String(), "query node exposing PromQL enabled Query API with data retrieved from multiple store nodes")
/*
解析命令行参数
*/
//Setup()的参数是一个函数,会被放入app对象的setups列表中
//闭包的使用技巧以及相关堆栈的分析
//最核心的是runQuery()方法
cmd.Setup(func(g *run.Group, logger log.Logger, reg *prometheus.Registry, tracer opentracing.Tracer, _ <-chan struct{
}, _ bool) error {
...
...
...
return runQuery(
g,
logger,
reg,
tracer,
*requestLoggingDecision,
*grpcBindAddr,
time.Duration(*grpcGracePeriod),
*grpcCert,
*grpcKey,
*grpcClientCA,
/*
其他代码
*/
)
)
}
- 3、runQuery函数
//使用run.Group对象来启动http server、grpc server、服务发现协程。
func runQuery(
g *run.Group, //其实来自main()方法
logger log.Logger,
reg *prometheus.Registry,
tracer opentracing.Tracer,
requestLoggingDecision string,
grpcBindAddr string,
grpcGracePeriod time.Duration,
grpcCert string,
grpcKey string,
grpcClientCA string,
/*
其他代码
*/
) error {
var (
// stores对象的类型StoreSet。它包含了一组store组件
//(位于下游的实现Store API的组件),这一组store组件是可以动态变化的
/*
type StoreSet struct {
//其他属性
stores map[string]*storeRef
}
*/
stores = query.NewStoreSet(...)
// proxy对象,即下游的Store API组件的代理
// 下游的Store API组件的列表,其实就是构造方法的入参stores.Get这个方法来获取
proxy = store.NewProxyStore(logger, reg, stores.Get, component.Query, selectorLset, storeResponseTimeout)
rulesProxy = rules.NewProxy(logger, stores.GetRulesClients)
/*
queryableCreator是一个方法,用于创建一个querier结构体对象;
querier结构体的属性proxy就是proxy对象,它包含了一组会动态变化的thanos store组件(动态变化是因为启动了一些额外的专门的协程来动态地修改这个切片);
*/
queryableCreator = query.NewQueryableCreator(
logger,
extprom.WrapRegistererWithPrefix("thanos_query_", reg),
proxy,
maxConcurrentSelects,
queryTimeout,
)
/*
这一段代码都是启动一些协程,定时发现和动态发现Store API组件的变化,随即更新stores对象中的类型为map[string]*storeRef的属性
*/
// 创建http server,注册http handler,并启动server
{
router := route.New()
//新建QueryAPI结构体对象
api := v1.NewQueryAPI(
logger,
stores,
engine,
queryableCreator