惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
博客园 - 司徒正美
TaoSecurity Blog
TaoSecurity Blog
Martin Fowler
Martin Fowler
T
Threat Research - Cisco Blogs
Blog — PlanetScale
Blog — PlanetScale
S
Secure Thoughts
博客园 - 三生石上(FineUI控件)
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
K
Kaspersky official blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Cisco Talos Blog
Cisco Talos Blog
H
Help Net Security
博客园 - 叶小钗
爱范儿
爱范儿
GbyAI
GbyAI
I
Intezer
M
MIT News - Artificial intelligence
Latest news
Latest news
Schneier on Security
Schneier on Security
T
Tor Project blog
Simon Willison's Weblog
Simon Willison's Weblog
I
InfoQ
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
C
CXSECURITY Database RSS Feed - CXSecurity.com
罗磊的独立博客
N
News and Events Feed by Topic
T
The Blog of Author Tim Ferriss
V2EX - 技术
V2EX - 技术
B
Blog
T
Tailwind CSS Blog
N
Netflix TechBlog - Medium
Security Latest
Security Latest
V
V2EX
F
Fortinet All Blogs
Forbes - Security
Forbes - Security
Application and Cybersecurity Blog
Application and Cybersecurity Blog
The Hacker News
The Hacker News
Scott Helme
Scott Helme
P
Privacy International News Feed
P
Palo Alto Networks Blog
H
Heimdal Security Blog
C
Cisco Blogs
T
The Exploit Database - CXSecurity.com
博客园 - Franky
酷 壳 – CoolShell
酷 壳 – CoolShell
G
Google Developers Blog
W
WeLiveSecurity
L
LINUX DO - 最新话题

博客园 - 左扬

VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 与其他 TSDB 对比:Prometheus/InfluxDB/Thanos/VM VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 写入吞吐/查询延迟/内存占用的数学模型 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 模块依赖图——从 import 语句看组件关系 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— Goroutine 池/atomic/零拷贝/sync.Pool VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 版本演进:1.146.0 LTS 重大更新解析 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 整体数据流:一条监控数据的完整生命周期 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 架构演进:从 TSDB 到 MergeSet 的设计取舍 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— Single-Node vs Cluster 模式本质区别 VictoriaMetrics 1.146.0 源码【左扬精讲】—— 开篇总览 Rust 专题【左扬精讲】—— 从语法到灵魂:Ownership、Borrowing 与多语言对比 kubernetes 源码【左扬精讲】—— kube-scheduler 启动流程源码分析 Rust 专题【左扬精讲】—— 选择控制语句、运算符与格式化输出 Rust 专题【左扬精讲】—— 所有权详解 Rust 专题【左扬精讲】—— 作用域详解 Rust 专题【左扬精讲】—— 变量、常量与标量数据类型 kubernetes 源码 / Operator 专题【左扬精讲】—— Deployment Controller 源码分析:从对象创建到滚动更新 kubernetes 源码 / Operator 专题【左扬精讲】—— Operator 开发中的 Webhook:从准入控制到生产部署 Kubernetes源码 / Operator 专题【左扬精讲】—— 实现 Application Controller:从零构建生产级控制器 Kubernetes 编程 / Operator 专题【左扬精讲】—— 定义 Application 资源 + 添加自定义新 API 完整指南 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 八):内部架构与核心组件 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 八): —— 从入口到调度的全链路源码剖析(k8s v1.36.1) DeepSeek-R1 多模态 R1 / VLM-GRPO【左扬精讲】—— Qwen2-VL 微调与视觉推理强化学习实战 DeepSeek-R1 工业 RAG + 微调混合系统【左扬精讲】—— R1 系列收官之作:从 Prompt → RAG → 微调 选型决策树 DeepSeek-R1 推理时扩展【左扬精讲】—— o1 / R1 慢思考机制:Self-Consistency + ToT + PRM 详解 DeepSeek-R1 端侧 LLM 工程【左扬精讲】—— llama.cpp 调参与 Apple Silicon / 国产 NPU / Android 端侧落地全攻略 DeepSeek-R1 vLLM + k8s 生产部署【左扬精讲】—— 从单卡 7B 到 100 卡 671B MoE 集群的工业化部署实战 DeepSeek-R1 评估与系统(Evaluation & Systems)【左扬精讲】—— 从 GSM8K/MMLU 到 LLM-as-Judge 的工业级评估方法论 DeepSeek-R1 模型训练与算法【左扬精讲】—— GRPO 进阶算法:DAPO / PRIME / RLVR / PRM 四大 2025 前沿改进 DeepSeek-R1 模型训练与算法【左扬精讲】—— 数据蒸馏:用 DeepSeek-R1-671B 生成 800K 高质量 CoT 样本的完整流水线 DeepSeek-R1 优化与微调实战【左扬精讲】—— 从 R1 强化学习新范式到 GRPO 微调一站式入门 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 七):自定义插件开发实战 —— 手写一个 Score 插件并注册到集群 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 六):Scheduler Profile 与多调度器 —— 如何配置多个 profile 实现多租户、Coordinated LeaderElection Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 五):SchedulingQueue 与 QueueingHint —— 三段队列的细节、v1.36 新引入的 QueueingHint 工作机制 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 四):抢占(Preemption)算法剖析 —— DefaultPreemption 如何选 victim、PodDisruptionBudget 如何约束 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 二):内置插件逐个精读 — NodeResourcesFit / NodeAffinity / TaintToleration / PodTopologySpread / VolumeBinding / InterPodAffinity Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):调度器内置插件 逐个精读 k8s 源码级精讲(二十六):调度器内置插件逐个精读 Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):调度器内置插件精读 — NodeResourcesFit / NodeAffinity / TaintToleration / PodTopologySpread / VolumeBinding / InterPodAffinity Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):Scheduling Framework 扩展点逐个源码拆解 Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):初识调度模型、内部架构与事件驱动机制 Kubernetes 编程 / client-go 专题【左扬精讲】—— 四种客户端:为什么、怎么选、怎么用 Kubernetes 编程 / Operator 专题【左扬精讲】—— controller-runtime、kubebuilder、operator-sdk 三大框架深度对比 Kubernetes 编程 / Operator 专题【左扬精讲】—— 深入理解 ManagedFields 字段冲突协调机制 Kubernetes 编程 / Operator 专题【左扬精讲】—— k8s Finalizers 深度解析:对象的生命周期与删除控制 Kubernetes 编程 / Operator 专题【左扬精讲】—— OwnerReference 字段与级联删除机制 Kubernetes 编程 / Operator 专题【左扬精讲】—— 深入学习 Server-Side Apply:managedFields 替代 last-applied-configuration 的演进方向 Kubernetes 编程 / Operator 专题【左扬精讲】—— k8s Annotations 与元数据体系(Operator 专题) Kubernetes 编程 / Operator 专题【左扬精讲】—— RESTMapper:把 Group / Version / Kind / Resource 四元组翻译成 REST 路径的"查字典"大师 Kubernetes 编程 / Operator 专题【左扬精讲】—— Converter 资源版本转换器 Kubernetes 编程 / Operator 专题【左扬精讲】—— Application 业务扩展:从单 Deployment 到多 Workload 的复合 Operator 演进 Kubernetes 编程 / Operator 专题【左扬精讲】—— OwnerReference / Finalizer / 准入控制:k8s 资源生命周期的三大支柱 Kubernetes 编程 / Operator 专题【左扬精讲】—— controller-runtime 框架内幕:从 Manager 到 Reconcile 的全栈拆解 Kubernetes 编程 / Operator 专题【左扬精讲】—— 生产级 Operator 最佳实践:并发安全、资源清理与高可用设计 Kubernetes 编程 / Operator 专题【左扬精讲】—— application-operator Reconcile 循环源码精讲:从 client-go Informer 到 workqueue 的全链路解剖 Kubernetes 编程 / Operator 专题【左扬精讲】—— 从零搭建一个 application-operator 新项目:脚手架、API 设计与基于原生 DeploymentStatus/ServiceStatus 的状态建模 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:生产级 Controller 实践:并发安全、资源清理与高可用设计 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析: Controller 调试与诊断工具:从日志分析到问题定位 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:DynamicClient 操作 CRD:无需代码生成的动态操作 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:控制器与 APIServer 完整交互流程:从 Watch 到缓存同步 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:错误处理与重试机制:WorkQueue 限速器详解 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Leader 选举机制:高可用控制器的必备技能 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Controller 开发模式完整实战 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:SharedInformerFactory 与等待缓存同步 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:从认证配置到 Deployment 操作 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:版本对应、架构组件与组件关系 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Informer 源码深度解析:从底层原理到实战应用 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Reflector 源码深度解析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:ListWatcher 源码深度解析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Indexer 与 ThreadSafeStore 核心原理与源码深度剖析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:DeltaFIFO 核心原理与源码深度剖析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:workqueue 核心原理与实战 Kubernetes 编程 / Operator 专题【左扬精讲】—— runtime.Codec 资源编解码:serializer 与 codec 差异、编解码数据结构、codec 核心调用链路 Kubernetes 编程 / Operator 专题【左扬精讲】—— Scheme 资源注册机制全解 Kubernetes 编程 / Operator 专题【左扬精讲】—— Kubernetes 自定义资源的内部版本与外部版本:从源码看版本定义机制 Kubernetes 编程 / Operator 专题【左扬精讲】—— Kubernetes 1.36.1 核心 API 数据结构全解 Kubernetes 编程 / Operator 专题【左扬精讲】—— Kubernetes 构建过程 【AIOPS】一文读懂LLM【左扬精讲】:从诞生到普及,解锁大语言模型的核心密码 【AIOPS】AI Agent 专题【左扬精讲】核心功能篇:MCP-VictoriaMetrics Hooks 源码精讲:Hooks 可观测性的无侵入式实现 【AIOPS】AI Agent 专题【左扬精讲】核心功能篇:MCP-VictoriaMetrics Golang 配置解析源码精讲 ——SRE 自定义 Agent 核心技巧 【AIOPS】AI Agent 专题【左扬精讲】核心功能篇:MCP-VictoriaMetrics Golang 并发模型解析 ——SRE 应对高并发采集的调优思路 【AIOPS】AI Agent 专题【左扬精讲】基础架构篇:MCP-VictoriaMetrics Golang 源码整体架构拆解 ——SRE 必懂的核心模块与数据流 OpenTelemetry 开发实战【左扬精讲】—— 云原生可观测体系构建与分布式追踪二次开发 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 7 —— 基于流量预测模型的智能弹性扩缩容 Operator 实战(AIOps 模型训练与智能扩容(下篇)—— 预测式弹性扩缩容 Operator 落地实现) Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 7 —— 基于流量预测模型的智能弹性扩缩容 Operator 实战(AIOps 模型训练与智能扩容(上篇)—— 时序预测模型构建与离线训练) Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 6 —— 基于运维专家知识库的智能故障诊断与排查 Operator 实战 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 5 —— 基于大语言模型(LLM)的实时日志流智能监测 Operator 实现 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 4 —— 基于 Operator 实现大模型私有化部署与管理 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 3(上篇)—— 面向 AI / 算力调度场景:GPU 竞价实例资源池统一调度管理 Operator 开发 Kubernetes编程 / Operator专题【左扬精讲】—— Operator 开发实战项目 2 —— 面向零售 / 电商潮汐流量难题:多云多集群数据中心级全链路弹性伸缩 DataCenter Scaler Operator 从 0 到 1 全链路开发 Kubernetes编程 / Operator专题【左扬精讲】—— 深入理解Kubebuilder注解:为什么Operator开发离不开这些特殊注释 Kubernetes编程 / Operator专题【左扬精讲】—— Operator 开发实战项目1 —— Applicaion Operator(通用应用生命周期管理 Operator 实战) Pod 镜像拉取失败?kubectl edit pods修改镜像地址的底层原理与实操 (该方法仅为临时应急方案,并非长期解决方案) Kubernetes编程/Operator专题精讲—— 理解控制器模式 —— 控制器模式的核心原理与实现逻辑(从原理到实践) 【AIOPS】AI Agent 专题【左扬精讲】模型微调实战:一站式平台 LLaMA-Factory 【AIOPS】AI Agent 专题【左扬精讲】基于 k8s+vLLM+Ray 分布式部署全指南:架构设计、资源调度与性能优化 【AIOPS】AI Agent专题【左扬精讲】非量化版DeepSeek分布式部署全指南:精度保障、显存规划与Ollama/vLLM选型 【AIOPS】AI Agent 专题【左扬精讲】零开发框架实现 ReAct Agent(Go SRE友好)
VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 多租户架构——accountID/projectID 与 tenant 隔离
左扬 · 2026-06-29 · via 博客园 - 左扬

VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 多租户架构——accountID/projectID 与 tenant 隔离

当你需要在同一个 VictoriaMetrics 实例中为多个客户或团队提供监控服务时,如何保证数据隔离?当某个租户的写入量突然飙升,如何防止它影响其他租户的查询性能?当你需要按租户进行资源配额管理时,VM 是如何实现的?

读完本篇,你应该能回答:VictoriaMetrics 的多租户模型是如何设计的?accountID 和 projectID 的关系是什么?Cluster 模式下如何实现 tenant 级别的资源隔离?写入和查询路径上 tenant 信息是如何传递的?

VictoriaMetrics Multi-Tenant accountID projectID Tenant隔离 Cluster模式 资源配额 v1.146.0

学习重点提示建议先通读全文,再重点回顾标注内容

重点掌握(必须)

  • TenantID 结构:accountID/projectID 的两段式设计(lib/storage/tenant.go
  • 写入路径 tenant 传递:vminsert 如何解析和转发 tenant 信息(app/vminsert/
  • 查询路径 tenant 隔离:vmselect 如何按 tenant 过滤数据(app/vmselect/
  • 存储层 tenant 路由:vmstorage 如何按 tenant 分区存储(lib/storage/

次重点(了解即可)

  • Enterprise 版本的配额管理特性
  • Single-Node 模式下的多租户支持
  • TenantID 的 URL 编码格式

文章目录

一、问题的起点:为什么需要多租户?

思考记忆提示多租户是云服务和企业级部署的基础——理解需求才能理解设计

  • 多租户可以降低运维成本:一个实例服务多个客户
  • 多租户可以实现资源隔离:防止某个租户影响其他租户
  • 多租户支持计费和配额管理:按租户统计资源使用
  • 面试高频提问:VM 的多租户是如何实现的?和 Prometheus 的 federation 有什么区别?

在 SaaS 监控服务、企业内部监控平台等场景中,多租户是刚需。一个监控平台需要为多个客户或团队提供服务,每个客户的数据必须严格隔离,防止"串租"现象。同时,平台运营方需要按租户进行资源配额管理和计费。

我的理解的意思是说

多租户架构可以想象成一个大型写字楼的多层租赁模式

没有多租户 = 独立别墅

每个客户自己盖一栋别墅(独立的 Prometheus 实例)。好处是完全隔离、互不影响;坏处是资源利用率低(每个别墅都要配电梯、发电机)、运维成本高(每个别墅都要独立管理)。

有多租户 = 写字楼分层租赁

一栋写字楼(VictoriaMetrics)分成多层(A公司租1-2层,B公司租3-4层)。每层有独立的大门和门禁(tenant 隔离),但共享电梯和供电系统(底层基础设施)。这样:

  • 资源利用率高:电梯、供电是共享的,不需要每个租户单独配置
  • 运维成本低:物业统一管理,租户只需要管理自己的楼层
  • 可以设置配额:A公司最多用2层,B公司最多用3层,防止某公司占满整栋楼
  • 可以单独计费:每层的物业费、水电费单独计算

VictoriaMetrics 的多租户就是这种"分层租赁"模式:每个 tenant(租户)有独立的命名空间(accountID/projectID),数据存储在独立的分区,但共享底层的存储引擎和网络基础设施。

1.1 多租户 vs 单租户

首先需要明确多租户和单租户的区别:

特性单租户模式多租户模式
部署架构 每个客户独立部署一个 VM 实例 多个客户共享一个 VM 实例
数据隔离 物理隔离(不同服务器) 逻辑隔离(tenant 命名空间)
运维成本 高(每个实例单独维护) 低(统一管理)
资源利用率 低(资源可能闲置) 高(资源共享)
适用场景 大型企业、关键业务 SaaS 服务、中小型客户

1.2 VM 多租户 vs Prometheus Federation

Prometheus 通过 Federation 实现多实例聚合,但这是"伪多租户"——每个 Prometheus 实例是独立的,数据聚合只是视图层面的,真正的数据隔离靠的是物理分离。

注意

Prometheus Federation 不是真正的多租户。每个 Prometheus 实例是独立的物理部署,只是通过 Federation 将数据聚合到中心节点。数据写入路径上没有 tenant 标识,无法实现真正的资源隔离和配额管理。VictoriaMetrics 的多租户是在数据写入路径上内嵌 tenant 信息,存储层天然支持 tenant 级别的隔离。

二、TenantID 架构:accountID/projectID 两段式设计

思考记忆提示TenantID 是 VM 多租户的核心——理解它的结构就理解了整个多租户模型

  • TenantID = accountID + projectID,两段式设计
  • accountID 是"账户",projectID 是"项目",可以理解为"公司 + 部门"
  • 面试高频提问:accountID 和 projectID 的关系是什么?为什么不用单一段式?

2.1 TenantID 的数据结构

VictoriaMetrics 的 TenantID 采用两段式设计:accountID/projectID。这个设计参考了 Google Cloud 的项目模型,提供了两层隔离能力。

// lib/storage/tenant.go(TenantID 数据结构)
// VictoriaMetrics v1.146.0

// TenantID 是两段式的:accountID/projectID
// 这种设计提供了两层隔离能力
type TenantID struct {
    // AccountID:账户 ID,类似于"公司编号"
    // 同一账户下的所有项目共享账户级别的配额
    AccountID uint32

    // ProjectID:项目 ID,类似于"部门编号"
    // 同一项目下的数据属于同一个业务线或服务
    ProjectID uint32
}

// 格式:accountID/projectID
// 例如:123/456 表示账户 123 的项目 456
// URL 中使用 : 表示,如 123:456

// TenantID 的字符串表示
func (tid *TenantID) String() string {
    return fmt.Sprintf("%d:%d", tid.AccountID, tid.ProjectID)
}

// 解析字符串为 TenantID
func ParseTenantID(s string) (*TenantID, error) {
    // 格式:accountID:projectID
    // 例如:123:456
    parts := strings.Split(s, ":")
    if len(parts) != 2 {
        return nil, fmt.Errorf("invalid tenant ID format: %s", s)
    }
    
    accountID, err := strconv.ParseUint(parts[0], 10, 32)
    if err != nil {
        return nil, fmt.Errorf("invalid accountID: %s", parts[0])
    }
    
    projectID, err := strconv.ParseUint(parts[1], 10, 32)
    if err != nil {
        return nil, fmt.Errorf("invalid projectID: %s", parts[1])
    }
    
    return &TenantID{
        AccountID: uint32(accountID),
        ProjectID: uint32(projectID),
    }, nil
}

// IsEmpty 检查 TenantID 是否为空
func (tid *TenantID) IsEmpty() bool {
    return tid.AccountID == 0 && tid.ProjectID == 0
}

设计精髓

两段式设计的优势在于提供了灵活的多级隔离能力:

  • 同项目隔离:相同 projectID、不同 accountID 的数据完全隔离
  • 账户级配额:可以按 accountID 设置资源配额,所有项目共享
  • 项目级配额:可以按 projectID 设置更细粒度的配额
  • 审计方便:可以按 accountID 聚合项目,生成账户级别的报表

如果使用单一段式(如只用一个 tenantID),就无法实现账户级别的聚合和配额管理。

2.2 TenantID 的 URL 编码

在 HTTP API 中,TenantID 通过 URL 路径传递,使用冒号(:)分隔 accountID 和 projectID。

┌─────────────────────────────────────────────────────────────────────────┐
│                    TenantID URL 编码格式                                   │
│                                                                          │
│  Single-Node 模式:                                                       │
│    /api/v1/write?tenant=123:456                                         │
│    /api/v1/query?tenant=123:456                                         │
│                                                                          │
│  Cluster 模式(通过 vminsert):                                           │
│    /insert/123:456/prometheus/api/v1/write                              │
│                                                                          │
│  Cluster 模式(通过 vmselect):                                          │
│    /select/123:456/prometheus/api/v1/query                             │
│                                                                          │
│  说明:                                                                   │
│    - 123 是 accountID                                                    │
│    - 456 是 projectID                                                    │
│    - 同一个 tenant 下的所有数据属于同一个命名空间                          │
└─────────────────────────────────────────────────────────────────────────┘

2.3 TenantID 的存储结构

TenantID 在存储层被编码到数据文件的路径和索引中,确保不同 tenant 的数据物理隔离。

// TenantID 在存储路径中的编码
// 路径格式:{accountID}/{projectID}/data

// 获取 tenant 的数据目录
func (tid *TenantID) GetDataDir() string {
    return fmt.Sprintf("data/%d/%d", tid.AccountID, tid.ProjectID)
}

// 示例:
// TenantID{AccountID: 123, ProjectID: 456}
// 数据目录:data/123/456/

我的理解的意思是说

TenantID 的两段式设计可以类比为手机号的"国家区号 + 本地号码"模式

单一段式 = 只有本地号码

假设手机号只有本地号码(如 12345678),不同城市的 12345678 是不同的号码,但无法按"城市"聚合。你可以说"给我所有 12345678 的号码",但无法说"给我北京的所有号码"。

两段式 = 国家区号 + 本地号码

实际手机号是 +86-10-12345678:

  • +86 是国家区号(类似 accountID)——"这是中国的号码"
  • 10 是城市区号(类似 projectID)——"这是北京的电话"
  • 12345678 是本地号码——"这是具体的某部电话"

这样设计的好处是:

  • 可以按国家聚合(+86 = 中国移动的所有号码)
  • 可以按城市聚合(北京的所有号码)
  • 可以精确到具体号码

TenantID 的两段式设计同理

  • 可以按 accountID 聚合("A 公司的所有项目")
  • 可以按 projectID 精确查询("A 公司的支付系统")
  • 可以实现账户级和项目级的配额管理

三、写入路径:vminsert 如何解析和转发 tenant

思考记忆提示vminsert 是 Cluster 模式的写入入口——理解它如何处理 tenant 是理解多租户写入的关键

  • vminsert 解析 URL 路径中的 tenant 信息
  • 根据 tenant 路由到对应的 vmstorage 节点
  • 面试高频提问:Cluster 模式下如何保证 tenant 数据路由的正确性?

3.1 vminsert 的 HTTP 处理

vminsert 是 VictoriaMetrics Cluster 模式的写入入口,接收来自 Prometheus、vmagent 等客户端的写入请求。

// app/vminsert/main.go(vminsert 主入口)
// VictoriaMetrics v1.146.0

// vminsert 处理写入请求的路由逻辑
func init() {
    // 注册 HTTP 路由
    // 格式:/insert/{tenant}/prometheus/api/v1/write
    http.HandleFunc("/insert/", func(w http.ResponseWriter, r *http.Request) {
        // 1. 解析 URL 路径,提取 tenant 信息
        tenant := extractTenantFromPath(r.URL.Path)
        
        // 2. 根据 tenant 路由到对应的 vmstorage
        storageNode := routeToStorage(tenant)
        
        // 3. 转发写入请求
        forwardToStorage(w, r, storageNode)
    })
}

// 从 URL 路径中提取 tenant 信息
// 路径格式:/insert/{tenant}/...
// 例如:/insert/123:456/prometheus/api/v1/write
func extractTenantFromPath(path string) *storage.TenantID {
    // 去掉 /insert/ 前缀
    path = strings.TrimPrefix(path, "/insert/")
    
    // 提取 tenant 部分(到下一个 / 之前)
    // 123:456/prometheus/api/v1/write -> 123:456
    parts := strings.SplitN(path, "/", 2)
    tenantStr := parts[0]
    
    // 解析为 TenantID
    return storage.ParseTenantID(tenantStr)
}

3.2 一致性哈希路由

vminsert 使用一致性哈希将 tenant 数据路由到 vmstorage 节点。同一个 tenant 的所有数据会被路由到同一个 vmstorage 节点,确保数据一致性。

// lib/storage/partition.go(一致性哈希路由)
// VictoriaMetrics v1.146.0

// 一致性哈希路由
// 同一个 tenant 的所有请求路由到同一个 vmstorage 节点
func routeToStorage(tenant *TenantID) *StorageNode {
    // 计算 tenant 的哈希值
    hash := hashTenant(tenant)
    
    // 通过一致性哈希环选择节点
    return consistentHash.GetNode(hash)
}

// 确保同一 tenant 的数据路由到同一节点
// 这样可以保证:
// 1. 数据写入的顺序性
// 2. 查询时不需要跨节点聚合
// 3. 简化副本管理

小贴士一致性哈希的优势

一致性哈希路由确保同一 tenant 的所有数据都路由到同一个 vmstorage 节点。这有几个好处:

  • 写入顺序保证:同一 tenant 的数据按时间顺序写入,不会乱序
  • 查询局部性:查询同一 tenant 的数据只需访问一个节点
  • 副本管理简单:同一 tenant 的主副本和从副本在同一节点组内

如果使用随机路由,同一 tenant 的数据会分散到多个节点,查询时需要跨节点聚合,延迟和复杂度都会增加。

3.3 写入流程完整链路

┌─────────────────────────────────────────────────────────────────────────┐
│                    Cluster 模式写入流程(多租户)                           │
│                                                                          │
│  1. Prometheus/vmagent                                                   │
│     │                                                                    │
│     │  POST /insert/123:456/prometheus/api/v1/write                      │
│     │  (数据 + TenantID)                                                 │
│     ▼                                                                    │
│  2. vminsert(负载均衡器入口)                                            │
│     │                                                                    │
│     │  - 解析 URL 路径,提取 tenant = 123:456                           │
│     │  - 计算 tenant 哈希                                                │
│     ▼                                                                    │
│  3. 一致性哈希环(routeToStorage)                                        │
│     │                                                                    │
│     │  tenant hash % N 个 vmstorage 节点                                 │
│     ▼                                                                    │
│  4. vmstorage(数据存储节点)                                            │
│     │                                                                    │
│     │  - 将 tenant 信息编码到数据路径                                     │
│     │  - 写入 data/123/456/ 目录                                         │
│     │  - 更新 tenant 级别的索引                                          │
│     ▼                                                                    │
│  5. 返回成功响应                                                         │
│                                                                          │
│  关键点:同一个 tenant 的所有数据 → 同一个 vmstorage 节点                  │
└─────────────────────────────────────────────────────────────────────────┘

四、查询路径:vmselect 如何按 tenant 过滤

思考记忆提示vmselect 是 Cluster 模式的查询入口——理解它如何处理 tenant 过滤是理解多租户查询的关键

  • vmselect 解析 URL 路径中的 tenant 信息
  • 将查询请求转发到对应 tenant 的 vmstorage 节点
  • 面试高频提问:查询时如何保证只能访问自己有权限的 tenant 数据?

4.1 vmselect 的 HTTP 处理

vmselect 是 VictoriaMetrics Cluster 模式的查询入口,接收来自 Grafana、API 客户端的查询请求。

// app/vmselect/main.go(vmselect 主入口)
// VictoriaMetrics v1.146.0

// vmselect 处理查询请求的路由逻辑
func init() {
    // 注册 HTTP 路由
    // 格式:/select/{tenant}/prometheus/api/v1/query
    http.HandleFunc("/select/", func(w http.ResponseWriter, r *http.Request) {
        // 1. 解析 URL 路径,提取 tenant 信息
        tenant := extractTenantFromPath(r.URL.Path)
        
        // 2. 验证请求者是否有权限访问该 tenant
        if !hasPermission(r, tenant) {
            http.Error(w, "access denied", http.StatusForbidden)
            return
        }
        
        // 3. 路由到对应的 vmstorage
        storageNode := routeToStorage(tenant)
        
        // 4. 转发查询请求
        forwardToStorage(w, r, storageNode)
    })
}

// 验证请求权限
// 实际实现可能包括:API Key 验证、JWT Token 验证、IP 白名单等
func hasPermission(r *http.Request, tenant *storage.TenantID) bool {
    // 从请求头中获取 API Key
    apiKey := r.Header.Get("X-API-Key")
    
    // 验证 API Key 是否有权限访问该 tenant
    return validateAPIKey(apiKey, tenant)
}

4.2 查询流程完整链路

┌─────────────────────────────────────────────────────────────────────────┐
│                    Cluster 模式查询流程(多租户)                           │
│                                                                          │
│  1. Grafana / API Client                                                 │
│     │                                                                    │
│     │  GET /select/123:456/prometheus/api/v1/query?query=...             │
│     │  Header: X-API-Key: xxx                                           │
│     ▼                                                                    │
│  2. vmselect(查询入口)                                                 │
│     │                                                                    │
│     │  - 解析 URL 路径,提取 tenant = 123:456                           │
│     │  - 验证 API Key 权限                                              │
│     │  - 检查请求速率是否超限                                            │
│     ▼                                                                    │
│  3. 权限验证通过                                                         │
│     │                                                                    │
│     │  - 计算 tenant 哈希                                              │
│     ▼                                                                    │
│  4. 一致性哈希环(routeToStorage)                                        │
│     │                                                                    │
│     │  tenant hash % N 个 vmstorage 节点                                 │
│     ▼                                                                    │
│  5. vmstorage(查询数据节点)                                            │
│     │                                                                    │
│     │  - 只扫描 data/123/456/ 目录下的数据                               │
│     │  - 其他 tenant 的数据完全不可见                                    │
│     ▼                                                                    │
│  6. 返回查询结果                                                         │
│                                                                          │
│  关键点:权限验证 + 数据路径隔离 = 完整的租户隔离                          │
└─────────────────────────────────────────────────────────────────────────┘

设计精髓

VM 多租户隔离的核心是数据路径隔离

  • 存储路径隔离:每个 tenant 的数据存储在独立的目录(data/{accountID}/{projectID}/)
  • 索引隔离:每个 tenant 有独立的 TSIDCache 和 MetricIDCache
  • 查询路径隔离:查询时只扫描对应 tenant 目录下的数据
  • 权限验证:API Key 和请求头验证确保只有授权用户能访问对应 tenant

这种设计的优势是:隔离是存储层的原生能力,不需要额外的查询过滤器。

五、存储路径:vmstorage 如何按 tenant 分区

思考记忆提示vmstorage 是数据存储的核心——理解它如何按 tenant 分区是理解多租户存储的关键

  • 同一个 tenant 的数据存储在同一目录结构
  • 不同 tenant 的数据在同一 vmstorage 节点内隔离
  • 面试高频提问:不同 tenant 的数据会混在一起吗?如何保证隔离?

5.1 数据目录结构

vmstorage 节点上的数据按 tenant 分区存储,每个 tenant 有独立的目录结构。

vmstorage 数据目录结构:
│
├── data/                          # 数据根目录
│   ├── 123/                       # AccountID = 123
│   │   ├── 456/                   # ProjectID = 456
│   │   │   ├── 20230601/          # 按日期分区
│   │   │   │   ├── small/         # 小 Part
│   │   │   │   └── big/           # 大 Part(合并后)
│   │   │   └── 20230602/
│   │   └── 789/                   # ProjectID = 789(同一账户的另一个项目)
│   └── 456/                       # AccountID = 456(另一个账户)
│       └── 111/
│           └── ...
│
├── indexdb/                       # 索引数据库(也按 tenant 分区)
│   ├── 123/
│   │   └── 456/
│   └── 456/
│       └── 111/
│
└── cache/                        # 缓存(也按 tenant 分区)
    ├── tsidcache/
    │   ├── 123_456/              # tenant 123:456 的缓存
    │   └── 123_789/
    └── metricidcache/
        ├── 123_456/
        └── 123_789/

5.2 Tenant 级别的资源统计

vmstorage 维护每个 tenant 的资源使用统计,用于配额管理和监控。

// lib/storage/tenant_stats.go(租户资源统计)
// VictoriaMetrics v1.146.0

// TenantStats 记录每个租户的资源使用情况
type TenantStats struct {
    TenantID TenantID

    // 写入统计
    RowsIngested    uint64  // 总写入行数
    SamplesIngested uint64  // 总写入样本数
    BytesIngested   uint64  // 总写入字节数

    // 存储统计
    StorageSize uint64  // 存储大小(字节)
    PartsCount  int     // Part 数量

    // 查询统计
    QueriesExecuted   uint64  // 查询执行次数
    QueryDuration    float64 // 查询耗时(秒)
    QueryErrors      uint64  // 查询错误数

    // 索引统计
    SeriesCount   uint64  // 时间序列数量
    LabelsCount   uint64  // 标签数量
    IndexSize     uint64  // 索引大小
}

// 更新租户统计
func (ts *TenantStats) Update(rows, samples, bytes uint64) {
    atomic.AddUint64(&ts.RowsIngested, rows)
    atomic.AddUint64(&ts.SamplesIngested, samples)
    atomic.AddUint64(&ts.BytesIngested, bytes)
}

// 获取当前统计快照
func (ts *TenantStats) Snapshot() TenantStatsSnapshot {
    return TenantStatsSnapshot{
        RowsIngested:    atomic.LoadUint64(&ts.RowsIngested),
        SamplesIngested: atomic.LoadUint64(&ts.SamplesIngested),
        BytesIngested:   atomic.LoadUint64(&ts.BytesIngested),
        StorageSize:     atomic.LoadUint64(&ts.StorageSize),
        SeriesCount:     atomic.LoadUint64(&ts.SeriesCount),
    }
}

我的理解的意思是说

数据按 tenant 分区存储可以类比为图书馆的"分类书架系统"

不分区 = 所有书混在一起

想象一个图书馆,所有书都堆在一个大厅里,没有任何分类。你要找《西游记》,得把大厅里的所有书翻一遍。

按 Tenant 分区 = 分类书架

实际上图书馆的做法是:

  • 按楼层分区(每个租户一层)—— 这是 AccountID 级别
  • 每个楼层按区域分类(每个项目一个区域)—— 这是 ProjectID 级别
  • 每个区域内部再按日期分类(每天一个新书架)—— 这是日期分区

这样设计的好处是:

  • 查找快:知道书在哪一层(AccountID)和哪个区域(ProjectID)就能快速定位
  • 隔离好:A 公司的书不会出现在 B 公司的区域
  • 统计方便:每个区域的书架满了,就知道该租户快用完配额了

VictoriaMetrics 的 tenant 分区同理:每个 tenant 有独立的目录结构,数据物理隔离,查询时只需扫描对应目录。

六、Single-Node 模式下的多租户支持

思考记忆提示Single-Node 模式也支持多租户——只是没有 Cluster 模式的分布式能力

  • Single-Node 模式下多租户通过URL 参数传递 tenant
  • 数据在本地按 tenant 目录隔离存储
  • 面试高频提问:Single-Node 模式支持多少个租户?有没有限制?

6.1 Single-Node 的多租户 API

Single-Node 模式也支持通过 tenant 参数指定租户,数据在本地按 tenant 隔离存储。

Single-Node 模式多租户 API:

写入:
  POST /api/v1/write?tenant=123:456
  Body: metric_name{label="value"} value timestamp

查询:
  GET /api/v1/query?tenant=123:456&query=up
  GET /api/v1/query_range?tenant=123:456&query=up&start=...&end=...

说明:
  - tenant 参数格式:accountID:projectID
  - 如果不指定 tenant,默认为 0:0(匿名租户)
  - 同一个 tenant 的数据存储在同一目录下

6.2 数据存储位置

Single-Node 模式下,不指定 tenant 的数据存储在根目录,指定 tenant 的数据存储在对应的 tenant 目录下。

Single-Node 数据目录:

不指定 tenant(匿名租户 0:0):
  {dataPath}/
    data/
      0/
        0/
          ...

指定 tenant(123:456):
  {dataPath}/
    data/
      123/
        456/
          ...

说明:
  - Single-Node 模式下,多个 tenant 的数据可能在同一个 vmstorage 节点
  - 不同 tenant 的数据通过目录隔离
  - 查询时通过 tenant 参数过滤

注意

Single-Node 模式的多租户支持适合中小规模场景。如果租户数量过多(如超过 1000 个),或者某些租户的写入量很大,建议使用 Cluster 模式,将不同租户分布到不同的 vmstorage 节点,实现更好的资源隔离。

七、Enterprise 版本的配额管理

思考记忆提示Enterprise 版本提供更强大的多租户管理能力——包括资源配额和访问控制

  • 资源配额:限制每个租户的写入速率、存储大小、时间序列数量
  • 访问控制:基于 API Key 的细粒度权限管理
  • 配额预检:写入前检查配额,超限则拒绝
  • 面试高频提问:Enterprise 版本的配额管理是如何实现的?

7.1 资源配额类型

VictoriaMetrics Enterprise 版本支持多种类型的资源配额:

配额类型说明作用
ingestionRate 写入速率(samples/s) 防止租户写入量过大影响其他租户
storageSize 存储大小(字节) 限制租户使用的磁盘空间
seriesCount 时间序列数量 限制租户创建的指标数量,防止高基数
queriesPerSecond 查询速率(queries/s) 限制租户的查询频率
queryResolution 查询分辨率(最大时间范围) 防止租户查询过大时间范围

7.2 配额检查流程

// Enterprise 版本的配额检查
// lib/storage/quota.go

// 检查写入配额
func CheckWriteQuota(tenant *TenantID, rows, samples uint64) error {
    quota := getTenantQuota(tenant)

    // 检查写入速率
    if !quota.allowIngestion(samples) {
        return ErrQuotaExceeded{
            Type:    "ingestionRate",
            Tenant:  tenant,
            Message: "写入速率超限",
        }
    }

    // 检查存储大小
    if !quota.allowStorageSize(estimatedSize) {
        return ErrQuotaExceeded{
            Type:    "storageSize",
            Tenant:  tenant,
            Message: "存储空间超限",
        }
    }

    // 检查时间序列数量
    if !quota.allowSeriesCount(newSeriesCount) {
        return ErrQuotaExceeded{
            Type:    "seriesCount",
            Tenant:  tenant,
            Message: "时间序列数量超限",
        }
    }

    return nil
}

// 检查查询配额
func CheckQueryQuota(tenant *TenantID) error {
    quota := getTenantQuota(tenant)

    // 检查查询速率
    if !quota.allowQuery() {
        return ErrQuotaExceeded{
            Type:    "queriesPerSecond",
            Tenant:  tenant,
            Message: "查询频率超限",
        }
    }

    return nil
}

小贴士配额预检的优势

Enterprise 版本的配额检查在写入前进行(预检),而不是写入后才发现超限。这有几个好处:

  • 快速失败:写入请求可以立即被拒绝,不需要等到存储时才报错
  • 节省资源:避免写入后再删除的浪费
  • 用户体验:客户端可以及时收到配额超限的错误,调整写入策略

八、FAQ:常见疑问

思考记忆提示FAQ 是全篇的"临考前速背"模块,20 组覆盖全链路

  • Q1-Q5 围绕 TenantID 结构:accountID/projectID 设计原理
  • Q6-Q10 围绕写入路径:vminsert tenant 解析和路由
  • Q11-Q15 围绕查询路径:vmselect tenant 过滤和权限验证
  • Q16-Q20 围绕存储和配额:vmstorage tenant 分区和 Enterprise 配额

Q1. accountID 和 projectID 的关系是什么?

accountID 是账户级别,projectID 是项目级别,类似于"公司 + 部门"的关系。同一 accountID 下的所有 projectID 共享账户级别的配额。accountID/projectID 的组合唯一确定一个租户。例如,accountID=123, projectID=456 表示"账户 123 的项目 456"。

Q2. 为什么使用两段式设计而不是单一段式?

两段式设计支持账户级和项目级的双重隔离和配额管理。单一段式(如只有一个 tenantID)无法实现账户级别的聚合。例如,云服务商可以按账户设置总配额,账户内的多个项目共享该配额;如果只有一个 tenantID,就无法区分"账户 A 的总配额"和"账户 B 的总配额"。

Q3. TenantID 的最大值是多少?能支持多少租户?

accountID 和 projectID 都是 uint32,最大值约 42 亿。理论上可以支持数十亿个租户。但实际上,租户数量受限于 vmstorage 节点的存储能力和网络带宽。建议单个 vmstorage 节点支持 1000-10000 个活跃租户。

Q4. Cluster 模式下如何保证 tenant 数据路由的一致性?

通过一致性哈希(consistent hashing)保证同一个 tenant 的所有请求路由到同一个 vmstorage 节点。vminsert 在接收到写入请求后,计算 tenantID 的哈希值,然后在一致性哈希环上查找对应的节点。由于哈希值是确定的,同一个 tenant 的所有请求都会路由到同一个节点。

Q5. 如果 vmstorage 节点故障,同一个 tenant 的数据会丢失吗?

如果启用了副本机制,数据不会丢失,但可能短暂不可用。Enterprise 版本支持 vmstorage 节点的副本复制。同一个 tenant 的主副本和从副本分布在不同的节点上,当主节点故障时,从节点可以接管服务,实现高可用。

Q6. vminsert 如何解析 URL 中的 tenant 信息?

通过解析 URL 路径,提取 /insert/{tenant}/ 后面的 tenant 字符串。路径格式为 /insert/{accountID:projectID}/prometheus/api/v1/write。vminsert 从路径中提取 tenant 字符串(如 123:456),然后解析为 TenantID 结构。

Q7. 写入时如果不指定 tenant,数据会存储在哪里?

如果不指定 tenant,默认为 0:0(匿名租户)。数据会存储在 data/0/0/ 目录下。在多租户环境中,建议始终明确指定 tenant,避免数据混淆。

Q8. 如何在 Prometheus 配置中指定 tenant?

通过 remote_write URL 中的 tenant 参数指定。配置示例:remote_write: url: "http://vminsert:8480/insert/123:456/prometheus/api/v1/write"。多个 Prometheus 实例可以配置不同的 tenant,实现数据隔离。

Q9. vmselect 如何验证请求者是否有权限访问某个 tenant?

通过 API Key 或 JWT Token 验证请求权限。Enterprise 版本支持基于 API Key 的权限管理。每个 API Key 绑定到特定的 tenant,请求时需要携带对应的 API Key。vmselect 会验证 API Key 是否有权限访问请求的 tenant。

Q10. 查询时如果指定了错误的 tenant,会返回什么错误?

如果 API Key 没有权限访问该 tenant,返回 403 Forbidden。错误消息类似于 "access denied for tenant 123:456"。客户端需要确保使用正确的 API Key。

Q11. 同一 vmstorage 节点上不同 tenant 的数据会相互影响吗?

正常情况下不会,因为数据路径完全隔离。每个 tenant 的数据存储在独立的目录结构下(data/{accountID}/{projectID}/),查询时只扫描对应目录。如果某个 tenant 的写入量突然飙升,可能影响同一节点的 CPU 和磁盘 I/O,但这是资源竞争问题,不是数据隔离问题。

Q12. 如何监控每个 tenant 的资源使用情况?

通过 /metrics 端点查看 tenant 级别的指标。关键指标包括:vm_tenant_rows_ingested_total(写入行数)、vm_tenant_samples_ingested_total(写入样本数)、vm_tenant_storage_size_bytes(存储大小)、vm_tenant_series_count(时间序列数量)。

Q13. Single-Node 模式和 Cluster 模式的多租户有什么区别?

Single-Node 模式在本地按 tenant 隔离存储,Cluster 模式将不同 tenant 分布到不同的 vmstorage 节点。Single-Node 适合中小规模场景,多个 tenant 共享本地资源。Cluster 模式适合大规模场景,可以按 tenant 进行水平扩展,实现更好的资源隔离。

Q14. Enterprise 版本的配额管理有哪些类型?

支持五种配额类型:ingestionRate(写入速率)、storageSize(存储大小)、seriesCount(时间序列数量)、queriesPerSecond(查询速率)、queryResolution(查询分辨率)。这些配额可以在账户级别或项目级别设置,实现灵活的配额管理策略。

Q15. 配额超限后会发生什么?

配额超限后,写入请求会被拒绝,返回配额超限错误。错误消息包含超限的配额类型(如 "ingestionRate exceeded")。客户端需要根据错误类型调整写入策略(如降低写入速率、删除过期数据释放空间)。

Q16. 如何为新租户创建 API Key?

通过 vmauth 或 Enterprise 管理界面创建 API Key。Enterprise 版本提供了 CLI 工具和 Web UI 来管理 API Key。每个 API Key 绑定到特定的 tenant 和权限级别(如只读、读写、管理员)。

Q17. 可以在运行时修改租户的配额吗?

可以,Enterprise 版本支持运行时动态调整配额。通过管理 API 或 CLI 工具可以实时修改租户的配额,新配额立即生效,不需要重启服务。

Q18. 多租户环境下如何进行故障排查?

通过 tenant 参数过滤日志和指标,定位问题租户。所有日志和指标都包含 tenant 标签(accountID:projectID)。使用 /internal/tenants 接口可以查看所有租户的概览,使用 /internal/tenant/{tenantID}/status 接口可以查看特定租户的详细状态。

Q19. 如何迁移现有数据到多租户架构?

可以使用 vmctl 工具将现有数据迁移到指定 tenant。迁移时指定 --tenant 参数,数据会被写入目标 tenant 的存储路径。迁移完成后,原有数据和新数据在逻辑上完全隔离。

Q20. 多租户架构下如何保证数据安全?

通过多层安全机制保证数据安全:存储层目录隔离、API 层权限验证、网络层 TLS 加密。存储层通过目录结构隔离不同 tenant 的数据,即使磁盘被直接访问也无法读取其他 tenant 的数据。API 层通过 API Key 或 JWT 验证权限。网络层通过 TLS 加密传输数据。

全篇必记总纲

VictoriaMetrics 多租户架构的核心是两段式 TenantID(accountID/projectID)+ 一致性哈希路由 + 目录级数据隔离:写入时 vminsert 解析 tenant 并路由到对应 vmstorage,查询时 vmselect 验证权限并只扫描对应目录,存储层通过 data/{accountID}/{projectID}/ 目录结构实现物理隔离。Enterprise 版本额外提供配额管理和细粒度权限控制。

九、Roadmap:后续预告

本篇覆盖了 VictoriaMetrics 的多租户架构设计,但还有很多细节尚未展开:

  • #07 Go 工程实践:Goroutine 池/atomic/零拷贝/sync.Pool——理解 VM 的高性能实现
  • #08 模块依赖图:从 import 语句看组件关系——理解 VM 的整体架构
  • #09 性能模型:写入吞吐/查询延迟/内存占用的数学模型——理解 VM 的性能上限
  • #10 与其他 TSDB 对比:Prometheus/InfluxDB/Thanos/VM——理解 VM 在竞品中的定位
  • #153 多租户隔离:accountID/projectID 资源配额强制——Enterprise 级别的配额管理

本文参考与源码链接:
  • lib/storage/tenant.go · TenantID 数据结构
  • app/vminsert/ · 写入入口
  • app/vmselect/ · 查询入口
  • lib/storage/ · 存储核心层
  • VictoriaMetrics Cluster 架构文档