Loki 多租户隔离实战:用 Shuffle Sharding 隔离查询路径、Ruler 与 Index Gateway 的工作负载

📅 发布时间:2026/9/12 5:32:54
Loki 多租户隔离实战:用 Shuffle Sharding 隔离查询路径、Ruler 与 Index Gateway 的工作负载
Loki 多租户隔离实战用 Shuffle Sharding 隔离查询路径、Ruler 与 Index Gateway 的工作负载【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/lokiShuffle sharding 是 Loki 中用于多租户资源隔离的关键技术它把每个租户的请求只路由到组件实例的子集shard而不是全部实例从而在共享集群中为租户提供接近单租户的使用体验。本文以查询路径为主线讲解这一技术的设计动机与实现原理并完整给出查询路径、Ruler、Index Gateway 三个组件的启用配置、参数语义与监控手段帮助你为多租户集群构建可控的故障爆炸半径。为什么需要 shuffle sharding多租户共享集群的故障放大问题Loki 查询路径默认就是分片的sharded但默认分片不使用 shuffle sharding每个租户的查询会被分片到所有querier 实例上即所有租户共享全部 querier 的计算资源。在多租户集群中这种全员共享的调度方式会放大两类问题任何一台实例故障都会波及所有租户querier 实例发生故障时其承载的所有租户查询都会受影响。一个问题租户会拖垮所有其他租户单个租户或一组租户发出的昂贵查询——例如导致 querier 内存溢出OOM被杀死、或直接崩溃的查询——会占满或压垮实例。由于所有租户都能使用所有 querier出错后该租户的查询会被重新调度到其他正在运行的 querier 上故障随之传染到被重新分配到的实例形成连锁影响。从源码看默认行为正是不设上限即全员参与在 pkg/validation/limits.go 中frontend.max-queriers-per-tenant的默认值为0而值为0或高于可用 querier 数量时所有querier 都会处理该租户的请求。这是理解 shuffle sharding 价值的前提默认配置下租户之间没有任何计算资源的隔离边界。shuffle sharding 的工作原理shuffle sharding 的核心思想是为每个租户分配一个由组件实例子集组成的 shard并尽量降低不同租户之间的实例重叠度。这样一个问题租户只会影响它自己 shard 内的 querier由于不同租户的 querier 重叠度很低受其影响的租户只是全体租户中很小的一部分shuffle sharding 相比默认分片策略不需要额外资源——它改变的是谁为谁服务的调度方式而不是实例总数。在 Loki 的实现中这一机制建立在统一的 ring 基础之上。从源码结构看核心入口位于 pkg/util/ring/sharding.go 的TenantShuffleSharding类型它通过ring.ShuffleShard(tenantID, shardSize)为每个租户生成一个子 ringsub-ring并用OwnsTenant判断当前实例是否在该租户的 shard 内pkg/util/shard.go 中的ShuffleShardSeed(identifier, zone)则负责根据租户标识与可用区信息生成确定性种子保证同一租户在不同组件frontend / scheduler间选择到同一组实例。需要强调的是shuffle sharding 并不能解决所有问题。如果一个租户反复发送同样的问题查询崩溃的 querier 会与 query-frontend 断开连接而新的 querier 会立即被分配到该租户的 shard 中——这会使 shuffle sharding 的隔离效果失效。缓解办法是配置一个遗忘延迟forget delay让 querier 崩溃断开后经过一段延迟再从租户 shard 中移除、并以健康实例替换。在 query-frontend 中对应参数-query-frontend.querier-forget-delay1m在 query-scheduler 中对应-query-scheduler.querier-forget-delay1m1 分钟通常是合理的取值。源码中的实现可见于 pkg/lokifrontend/frontend/v1/frontend.go 与 pkg/scheduler/scheduler.go 的QuerierForgetDelay配置项默认0即不延迟。低实例重叠概率为什么子集 随机打乱就能带来好的隔离效果以一个运行 50 台 querier、每个租户分配 4 台的集群为例实例在租户间打乱组合后共有约 23 万种组合。统计上随机挑选两个不同租户它们之间的实例重叠概率为71%的概率完全不共享任何实例26%的概率仅共享 1 台实例2.7%的概率共享 2 台实例0.08%的概率共享 3 台实例只有0.0004%的概率实例完全重叠。下图直观展示了这一概率随重叠实例数增加而急剧下降的分布图片位于 docs/sources/operations/shuffle-sharding/shuffle-sharding-probability.png可以看到绝大多数租户对之间几乎没有实例交集这正是一个问题租户的故障影响被限制在很小范围的根本原因。为查询路径启用 shuffle shardingLoki 的查询路径query path包括 query-frontend、query-scheduler 与 querier。启用 shuffle sharding 的方法是设置-frontend.max-queriers-per-tenant取值为大于 0 且小于可用 querier 数量。该参数对应的 per-tenant 配置为max_queriers_per_tenant表示分配给该租户的 querier 数量。此选项仅在查询请求经过 query-frontend无论是否挂载 scheduler时才生效。在 pkg/validation/limits.go 中可以看到该参数的定义与语义值为0或高于可用 querier 数量时所有 querier 都会处理该租户的请求每个 frontend或使用 scheduler 时每个 scheduler都会为同一租户选择同一组 querier——前提是所有 querier 都连接到这些 frontend/scheduler。注意该选项只对连接 query-frontend/query-scheduler 的 querier 生效不适用于使用 downstream URL 的下游模式。两种配额方式固定数量与比例容量除了固定数量的max_queriers_per_tenant还可以使用比例方式-frontend.max-query-capacityper-tenant 配置max_query_capacity允许值范围为0.0到1.0表示租户可以使用可用查询容量的比例。例如设置为0.5则该租户可以使用一半的可用 querier在单机可扩展部署模式中即一半的read组件。两种配额同时配置时Loki 取两者计算结果中较小的 querier 数量min(max_queriers_per_tenant, ceil(querier_replicas * max_query_capacity))。两者都未配置时所有 querier 为该租户服务。从源码看pkg/validation/limits.gomax_query_capacity超出[0, 1]范围时会被自动收敛到边界值避免非法配置。配套的并行度与并发参数围绕查询路径还有两个相互关联的参数需要理解max_query_parallelismper-tenant 配置表示一次查询经过查询拆分query splitting与查询分片query sharding之后生成的所有子查询中可以同时调度执行的最大数量。其默认值为32见 pkg/validation/limits.go 中querier.max-query-parallelism的定义。-querier.max-concurrentper-querier 配置max_concurrent控制单个 querier 同时处理的查询数量上限querier 会把这个额度平均分配给它所连接的每个 query-frontend 或 query-scheduler。该参数决定的是单台 querier 的并发容量与max_queriers_per_tenant决定用多少台 querier形成互补——前者管深度、后者管广度。按租户覆盖配额max_queriers_per_tenant作为 per-tenant 限制可以在 limits overrides 配置中按租户覆盖全局默认值。这正是 pkg/validation/limits.go 中该字段被定义为 per-tenant 限制由Overrides.MaxQueriersPerTenant按userID查询的原因全局配置给默认值个别租户通过 overrides 单独调整例如给关键租户更多的 querier 配额。查询路径的 shuffle sharding 监控指标启用 shuffle sharding 后需要通过指标验证隔离效果并发现资源瓶颈。以下指标与 shuffle sharding 直接相关整体 query-scheduler 队列耗时loki_query_scheduler_queue_duration_seconds_*每个租户的 query-scheduler 队列长度loki_query_scheduler_queue_length带user标签每个租户的 query-scheduler 队列耗时可使用以下 LogQL 查询在 query-frontend 容器日志中提取max_over_time({cluster$cluster,containerquery-frontend, namespace$namespace} | metrics.go |logfmt | unwrap duration(queue_time) | __error__ [5m]) by (org_id)如果上述指标频繁出现尖峰通常意味着三种情况之一某个租户试图使用超出其配额的查询资源该租户可能需要调大max_queriers_per_tenantLoki 整体实例可能配置不足under provisioned。另一个有用的查询是统计每个租户实际使用了多少台 queriercount by (org_id) (sum by (org_id, pod) (count_over_time({job$namespace/querier, cluster$cluster} | metrics.go | logfmt [$__interval])))将实际使用量与配额对比可以快速发现配额过高或过低的租户是调优max_queriers_per_tenant的直接依据。在 Ruler 中启用 shuffle shardingRuler 同样可以把规则组rule group的评估分片到 ruler 实例子集上思路与查询路径一致每个租户的规则组只由部分 ruler 实例评估而不是全部。需要依次完成三步配置设置-ruler.enable-shardingenable_sharding为true开启基于 ring 的规则组分片。默认值为false即每个 ruler 实例评估所有规则。设置-ruler.sharding-strategysharding_strategy为shuffle-sharding默认值default只做规则组在所有 ruler 实例间的分片不使用 shuffle sharding。源码中支持的策略集合定义在 pkg/ruler/base/ruler.go即default与shuffle-sharding两种。设置 per-tenant 配置ruler_tenant_shard_size对应 flag-ruler.tenant-shard-size为租户规则组应分片到的 ruler 实例数量。该值为0时对该租户禁用 shuffle sharding其规则组改由全部 ruler 实例分片。例如以下配置为 Ruler 启用 shuffle sharding并默认给每个租户分配 3 台 ruler 实例的 shardruler: enable_sharding: true sharding_strategy: shuffle-sharding limits_config: ruler_tenant_shard_size: 3从源码看pkg/ruler/base/ruler.goRuler 在ShardingStrategyShuffle策略下会按租户查询其RulerTenantShardSizeshard size 为0即表示该租户关闭 shuffle sharding该 per-tenant 值的默认全局设置来自 pkg/validation/limits.go 中ruler.tenant-shard-size默认0。另外需要注意无论是否使用 shuffle sharding按规则组by-group分片的算法在 pkg/ruler/base/ruler.go 中默认始终启用。在 Index Gateway 中启用 shuffle shardingIndex Gateway 也可以把租户分片到部分实例上使每个租户的索引只由可用实例的子集提供服务。启用步骤如下设置-index-gateway.modemode为ring在ring模式下每台 index gateway 实例只负责一部分租户默认的simple模式下每台实例负责所有租户ring 分片不适用。设置 per-tenant 配置index_gateway_shard_size对应 flag-index-gateway.shard-size为租户索引应分片到的 index gateway 实例数量。与查询路径不同shard size 为0并不表示使用全部实例。如果全局index_gateway_shard_size为0Loki 会在启动时用 index gateway ring 的副本因子replication factor替代它——即已废弃的-replication-factor参数YAML 中为index_gateway.ring.replication_factor默认3。这意味着使用默认配置以ring模式运行的 index gateway已经默认把每个租户的索引分片到 3 台实例上。由于replication-factor已废弃建议显式设置index_gateway_shard_size而不是依赖它。例如以下配置为 Index Gateway 启用 shuffle sharding并默认给每个租户分配 5 台实例的 shardindex_gateway: mode: ring limits_config: index_gateway_shard_size: 5从源码看ring 模式下的分片实现在 pkg/indexgateway/shufflesharding.goGetShuffleShardingSubring先读取该租户的IndexGatewayShardSize若shardSize 0则回退到 ring 的副本因子随后调用ring.ShuffleShard(tenantID, shardSize)生成子 ringpkg/indexgateway/gateway_test.go 中的测试用mockLimits验证了不同 shard size 下的行为。该 per-tenant 值的默认全局设置位于 pkg/validation/limits.goindex-gateway.shard-size默认0。注意-index-gateway.max-capacityindex_gateway_max_capacity是一个独立的实验性设置用于将每个租户限制为可用 index gateway 实例的某个比例0.0到1.0。它只适用于simple模式——在simple模式下通过哈希租户 ID 选择实例子集而不是使用 ring在ring模式下它不生效因此不属于本文描述的ring模式 shuffle sharding 配置范畴。与之配套的-index-gateway.min-shuffle-shard-size默认3见 pkg/indexgateway/client.go定义了simple模式下 shuffle shard 的最小实例数置0可关闭该下限相关实现与测试见 pkg/indexgateway/client.go 的jumpHashShuffleSharding及 pkg/indexgateway/client_test.go。适用边界与最佳实践小结综合以上配置shuffle sharding 在 Loki 中的使用要点可以总结为按需启用、彼此独立查询路径、Ruler、Index Gateway 各自独立配置可以只对其中一个组件启用不影响其他组件。配额是隔离的抓手查询路径用max_queriers_per_tenant固定数量或max_query_capacity比例容量定义 shard 大小二者同时设置时取较小值Ruler 与 Index Gateway 则用各自的 per-tenant shard size 参数。别忘了故障恢复延迟反复崩溃的查询会破坏 shuffle sharding 的隔离效果建议在 query-frontend / query-scheduler 上配置querier-forget-delay如1m给崩溃实例一个退场缓冲期。用指标驱动调优通过loki_query_scheduler_queue_length、队列耗时以及每租户实际使用 querier 数等指标判断是配额不足、租户越界还是集群整体欠配再决定调整方向。Shuffle sharding 不是万能的例如无法阻止问题查询本身导致的崩溃但它以零额外资源成本为多租户 Loki 集群提供了一层可量化、可监控、可配置的故障隔离边界。【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/loki创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考