- 时序数据库
- 后端
- 数据存储
- 可观测性
- 云原生
【免费下载链接】mimir
Grafana Mimir provides horizontally scalable, highly available, multi-tenant, long-term storage for Prometheus.
导读
本文基于 Grafana Mimir 仓库中的 Ruler 设计文档(位于 compartments 架构文档集),深入讲解在 compartments(分区舱室)架构下 Ruler 如何作为全局查询层的一部分工作:规则评估通过远程评估路径走专属的 ruler query-frontend / query-scheduler / ruler querier,规则结果则通过所在可用区的 headless distributor 服务,借助 gRPC 客户端负载均衡写入到对应写入分区的 Kafka 集群,再按租户 + 指标名分片到读取分区。读完本文,你将掌握 compartments 模式下 Ruler 的完整调用链、双层分片原理、关键配置参数,以及该设计背后的权衡。
背景说明:compartments 是 Mimir 中处于实验阶段、默认关闭的架构(见 状态与限制),它将单个 Mimir cell 按系列切分为多个互相独立的"子 cell"(compartment)。其中写路径由写分区(write compartment,
wc-<id>)承担,存储侧由读分区(read compartment,rc-<id>)承担,二者数量相互独立、没有一一对应关系。
一、Ruler 在 compartments 架构中的定位
按照 compartments 总览文档,一个 Mimir cell 被拆成两套互相独立的分区集合:
- 写分区(Write compartments):负责摄入侧,由 distributors 加一套专属 Kafka 集群组成;
- 读分区(Read compartments):负责指标存储侧,包含 ingesters、store-gateways、block-builders、compactors;
- 全局查询层(Global query layer):query-frontend、query-scheduler、querier、ruler 横跨所有分区,不属于任何一组。
Ruler 属于全局查询层,这一点与原文档的表述完全一致:它不再以某个读写分区的组成部分运行,而是以 cell 级的多可用区(multi-zone)舰队形式存在。具体而言:
- Ruler 实例使用一个全局 ring来对规则组(rule groups)做分片;
- 规则组从共享的 ruler storage(独立于各读分区的对象存储桶)读取;
- 每个可用区都有一份 ruler、ruler 查询路径以及 distributor 切片(zonal slice),而每个写分区则拥有一个 cell 级的逻辑 Kafka 集群。
从源码看,pkg/ruler/ruler.go 中定义了RulerRingKey = "ring",即 ruler ring 在 KVStore 中的注册键;规则组的拉取与分片正是围绕这个全局 ring 展开的。
二、规则评估:远程评估与读分区定位查询
2.1 远程规则评估(Remote rule evaluation)
在 compartments 模式下,Ruler不使用内部查询路径直接读数据,而是把每一次规则评估发送给所在可用区的 ruler query-frontend:
- Ruler 将每条规则的 PromQL 表达式封装为远程读请求,发给本可用区的ruler query-frontend;
- 查询经过ruler query-scheduler排队调度;
- 由ruler querier实际去查询读分区,其查询行为与普通查询完全一致:按照指标名定位到特定读分区,必要时向多个读分区 fan-out,然后合并结果。
在本地开发环境的配置 development/mimir-compartments/config/mimir.yaml 中可以看到对应设置:
ruler: alertmanager_url: http://alertmanager-1:8041/alertmanager,http://alertmanager-2:8042/alertmanager,http://alertmanager-3:8043/alertmanager for_grace_period: 1s # Evaluate rules via query-frontend (remote rule evaluation). query_frontend: address: dns:///query-frontend:9095这里ruler.query_frontend.address使用dns:///前缀的 gRPC 地址以启用客户端负载均衡。对应的源码实现位于 pkg/ruler/remotequerier.go:QueryFrontendConfig支持dns:///形式的 gRPC 地址或 HTTP(S) 地址(对接 Prometheus 兼容服务),并提供-ruler.query-frontend.query-result-response-format(json或protobuf,默认protobuf)与-ruler.query-frontend.max-retries-rate等参数。实际的远程读请求通过 Prometheus Remote Read 协议发送到/api/v1/read端点(见 pkg/ruler/remotequerier.go)。
2.2 查询如何定位读分区
规则的表达式在读取数据时遵循与普通查询相同的 compartment-aware 定位逻辑,详见 读分区文档:
- 等值匹配单个指标名:该指标的所有序列只存在于一个读分区,查询仅发给那个分区;
- 指标名被限制为有限集合(例如等价于
a|b|c的正则):查询发给这些指标名所属分区的并集; - 其余情况(如开放正则
.+或没有指标名过滤):查询 fan-out 到所有读分区并合并结果。
这一逻辑在源码 pkg/compartments/router.go 的CompartmentsForMatchers中实现:遍历__name__matcher,优先取等值匹配(唯一确定一个分区),其次取可枚举的正则匹配集(取最小集合映射到分区并集),无法收缩时返回全部分区。
同时要强调:持有输入序列的读分区并不决定结果写到哪里。规则产生的序列会按它们自己的指标名重新路由,因此一条规则完全可以从一组读分区读数据,把结果写到另一组读分区——这是本文第三、四节的核心。
三、写入规则结果:zonal headless distributor 服务
3.1 调用链与客户端负载均衡
规则评估完成后,无论是 recording rule 还是 alerting rule,Ruler 都会把生成的序列通过 gRPC 推送给所在可用区的一个 headless distributor 服务。该服务暴露了本可用区内每个写分区的 distributor 切片,gRPC 客户端负载均衡为每个请求选择其中一个端点。
这里的"headless"体现在部署上:在 Kubernetes 中对应 distributor 的 headless service(gRPC 端口);在本地开发环境中,development/mimir-compartments/docker-compose.jsonnet 为distributor-wc-0、distributor-wc-1共享了同一个网络别名distributor,nginx 通过该别名在两者之间做负载均衡。Ruler 的对应参数同样使用 DNS 形式:
-ruler.distributor.address=dns:///distributor:9095该参数的源码定义在 pkg/ruler/distributor_client.go:
ruler.distributor.address:规则结果要推送的 distributor gRPC 地址;为空时 Ruler 使用内部 distributor(非 compartments 模式),填写dns:///前缀的 DNS 地址时启用 gRPC 客户端负载均衡;ruler.distributor.remote-timeout:默认10s,单次推送请求的超时;ruler.distributor.grpc-client-config:gRPC 客户端详细配置。
校验逻辑(pkg/ruler/distributor_client.go)要求地址必须是 gRPC 地址(拒绝http:///https://),且使用服务发现时必须满足dns:///三斜杠格式。
3.2 被选中的 distributor 完成双层分片
被选中的 distributor 会把请求送入自己写分区的 Kafka 集群,并对每条生成的序列做与普通写入完全相同的双层分片(见 分片文档):
- 按租户 + 指标名分片到读分区:
read_compartment = hash(user_id, metric_name) % num_read_compartments因为 user ID 参与了哈希,同一指标名在不同租户下可能落到不同读分区;
- 在读分区内按序列标签哈希分片到分区(partition):复用 Mimir 原有的系列标签哈希分片逻辑。
对应的哈希实现见 pkg/mimirpb/series_sharding.go 的ShardByMetricName(userID, metricName),它先哈希 user ID 再加入 metric name;写路径侧的分组逻辑见 pkg/distributor/compartments.go 的getCompartmentTokensForWriteRequest:它遍历请求中的每条序列与 metadata,调用router.CompartmentForMetric归组到对应读分区,并为每个条目计算分区 ring token,然后过滤掉空分区。TopicRouter(pkg/compartments/router.go)则把<read-compartment-id>占位符替换成实际读分区 ID,得到每个分区的 Kafka topic(例如mimir-ingest-rc-<read-compartment-id>,占位符常量定义在 pkg/compartments/config.go)。
Ruler 侧的推送客户端实现同样在 pkg/ruler/distributor_client.go:Push通过distributorpb.DistributorClient.Push发送mimirpb.WriteRequest,并在请求元数据中标记"client-id": "ruler"以便 distributor 中间件识别规则输出;大请求会按最大可发送大小拆分(splitWriteRequest),推送失败时对Unavailable、DeadlineExceeded、Internal等可重试错误按退避策略重试(pushWithRetries/isRetryableDistributorPushError),并暴露cortex_ruler_remote_distributor_requests_per_write_request指标记录单个写入请求被拆分的次数。
四、完整架构图
原文档提供了如下 Mermaid 架构图,展示了一个代表性可用区内 Ruler 的读写路径(以 2 个读分区、2 个写分区为例):
读图时需要抓住几个关键点:
- 读写两条路径并行且独立:左侧是评估查询路径(Ruler → ruler query-frontend → ruler query-scheduler → ruler querier → 读分区),右侧是结果写入路径(Ruler → zonal headless distributor service → 各写分区 distributor → Kafka → 读分区)。
- Kafka 集群按写分区隔离:每个写分区拥有一个 cell 级的逻辑 Kafka 集群,图中
wc-0、wc-1各一个;而ingest-rc-<id>topic 在每个写分区的 Kafka 集群里都存在一份,因为每个写分区都会写入每个读分区(见 写分区文档)。 - 2×2 只是示意:两类分区都跨可用区分布,数量相互独立,不存在一一配对;图中展示在可用区内的 ruler、ruler 查询路径和 distributor 切片在每个可用区都会重复出现。
五、compartments 模式下 Ruler 的特殊行为
5.1 不再运行内部 distributor
在 compartments 部署中,Ruler不会为规则结果写入运行内部 distributor(非 compartments 模式下 Ruler 可以直接把结果交给进程内的 distributor)。这正是 pkg/ruler/distributor_client.go 中ruler.distributor.address默认值为空时的分支说明:"If empty, the ruler writes using the internal distributor"——配置了远程地址后,写入就完全改走 gRPC 远程路径。
5.2 评估与写入是分离的两步
原文档明确说明:PromQL 表达式评估与结果摄入是两个独立步骤。可能出现评估成功但结果写入失败的情况,此时:
- 规则被报告为unhealthy;
- 一次evaluation failure被记录。
这意味着 compartments 并不会让规则写入"搭便车"成功——结果写入的失败不会被评估成功掩盖。
5.3 Alertmanager 通知路径不受影响
规则产生的告警通知继续走既有的 Alertmanager 通知路径,不会经由 compartments 路由。本地开发环境的配置也印证了这一点:ruler.alertmanager_url直接指向一组 alertmanager 实例(见 development/mimir-compartments/config/mimir.yaml),与分区无关。
六、为什么 Ruler 保持全局:设计与权衡
6.1 为什么不能把 Ruler 放进某个写分区
原文档给出了两个候选方案的否定理由:
- 把 Ruler 放进单个写分区:会导致所有规则结果请求都指向该分区,形成单一热点与故障集中点;
- 把 Ruler 按读分区分散、并逐一绑定到写分区:因为读写分区的数量相互独立,这种映射无法干净地对齐。
因此 Ruler 留在全局层,把每一次结果写入请求都发给分区内(zonal)的 distributor 服务:客户端负载均衡从一个包含所有写分区 zonal 切片的端点池中挑选一个 distributor,被选中的 distributor 再执行常规的读分区分片 + 分区内分片。
6.2 明确的权衡
原文档坦诚地指出了代价:compartments 不能隔离 Ruler 的调度与评估。如果整个 Ruler 舰队出现问题,会影响所有规则的评估——哪怕其中一些规则本可以被定位到不同读分区。不过,当一个规则表达式可以被收缩到读分区的一个子集时,读分区仍然能限制数据路径(data-path)上的故障影响面,即查询爆炸半径仍然得到缩减。
6.3 与全局 ring 的对应关系
Ruler 使用单个全局 ring 进行规则组分片,这与写分区侧"distributors 共享单一全局 ring"的设计思路一致(后者仅用于统计全局 distributor 数量以驱动全局租户限流,见 写分区文档)。全局 ring 的存在是"Ruler 作为全局层而非分区内组件"这一设计的直接体现。
七、配置实践:在本地 compartments 环境中验证
仓库提供了本地开发环境development/mimir-compartments(2 个写分区 + 2 个读分区),可以端到端验证本文描述的行为,详见 状态与限制文档。
关键配置片段汇总(来自 development/mimir-compartments/config/mimir.yaml 与 development/mimir-compartments/docker-compose.jsonnet):
# 启用 compartments 并声明读写分区数量 compartments: enabled: true read: num_compartments: 2 write: num_compartments: 2 # 全局启用 ingest-storage 写入侧;各组件通过 CLI 覆盖为自己的分区服务 ingest_storage: enabled: true kafka: address: kafka-wc-0:9092 topic: mimir-ingest-rc-0 ruler: # 远程规则评估:发往本可用区的 query-frontend query_frontend: address: dns:///query-frontend:9095 # 告警通知继续走既有 Alertmanager 路径 alertmanager_url: http://alertmanager-1:8041/alertmanager,... ruler_storage: s3: bucket_name: mimir-ruler各写分区的 distributor 通过 CLI 指定自己的 Kafka 集群与 topic 模板:
-distributor.write-compartment-id=<wc-id> -ingest-storage.kafka.address=kafka-wc-<write-compartment-id>:9092 -ingest-storage.kafka.topic='mimir-ingest-rc-<read-compartment-id>'而 Ruler 实例则通过以下参数把结果写入交给分区内 distributor 服务、把评估查询交给分区内 query-frontend(见 development/mimir-compartments/docker-compose.jsonnet):
-ruler.distributor.address=dns:///distributor:9095环境中两个读分区的指标都可查询:当查询等值匹配某个指标名时,由持有该指标名的读分区单独服务;没有指标名等值匹配时则 fan-out 到两个读分区。
八、当前实现状态与局限
根据 状态与限制文档,"全局 Ruler 评估与写入"已被列为当前已实现的部分:Ruler 通过远程的 compartment-aware 查询路径评估规则,并把生成的序列发给横跨所有写分区的 zonal distributor 端点池,被选中的 distributor 再执行常规读分区分片。
但 compartments 整体仍是实验性且默认关闭的特性,部分能力尚未补齐,例如 per-compartment block-builders 尚未进入 jsonnet 部署库、shuffle sharding 与集成测试等写路径能力仍在建设中。因此,本文描述的全局 Ruler 行为适用于开启 compartments 的部署场景,生产落地前应结合 状态与限制文档 与 配置文档 确认当前支持的边界。
延伸阅读
- compartments 总览与架构
- 双层分片与分段标签选择
- 读分区:存储与定位查询
- 写分区:Kafka 隔离与全局 ring
- 命名规范(wc- / rc- )
- compartments 配置归属说明
- 本地开发环境配置
- 本地开发环境编排定义
- 时序数据库
- 后端
- 数据存储
- 可观测性
- 云原生
【免费下载链接】mimir
Grafana Mimir provides horizontally scalable, highly available, multi-tenant, long-term storage for Prometheus.
相关推荐
OpenScreen麦克风设置:增益调整与降噪技巧终极指南
OpenScreen麦克风设置:增益调整与降噪技巧终极指南 OpenScreen是一款强大的开源屏幕录制工具,能够创建高质量演示视频,完全免费且无水印。本文将详
桌面应用音视频视频处理屏幕录制Loki Ruler 远程规则评估(Remote Rule Evaluation)设计解析与实现指南
Loki Ruler 远程规则评估(Remote Rule Evaluation)设计解析与实现指南 导读 本文基于 Loki 社区 LID(Loki Impr
可观测性日志分析后端微服务对象存储云原生Cortex Ruler 租户联邦(Federated Rules):跨租户规则评估的设计与实战
Cortex Ruler 租户联邦(Federated Rules):跨租户规则评估的设计与实战 导读 本文围绕 Cortex 开源仓库中的 Ruler 租户联
可观测性时序数据库后端指标监控
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考