3. 可观测性三大支柱:日志、指标、链路追踪在多租户场景下的特殊要求

在单租户环境中,可观测性三大支柱(Logging、Metrics、Tracing)的设计相对直接:数据归属于单一主体,存储与查询无需考虑隔离。然而,在多租户架构中,每一根支柱都必须面对数据隔离、资源争抢、租户基数膨胀带来的新挑战。本章从三大支柱各自的本质出发,逐一拆解多租户场景下的特殊约束与设计要点。

3.1 日志(Logging):租户级隔离与成本控制

日志是多租户可观测性中数据量最大、写入最频繁的支柱。多租户场景下,日志系统需要同时满足写入隔离、查询过滤与存储成本分摊三个核心要求。

核心挑战: 一个租户的日志洪流不能影响其他租户的写入可用性;查询时必须精确限定租户范围,避免跨租户数据泄露;存储成本需按租户计量。

3.1.1 日志写入的租户路由策略

日志采集端必须将租户标识(Tenant ID)作为元数据注入每条日志记录。常见的注入方式包括:

  • Sidecar 注入: 在 Pod 级别通过 Sidecar 容器自动添加租户标签,对应用代码无侵入。
  • 日志代理重写: 在 Fluentd / Logstash 等采集代理中,根据来源命名空间或服务名动态映射租户 ID。
  • 应用层显式传递: 业务代码在日志上下文(如 MDC)中设置 tenantId,适合需要精细权限控制的场景。
策略 侵入性 灵活性 推荐场景
Sidecar 注入 低 中 Kubernetes 原生多租户
代理重写 低 高 异构基础设施
应用层传递 高 高 需要细粒度权限审计

3.1.2 存储层的租户隔离模型

日志存储层通常采用以下三种隔离模型之一:

  • 索引级隔离: 每个租户使用独立的日志索引(如 Elasticsearch 的 tenant-xxx-2025-03),查询时直接限定索引范围。隔离性高,但索引数量随租户线性增长,集群开销大。
  • 文档级隔离: 所有租户共享同一索引,每条日志文档携带 tenant_id 字段,查询时通过过滤器强制限定。成本低,但需要确保查询层不会遗漏过滤条件。
  • 分片级隔离: 将特定租户的日志路由到固定分片,结合索引级与文档级的优点,适合超大规模租户。
实践建议: 对于 SaaS 类多租户系统,优先采用索引级隔离 + 冷热分层。热索引按天滚动,冷索引按租户归档,既保证写入性能,又便于成本核算。

3.2 指标(Metrics):高基数租户标签与聚合降噪

指标系统在多租户场景下最突出的问题是标签基数爆炸。每个租户的指标都携带 tenant_id 标签,当租户数量达到数千甚至数万时,Prometheus 等时序数据库的标签索引将面临严重压力。

避坑提示: 不要将 tenant_id 作为普通标签直接加入所有指标。一个拥有 10,000 个租户的系统,每个指标多一个 tenant_id 标签,会导致时间序列数量瞬间膨胀 10,000 倍,直接击穿存储层。

3.2.1 租户标签的降维策略

解决高基数问题的常用方法包括:

  • 租户聚合指标: 在采集端预先按租户聚合,只保留每个租户的汇总值(如 P99 延迟、错误率),丢弃原始细粒度标签。
  • 双层指标架构: 第一层为全局无租户标签的集群级指标(用于容量规划),第二层为按需启用的租户级指标(用于租户排障)。
  • 租户分桶: 将租户 ID 通过哈希映射到有限数量的桶(如 256 个),指标携带 bucket_id 而非 tenant_id,大幅降低基数。
双层指标架构:租户级与集群级分离 应用实例 应用实例 应用实例 采集代理(预聚合 + 租户标签降维) 集群级指标(无租户标签) 用于容量规划与全局告警 租户级指标(按需启用) 用于租户排障与 SLA 监控 关键设计:租户级指标仅在排障时临时启用,默认只采集集群级指标以控制基数

3.2.2 租户级告警的隔离与降噪

多租户指标告警需要避免“告警风暴”与“跨租户误报”。设计要点包括:

  • 告警规则租户化: 每个租户拥有独立的告警规则配置,规则中自动注入 tenant_id 过滤条件。
  • 租户级静默: 允许租户管理员在维护窗口期临时静默本租户的告警,不影响其他租户。
  • 聚合告警: 当同一故障影响多个租户时,生成一条聚合告警而非 N 条独立告警。

3.3 链路追踪(Tracing):上下文传播与采样策略

链路追踪在多租户场景下的核心矛盾在于:追踪数据需要跨服务传播租户上下文,同时采样策略必须兼顾租户维度的完整性。

3.3.1 租户上下文在 Trace 中的传播

在分布式调用链中,租户 ID 必须作为 Baggage 项随 Trace Context 一起传播。常见的传播方式:

  • W3C Trace Context 扩展: 在 tracestate 字段中携带 tenant_id,对 OpenTelemetry 标准兼容性好。
  • HTTP Header 透传: 自定义 Header(如 X-Tenant-Id)在服务间传递,适合非 OpenTelemetry 场景。
  • 消息队列 Header 注入: 在 Kafka / RabbitMQ 消息的 Header 中携带租户 ID,确保异步调用链不丢失上下文。
// OpenTelemetry 中通过 Baggage 传播租户 ID 示例
import io.opentelemetry.api.baggage.Baggage;
import io.opentelemetry.api.trace.Span;

// 在入口服务设置租户上下文
Baggage current = Baggage.current().toBuilder()
    .put("tenant_id", "tenant-abc-123")
    .build();
current.makeCurrent();

// 在后续服务中提取
String tenantId = Baggage.current().getEntryValue("tenant_id");
Span.current().setAttribute("tenant.id", tenantId);

3.3.2 租户感知的采样策略

传统的固定比例采样在多租户场景下会导致小租户的追踪数据被淹没。推荐采用租户感知采样:

采样策略 原理 适用场景
租户配额采样 每个租户独立分配采样配额(如每分钟 100 条 Trace) 租户规模差异大,需保证每个租户都有数据
头部采样 + 租户标签 在入口处按租户 ID 哈希决定是否采样,保证同一租户的调用链完整 需要完整追踪单次请求全链路
尾部采样 + 租户过滤 先全量采集,再根据租户规则(如仅保留错误 Trace)进行过滤 存储成本充足,需要高保真排障数据
最佳实践: 对于 SLA 要求高的租户(如付费企业租户),采用头部采样 + 100% 采样率;对于免费租户,采用租户配额采样,每个租户每小时仅保留 10 条完整 Trace。这样既保障了核心租户的可观测性,又控制了总体存储成本。

3.4 三大支柱的租户数据关联

在多租户排障场景中,日志、指标、链路追踪需要能够围绕同一个租户 ID 进行关联分析。设计统一的租户数据模型是关键:

多租户可观测性数据关联模型 租户 ID 日志 (tenant_id) 指标 (tenant_id) 链路 (tenant_id) 统一租户 ID 作为三大支柱的关联键,支持从指标异常下钻到日志,再从日志关联到链路

3.4.1 关联查询的租户权限控制

当用户从 Grafana 或 Kibana 发起跨支柱查询时,系统必须确保:

  • 查询范围自动限定: 用户登录后,其角色绑定的租户 ID 自动注入所有查询语句的过滤条件。
  • 跨数据源统一鉴权: 日志、指标、追踪三个数据源共享同一套租户权限模型,避免出现“指标能看到但日志看不到”的权限漏洞。
  • 审计日志记录: 所有跨租户查询操作(如管理员排障)必须记录操作人、查询时间、涉及的租户 ID。
安全红线: 禁止在查询 API 中暴露“不传 tenant_id 则查询全部”的默认行为。多租户系统的查询接口必须强制要求租户 ID,即使管理员角色也需要显式指定租户范围。

3.5 本章小结