


















当你需要在同一个 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 编码格式
文章目录
思考记忆提示 — 多租户是云服务和企业级部署的基础——理解需求才能理解设计
在 SaaS 监控服务、企业内部监控平台等场景中,多租户是刚需。一个监控平台需要为多个客户或团队提供服务,每个客户的数据必须严格隔离,防止"串租"现象。同时,平台运营方需要按租户进行资源配额管理和计费。
我的理解的意思是说
多租户架构可以想象成一个大型写字楼的多层租赁模式:
没有多租户 = 独立别墅
每个客户自己盖一栋别墅(独立的 Prometheus 实例)。好处是完全隔离、互不影响;坏处是资源利用率低(每个别墅都要配电梯、发电机)、运维成本高(每个别墅都要独立管理)。
有多租户 = 写字楼分层租赁
一栋写字楼(VictoriaMetrics)分成多层(A公司租1-2层,B公司租3-4层)。每层有独立的大门和门禁(tenant 隔离),但共享电梯和供电系统(底层基础设施)。这样:
VictoriaMetrics 的多租户就是这种"分层租赁"模式:每个 tenant(租户)有独立的命名空间(accountID/projectID),数据存储在独立的分区,但共享底层的存储引擎和网络基础设施。
首先需要明确多租户和单租户的区别:
| 特性 | 单租户模式 | 多租户模式 |
|---|---|---|
| 部署架构 | 每个客户独立部署一个 VM 实例 | 多个客户共享一个 VM 实例 |
| 数据隔离 | 物理隔离(不同服务器) | 逻辑隔离(tenant 命名空间) |
| 运维成本 | 高(每个实例单独维护) | 低(统一管理) |
| 资源利用率 | 低(资源可能闲置) | 高(资源共享) |
| 适用场景 | 大型企业、关键业务 | SaaS 服务、中小型客户 |
Prometheus 通过 Federation 实现多实例聚合,但这是"伪多租户"——每个 Prometheus 实例是独立的,数据聚合只是视图层面的,真正的数据隔离靠的是物理分离。
注意
Prometheus Federation 不是真正的多租户。每个 Prometheus 实例是独立的物理部署,只是通过 Federation 将数据聚合到中心节点。数据写入路径上没有 tenant 标识,无法实现真正的资源隔离和配额管理。VictoriaMetrics 的多租户是在数据写入路径上内嵌 tenant 信息,存储层天然支持 tenant 级别的隔离。
思考记忆提示 — TenantID 是 VM 多租户的核心——理解它的结构就理解了整个多租户模型
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
}
设计精髓
两段式设计的优势在于提供了灵活的多级隔离能力:
如果使用单一段式(如只用一个 tenantID),就无法实现账户级别的聚合和配额管理。
在 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 下的所有数据属于同一个命名空间 │
└─────────────────────────────────────────────────────────────────────────┘
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:
这样设计的好处是:
TenantID 的两段式设计同理:
思考记忆提示 — vminsert 是 Cluster 模式的写入入口——理解它如何处理 tenant 是理解多租户写入的关键
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)
}
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 的数据会分散到多个节点,查询时需要跨节点聚合,延迟和复杂度都会增加。
┌─────────────────────────────────────────────────────────────────────────┐
│ 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 是 Cluster 模式的查询入口——理解它如何处理 tenant 过滤是理解多租户查询的关键
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)
}
┌─────────────────────────────────────────────────────────────────────────┐
│ 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 多租户隔离的核心是数据路径隔离:
这种设计的优势是:隔离是存储层的原生能力,不需要额外的查询过滤器。
思考记忆提示 — vmstorage 是数据存储的核心——理解它如何按 tenant 分区是理解多租户存储的关键
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/
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 分区 = 分类书架
实际上图书馆的做法是:
这样设计的好处是:
VictoriaMetrics 的 tenant 分区同理:每个 tenant 有独立的目录结构,数据物理隔离,查询时只需扫描对应目录。
思考记忆提示 — Single-Node 模式也支持多租户——只是没有 Cluster 模式的分布式能力
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 的数据存储在同一目录下
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 版本提供更强大的多租户管理能力——包括资源配额和访问控制
VictoriaMetrics Enterprise 版本支持多种类型的资源配额:
| 配额类型 | 说明 | 作用 |
|---|---|---|
| ingestionRate | 写入速率(samples/s) | 防止租户写入量过大影响其他租户 |
| storageSize | 存储大小(字节) | 限制租户使用的磁盘空间 |
| seriesCount | 时间序列数量 | 限制租户创建的指标数量,防止高基数 |
| queriesPerSecond | 查询速率(queries/s) | 限制租户的查询频率 |
| queryResolution | 查询分辨率(最大时间范围) | 防止租户查询过大时间范围 |
// 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 是全篇的"临考前速背"模块,20 组覆盖全链路
accountID 是账户级别,projectID 是项目级别,类似于"公司 + 部门"的关系。同一 accountID 下的所有 projectID 共享账户级别的配额。accountID/projectID 的组合唯一确定一个租户。例如,accountID=123, projectID=456 表示"账户 123 的项目 456"。
两段式设计支持账户级和项目级的双重隔离和配额管理。单一段式(如只有一个 tenantID)无法实现账户级别的聚合。例如,云服务商可以按账户设置总配额,账户内的多个项目共享该配额;如果只有一个 tenantID,就无法区分"账户 A 的总配额"和"账户 B 的总配额"。
accountID 和 projectID 都是 uint32,最大值约 42 亿。理论上可以支持数十亿个租户。但实际上,租户数量受限于 vmstorage 节点的存储能力和网络带宽。建议单个 vmstorage 节点支持 1000-10000 个活跃租户。
通过一致性哈希(consistent hashing)保证同一个 tenant 的所有请求路由到同一个 vmstorage 节点。vminsert 在接收到写入请求后,计算 tenantID 的哈希值,然后在一致性哈希环上查找对应的节点。由于哈希值是确定的,同一个 tenant 的所有请求都会路由到同一个节点。
如果启用了副本机制,数据不会丢失,但可能短暂不可用。Enterprise 版本支持 vmstorage 节点的副本复制。同一个 tenant 的主副本和从副本分布在不同的节点上,当主节点故障时,从节点可以接管服务,实现高可用。
通过解析 URL 路径,提取 /insert/{tenant}/ 后面的 tenant 字符串。路径格式为 /insert/{accountID:projectID}/prometheus/api/v1/write。vminsert 从路径中提取 tenant 字符串(如 123:456),然后解析为 TenantID 结构。
如果不指定 tenant,默认为 0:0(匿名租户)。数据会存储在 data/0/0/ 目录下。在多租户环境中,建议始终明确指定 tenant,避免数据混淆。
通过 remote_write URL 中的 tenant 参数指定。配置示例:remote_write: url: "http://vminsert:8480/insert/123:456/prometheus/api/v1/write"。多个 Prometheus 实例可以配置不同的 tenant,实现数据隔离。
通过 API Key 或 JWT Token 验证请求权限。Enterprise 版本支持基于 API Key 的权限管理。每个 API Key 绑定到特定的 tenant,请求时需要携带对应的 API Key。vmselect 会验证 API Key 是否有权限访问请求的 tenant。
如果 API Key 没有权限访问该 tenant,返回 403 Forbidden。错误消息类似于 "access denied for tenant 123:456"。客户端需要确保使用正确的 API Key。
正常情况下不会,因为数据路径完全隔离。每个 tenant 的数据存储在独立的目录结构下(data/{accountID}/{projectID}/),查询时只扫描对应目录。如果某个 tenant 的写入量突然飙升,可能影响同一节点的 CPU 和磁盘 I/O,但这是资源竞争问题,不是数据隔离问题。
通过 /metrics 端点查看 tenant 级别的指标。关键指标包括:vm_tenant_rows_ingested_total(写入行数)、vm_tenant_samples_ingested_total(写入样本数)、vm_tenant_storage_size_bytes(存储大小)、vm_tenant_series_count(时间序列数量)。
Single-Node 模式在本地按 tenant 隔离存储,Cluster 模式将不同 tenant 分布到不同的 vmstorage 节点。Single-Node 适合中小规模场景,多个 tenant 共享本地资源。Cluster 模式适合大规模场景,可以按 tenant 进行水平扩展,实现更好的资源隔离。
支持五种配额类型:ingestionRate(写入速率)、storageSize(存储大小)、seriesCount(时间序列数量)、queriesPerSecond(查询速率)、queryResolution(查询分辨率)。这些配额可以在账户级别或项目级别设置,实现灵活的配额管理策略。
配额超限后,写入请求会被拒绝,返回配额超限错误。错误消息包含超限的配额类型(如 "ingestionRate exceeded")。客户端需要根据错误类型调整写入策略(如降低写入速率、删除过期数据释放空间)。
通过 vmauth 或 Enterprise 管理界面创建 API Key。Enterprise 版本提供了 CLI 工具和 Web UI 来管理 API Key。每个 API Key 绑定到特定的 tenant 和权限级别(如只读、读写、管理员)。
可以,Enterprise 版本支持运行时动态调整配额。通过管理 API 或 CLI 工具可以实时修改租户的配额,新配额立即生效,不需要重启服务。
通过 tenant 参数过滤日志和指标,定位问题租户。所有日志和指标都包含 tenant 标签(accountID:projectID)。使用 /internal/tenants 接口可以查看所有租户的概览,使用 /internal/tenant/{tenantID}/status 接口可以查看特定租户的详细状态。
可以使用 vmctl 工具将现有数据迁移到指定 tenant。迁移时指定 --tenant 参数,数据会被写入目标 tenant 的存储路径。迁移完成后,原有数据和新数据在逻辑上完全隔离。
通过多层安全机制保证数据安全:存储层目录隔离、API 层权限验证、网络层 TLS 加密。存储层通过目录结构隔离不同 tenant 的数据,即使磁盘被直接访问也无法读取其他 tenant 的数据。API 层通过 API Key 或 JWT 验证权限。网络层通过 TLS 加密传输数据。
全篇必记总纲
VictoriaMetrics 多租户架构的核心是两段式 TenantID(accountID/projectID)+ 一致性哈希路由 + 目录级数据隔离:写入时 vminsert 解析 tenant 并路由到对应 vmstorage,查询时 vmselect 验证权限并只扫描对应目录,存储层通过 data/{accountID}/{projectID}/ 目录结构实现物理隔离。Enterprise 版本额外提供配额管理和细粒度权限控制。
本篇覆盖了 VictoriaMetrics 的多租户架构设计,但还有很多细节尚未展开:
本文参考与源码链接:
• lib/storage/tenant.go · TenantID 数据结构
• app/vminsert/ · 写入入口
• app/vmselect/ · 查询入口
• lib/storage/ · 存储核心层
• VictoriaMetrics Cluster 架构文档
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。