2. 可观测性三大支柱:日志、指标、链路追踪在多租户场景下的基础概念
在多租户系统中,可观测性的三大支柱——日志(Logging)、指标(Metrics)和链路追踪(Tracing)——不再是独立的工具,而是必须与租户隔离策略深度绑定的数据模型。每个支柱都需要在采集、存储、查询和展示层面嵌入租户上下文,否则跨租户的数据泄露或故障边界模糊将不可避免。
2.1 三大支柱的核心定义与租户隔离挑战
下表从数据特征、隔离粒度、常见陷阱三个维度对比三大支柱在多租户环境下的基础差异:
| 支柱 | 数据特征 | 租户隔离粒度 | 常见隔离陷阱 |
|---|---|---|---|
| 日志 | 离散、非结构化或半结构化文本,记录事件详情 | 每条日志记录必须携带 tenant_id 标签 |
日志聚合时丢失租户字段;全文索引跨租户泄露 |
| 指标 | 时序数值,聚合后反映系统状态 | 指标标签(Label)中注入租户维度 | 全局聚合指标掩盖单租户异常;高基数标签导致存储爆炸 |
| 链路追踪 | 请求级调用链,包含 Span 上下文 | Span 中注入 tenant_id 上下文,传播至下游服务 |
异步消息丢失租户上下文;跨服务边界未透传租户标识 |
关键原则: 在多租户可观测性中,租户标识必须作为一等公民(First-Class Citizen)嵌入数据模型,而非事后通过正则或模糊匹配提取。
2.2 日志(Logging):结构化与租户标签
多租户日志的核心要求是结构化。非结构化日志无法可靠地按租户过滤,而结构化日志通过键值对(Key-Value)或 JSON 格式,将 tenant_id 作为必填字段。
// 结构化日志示例(JSON 格式)
{
"timestamp": "2025-03-21T10:15:30.123Z",
"level": "ERROR",
"tenant_id": "tenant-abc-123",
"service": "payment-service",
"trace_id": "trace-xyz-789",
"message": "Payment gateway timeout",
"error_code": "PAY_TIMEOUT",
"duration_ms": 5002
}
日志隔离的三大基础策略:
- 采集层隔离: 每个租户的日志写入独立索引(如 Elasticsearch 的
logs-{tenant_id}-yyyy-MM-dd),避免跨租户查询性能干扰。 - 存储层隔离: 使用租户级保留策略(Retention Policy),高 SLA 租户日志保留 90 天,普通租户保留 30 天。
- 查询层隔离: 查询接口强制校验用户权限,仅返回其所属租户的日志记录。
避坑提示: 避免在日志中打印租户敏感数据(如 API Key、个人身份信息)。即使日志按租户隔离,存储介质的安全等级仍需统一。
2.3 指标(Metrics):标签基数与租户维度
指标在多租户场景下最大的挑战是标签基数(Cardinality)。每个租户作为一个标签值加入指标,当租户数量达到数千甚至数万时,时序数据库的索引和存储压力会急剧上升。
推荐的指标隔离模型:
- 租户级指标: 使用
tenant_id标签,仅记录关键业务指标(如请求量、错误率、P99 延迟),避免记录细粒度调试指标。 - 全局聚合指标: 去除
tenant_id标签,用于基础设施监控(如 CPU、内存、网络吞吐),不暴露租户维度。 - 租户分组指标: 按租户等级(Tier)或业务线聚合,降低标签基数。
# Prometheus 指标示例(带租户标签)
# 租户级请求量
http_requests_total{tenant_id="tenant-abc-123", status="200"} 1024
http_requests_total{tenant_id="tenant-def-456", status="200"} 512
# 全局聚合(无租户标签)
node_cpu_seconds_total{mode="user"} 3600
高基数警告: 不要将
user_id、request_id 等极高基数字段作为指标标签。这会导致时序数据库性能崩溃。用户级数据应通过日志或追踪处理。
2.4 链路追踪(Tracing):上下文传播与租户透传
链路追踪在多租户系统中的核心任务是跨服务边界透传租户上下文。一个请求可能经过 API 网关、认证服务、业务服务、数据服务等多个微服务,每个服务都必须从上游 Span 中提取 tenant_id 并注入到下游 Span。
租户上下文传播的标准做法:
- W3C Trace Context 扩展: 在
tracestate字段中携带tenant_id,而非自定义 HTTP Header。 - 异步消息透传: 在消息队列(如 Kafka、RabbitMQ)的消息头中注入租户上下文,消费者端自动恢复。
- Span 标签注入: 每个 Span 必须包含
tenant_id标签,用于后续按租户过滤追踪数据。
// OpenTelemetry Span 示例(Java)
Span span = tracer.spanBuilder("processPayment")
.setAttribute("tenant_id", "tenant-abc-123")
.setAttribute("service.name", "payment-service")
.startSpan();
故障排查价值: 当租户 A 报告请求超时,通过
tenant_id 过滤追踪数据,可以快速定位是租户 A 专属的数据库连接池耗尽,还是全局性的网络抖动。
2.5 三大支柱的协同关系:多租户故障排查视图
下图展示了日志、指标、链路追踪在多租户故障排查中的协同工作流:
2.6 三大支柱的租户隔离矩阵
在实际架构中,三大支柱的隔离策略需要组合使用。下表总结了不同场景下的推荐隔离方案:
| 场景 | 日志策略 | 指标策略 | 链路追踪策略 |
|---|---|---|---|
| 金融级租户(高隔离) | 独立日志索引 + 独立存储集群 | 独立指标实例 + 租户级 Dashboard | 独立追踪后端 + 租户级采样率 |
| 标准 SaaS 租户 | 共享集群 + 按 tenant_id 路由索引 | 共享实例 + 租户标签 + 降采样 | 共享后端 + 租户上下文过滤 |
| 免费/试用租户 | 共享索引 + 短保留期(7天) | 聚合指标 + 无租户级标签 | 头部采样 + 不保留全量追踪 |
架构建议: 不要追求所有租户使用同一隔离级别。根据租户 SLA 和付费等级,动态调整三大支柱的隔离深度,在成本与可观测性之间取得平衡。