#冲刺创作新星#Thanos 架构剖析(二)统一的查询入口 原创
Thanos 的 Query 组件实现了 Prometheus 的 HTTP v1 API,可以像 Prometheus 的 Graph 一样,通过 PromQL 查询 Thanos 集群中的数据。
简而言之,它从底层的 Store API 收集查询所需的数据,执行查询操作并返回结果。 Query 组件是可以无状态和水平扩展的。
查询器是完全无状态和水平可伸缩的。
Thanos Query 组件的启动非常简单 ,比如:
thanos query \
--http-address "0.0.0.0:9090" \
--store "<store-api>:<grpc-port>" \
--store "<store-api2>:<grpc-port>"
- –http-address :组件启动后对外提供服务的地址和端口
- –store :Thanos 提供数据查询的 IP 地址和 gRPC 端口,一般是 Sidecar 和 Store 的地址和端口。
Thanos Query 作为入口,后边是任何实现了gRPC StoreAPI的组件,我们可以从任意数量的不同存储聚合数据,比如:
- Prometheus ,使用 Sidecar 作为代理从 Prometheus 获取数据
- 对象存储,使用 Store 组件为对象存储代理请求可以获取对象存储中的监控数据
- 全局的告警规则和记录规则,比如使用 Thanos Rule 组件
- Receiver 组件的数据,Prometheus 可以通过远程写功能将数据吐给 Receiver,Query 可以从Receiver 组件获取这些数据
- 其他的 Query 组件
- 其他非 Prometheus 的系统,比如 OpenTSDB。
因为 Thanos Query 的这种设计,我们可以在一个地方,查询所有的内容,包括 Prometheus 的数据、告警规则、监控记录规则,这些来源不同的指标也可以进行汇总。
HA 集群运行时重复数据删除
Prometheus 是有状态的,如果使用运行多个 Prometheus 实例使用过同一个数据库来实现高可用是不可能的。另外通过简单的负载均衡来实现高可用,当出现程序崩溃后,监控数据还在上传,会导致 2 个数据库的不一致。
Thanos 为 Prometheus 提供的高可用方案是,单独启动两个 Prometheus 实例同时去拉取监控数据,Thanos Query 会从 2 个实例中获取数据,然后对数据进行去重,如果存在空白,那么就保留。这样整个 Query 对用户是透明的,2 个 Prometheus 实例对用户是无感的,Query 在前边做了代理。对于数据来说,2 个 Prometheus 只要有一个有数据,用户就能查到数据。我们也可以启动 3 个或者更多的实例来保证监控的高可用。
指标查询工作流
https://thanos.io/tip/components/query.md/#metric-query-flow-overview
总体来说,Thanos 暴露的 Query API 保证兼容 Prometheus 2.x 版本的API。上图显示了 Query 对每个 Prometheus 查询请求所做的工作。
重复数据删除
查询层可以删除从高可用性数据源(如Prometheus)收集的时间序列数据,必须为整个集群选择一个固定的单个或多个副本标签,然后可以在启动时传递给查询节点。
两个或多个只通过给定的复制标签区分的序列将合并为一个时间序列。这也隐藏了单个数据源集合中的差距。
下面是一个单副本标签的例子,
Prometheus + sidecar “A”: cluster=1,env=2,replica=A
Prometheus + sidecar “B”: cluster=1,env=2,replica=B
Prometheus + sidecar “A” in different cluster: cluster=2,env=2,replica=A
我们可以配置 Query 组件的启动参数如下:
thanos query \
--http-address "0.0.0.0:9090" \
--query.replica-label "replica" \
--store "<store-api>:<grpc-port>" \
--store "<store-api2>:<grpc-port>" \
--query.replica-label "replica"
参数的作用是打开删除重复数据,这样可以过滤多个 Prometheus 实例采集同一份监控数据带来的数据重复。
我们查询 up{job="prometheus",env="2"}
指标的时候会查到 2 个结果 :
up{job="prometheus",env="2",cluster="1"} 1
up{job="prometheus",env="2",cluster="2"} 1
如果不使用 --query.replica-label "replica"
参数关闭重复数据删除,我们会得到 3 个结果,
up{job="prometheus",env="2",cluster="1",replica="A"} 1
up{job="prometheus",env="2",cluster="1",replica="B"} 1
up{job="prometheus",env="2",cluster="2",replica="A"} 1
在这个例子中,使用多个复制标签会得到相同的输出:
- Prometheus + sidecar “A”:
cluster=1,env=2,replica=A,replicaX=A - Prometheus + sidecar “B”:
cluster=1,env=2,replica=B,replicaX=B - Prometheus + sidecar “A” in different cluster:
cluster=2,env=2,replica=A,replicaX=A
thanos query \
--http-address "0.0.0.0:9090" \
--query.replica-label "replica" \
--query.replica-label "replicaX" \
--store "<store-api>:<grpc-port>" \
--store "<store-api2>:<grpc-port>" \
这个逻辑也可以通过 Query API 上的参数来控制。下面的更多细节。
Query Frontend
thanos query-frontend
命令实现了一个可以放在 thanos Query 前面的服务,以改进读取路径。它基于 Cortex Query Frontend 组件,所以你可以找到一些常见的特性,如查询拆分和结果缓存。
Thanos Query Frontend 是无状态和水平扩展的,可以使用下列命令来启动 Thanos Query Frontend
thanos query-frontend \
--http-address "0.0.0.0:9090" \
--query-frontend.downstream-url="<thanos-querier>:<querier-http-port>"
目前,只有范围查询(/api/v1/query_range 的 api调用)实际上是通过Query Frontend处理的。所有其他的 API 调用直接进入下游 Thanos Query 来查询,这意味着只有范围查询被分割和缓存,开发者已经将即时查询加到计划列表中,后续也会支持。
查询拆分
Query Frontend 根据配置的 --query-range.split-interval
将一个长查询拆分为多个短查询。--query-range.split-interval
默认值是 24h ,当查询 24 小时内的指标时不进行拆分,当查询超过 24 小时的指标时,会对查询语句进行拆分来减小查询压力。当启用缓存时,它应该大于0。
使用查询拆分功能有3 个好处:
1、它可以保证查询组件 Query 和其他组件的稳定性,防止大型查询对 Query 造成 OOM,导致程序崩溃。
2、可以更好的并行查询
3、可以提供更好的查询负载。
Retry
Thanos Query Frontend 支持重试机制,可以在 HTTP 查询请求失败时进行重试,通过使用参数 --query-range.max-retries-per-request
限制最大重试次数,这个一般建议重试 1 次到 2 次就可以了,不建议超过 3 次,太多的重试会导致 Thanos Query 组件的压力变大。另外就是如果针对查询语句有错误或者其他问题,尽快失败,暴露问题,而不是一次一次的重试。
Cache
Query Frontend 支持缓存查询结果,并在后续查询中重用它们。如果缓存的结果不完整,Query Frontend 会计算所需的子查询,并在下游 Query 上并行的执行这些。Query Frontend 可以选择将查询与其步长参数对齐,以提高查询结果的可缓存性。目前,支持内存缓存(fifo缓存)和memcached。
内存缓存
type: IN-MEMORY
config:
max_size: ""
max_size_items: 0
validity: 0s
max_size: 在内存中缓存的最大尺寸,单位可以是 KB、MB、GB。
如果 max_size 和 max_size_items 都没有设置,就不会创建缓存。
如果只设置 max_size 或 max_size_items 中的任意一个,则对其他字段没有限制。
memcached 缓存
type: MEMCACHED
config:
addresses: []
timeout: 0s
max_idle_connections: 0
max_async_concurrency: 0
max_async_buffer_size: 0
max_get_multi_concurrency: 0
max_item_size: 0
max_get_multi_batch_size: 0
dns_provider_update_interval: 0s
auto_discovery: false
expiration :指定 memcached 缓存有效时间。如果设置为0,则使用默认的24小时过期时间。
默认的 memcached 配置是:
type: MEMCACHED
config:
addresses: [your-memcached-addresses]
timeout: 500ms
max_idle_connections: 100
max_item_size: 1MiB
max_async_concurrency: 10
max_async_buffer_size: 10000
max_get_multi_concurrency: 100
max_get_multi_batch_size: 0
dns_provider_update_interval: 10s
expiration: 24h
Redis 缓存
默认的 Redis 配置如下:
type: REDIS
config:
addr: ""
username: ""
password: ""
db: 0
dial_timeout: 5s
read_timeout: 3s
write_timeout: 3s
pool_size: 100
min_idle_conns: 10
idle_timeout: 5m0s
max_conn_age: 0s
max_get_multi_concurrency: 100
get_multi_batch_size: 100
max_set_multi_concurrency: 100
set_multi_batch_size: 100
expiration: 24h0m0s
expiration :指定 Redis 缓存有效时间。如果设置为0,则使用默认的24小时过期时间。
慢查日志
Query Frontend 支持使用 --query-frontend.log-queries-longer-than
参数去通过日志记录查询时长超过某个范围的查询语句。这个功能有 2 个作用,第一个是在 Query 被查挂的时候,查找出现问题的原因,第二个功能是定期查找耗时长的语句,对这些语句优化或者制定规范,这样来提高查询效率。
一些推荐的配置
Thanos Query Frontend 的 HTTP Client 可以配置 --query-frontend.downstream-tripper-config
和 --query-frontend.downstream-tripper-config-file
.
如果使用负载均衡指向后端主机,那么建议 max_idle_conns_per_host 参数值最少为 100 ,否则 Query Frontend 无法利用 HTTP 活动链接,延迟将会升高 10% ~ 20% 。默认情况下,Go HTTP client 会在每个机器上保留 2 个空闲链接。