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

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

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

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

lib/auth/auth.go                       ← Token 结构体(AccountID/ProjectID)+ NewToken 解析
lib/tenantmetrics/counter_map.go        ← TenantID 结构体 + per-tenant 指标统计
app/vminsert/main.go                    ← Cluster 写入入口 RequestHandler
app/vmselect/main.go                    ← Cluster 查询入口 Init
docs/victoriametrics/Cluster-VictoriaMetrics.md ← 官方多租户文档

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

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

重点掌握(必须)

  • TenantID 结构lib/auth/auth.goToken 的两字段设计
  • 写入路径 tenant 传递app/vminsert/main.goRequestHandler 如何处理 tenant
  • 查询路径 tenant 隔离app/vmselect/main.goInit 如何注册路由
  • 存储层 tenant 路由:数据在各 vmstorage 节点间均匀分布

次重点(了解即可)

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

文章目录

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

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

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

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

我理解源码的意思是说

多租户在 VM 中不是什么额外的"附加功能",而是从数据结构层面就设计好的基础模型。我们直接从 lib/auth/auth.go 出发,剖析两字段设计的真实实现:

源码视角一:Token 结构体只有两个 uint32 字段

lib/auth/auth.go 第 10-13 行,Token 结构体的定义极简:

type Token struct {
    AccountID uint32
    ProjectID uint32
}

没有第三个字段,没有 version,没有 metadata。VM 的多租户模型从数据结构上就是两个字段,非常克制。为什么要用两个 uint32 而不是一个大整数?答案在 lib/auth/auth.go 第 23 行的 String() 方法:

func (t *Token) String() string {
    if t.ProjectID == 0 {
        return fmt.Sprintf("%d", t.AccountID)
    }
    return fmt.Sprintf("%d:%d", t.AccountID, t.ProjectID)
}

ProjectID == 0 时,只输出 accountID。这就是 Cluster URL 格式 /insert/{accountID}/... 而非 /insert/{accountID}:{projectID}/... 的根本原因——projectID 为 0 时可以省略。

源码视角二:Init 方法的解析逻辑决定了 URL 格式

lib/auth/auth.go 第 46-66 行的 Init 方法:

func (t *Token) Init(authToken string) error {
    tmp := strings.Split(authToken, ":")
    if len(tmp) > 2 {
        return fmt.Errorf("unexpected number of items...")
    }
    n, err := strconv.ParseUint(tmp[0], 10, 32)
    accountID := uint32(n)
    projectID := uint32(0)
    if len(tmp) > 1 {
        n, err := strconv.ParseUint(tmp[1], 10, 32)
        projectID = uint32(n)
    }
    t.Set(accountID, projectID)
    return nil
}

关键点:最多 Split 出 2 段(禁止 3 段);如果只有 1 段,projectID 默认为 0。这就是为什么 Single-Node 用 ?tenant=123:456 而 Cluster 用 URL 路径中的 accountID(projectID 放 URL 参数或省略)。

源码视角三:multitenant 特殊值

lib/auth/auth.go 第 35-43 行:

func NewTokenPossibleMultitenant(authToken string) (*Token, error) {
    if authToken == "multitenant" {
        return nil, nil
    }
    return NewToken(authToken)
}

当传入 "multitenant" 时返回 nil Token。这是 Cluster 多租户端点(如 /insert/multitenant/prometheus/...)的实现基础——内部用 nil Token 区分"未指定具体租户"的请求。

源码视角总结:VM 多租户的 3 个设计原则

  • 1 个 Token 两个字段:AccountID uint32 + ProjectID uint32,范围 [0, 2^32)
  • 1 个省略规则:ProjectID == 0 时可省略,体现在 String() 和 URL 格式
  • 1 个特殊值:"multitenant" 返回 nil Token,支持多租户端点

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 Token 的数据结构

VictoriaMetrics 的 Token 采用两段式设计:accountID/projectID。这个设计参考了 Google Cloud 的项目模型,提供了两层隔离能力。注意:真实源码中没有名为 TenantID 的结构体,实际类型名是 Token(定义在 lib/auth/auth.go)。

// lib/auth/auth.go(Token 数据结构)
// VictoriaMetrics v1.146.0

type Token struct {
    // AccountID:账户 ID,范围 [0, 2^32)
    // 同一账户下的所有项目共享账户级别的配额
    AccountID uint32

    // ProjectID:项目 ID,范围 [0, 2^32)
    // 同一项目下的数据属于同一个业务线或服务
    ProjectID uint32
}

// Token 的字符串表示
// 注意:当 ProjectID == 0 时只输出 AccountID
func (t *Token) String() string {
    if t.ProjectID == 0 {
        return fmt.Sprintf("%d", t.AccountID)
    }
    return fmt.Sprintf("%d:%d", t.AccountID, t.ProjectID)
}

// 解析字符串为 Token
// 格式:accountID:projectID 或只有 accountID
func (t *Token) Init(authToken string) error {
    tmp := strings.Split(authToken, ":")
    if len(tmp) > 2 {
        return fmt.Errorf("unexpected number of items...")
    }
    accountID := uint32(0)
    projectID := uint32(0)
    n, _ := strconv.ParseUint(tmp[0], 10, 32)
    accountID = uint32(n)
    if len(tmp) > 1 {
        n, _ := strconv.ParseUint(tmp[1], 10, 32)
        projectID = uint32(n)
    }
    t.Set(accountID, projectID)
    return nil
}

设计精髓

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

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

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

2.2 TenantID 的 URL 编码

在 HTTP API 中,TenantID 通过 URL 路径传递,Cluster 模式只用 accountID 作为路径段,projectID 可省略。

┌─────────────────────────────────────────────────────────────────────────┐
│                    TenantID URL 编码格式                                   │
│                                                                          │
│  Single-Node 模式(URL 参数):                                            │
│    /api/v1/write?tenant=123:456                                         │
│    /api/v1/query?tenant=123:456                                         │
│                                                                          │
│  Cluster 模式(URL 路径,★ 关键点):                                      │
│    /insert/{accountID}/prometheus/api/v1/write                          │
│    /select/{accountID}/prometheus/api/v1/query                          │
│                                                                          │
│  说明:                                                                   │
│    - 路径中只用 accountID,projectID 省略时为 0                           │
│    - 如果需要精确指定 projectID,通过 HTTP 头指定:                         │
│      AccountID: {accountID}                                              │
│      ProjectID: {projectID}                                              │
│    - 多租户端点:/insert/multitenant/...(通过 vm_account_id 标签指定)   │
└─────────────────────────────────────────────────────────────────────────┘

注意:Cluster URL 中不存在 /insert/123:456/... 格式。accountID 和 projectID 不能同时出现在 URL 路径中,projectID 只能通过 HTTP 头或标签传递。这与 Single-Node 的 ?tenant=123:456 格式不同。

三、写入路径:vminsert 如何处理 tenant

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

  • vminsert 接收各协议数据,统一写入路径
  • 数据在各 vmstorage 节点间均匀分布,不是按 tenant 路由到特定节点
  • 面试高频提问:Cluster 模式下 tenant 数据是如何分布的?

3.1 vminsert 的 HTTP 处理

vminsert 是 VictoriaMetrics Cluster 模式的写入入口,接收来自 Prometheus、vmagent 等客户端的写入请求。读 app/vminsert/main.go,其核心是 RequestHandler 函数,通过 switch 语句分发到各协议的 InsertHandler。

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

// RequestHandler 是 vminsert 的核心 HTTP 处理函数
func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
    path := strings.ReplaceAll(r.URL.Path, "//", "/")

    // 处理静态资源
    if strings.HasPrefix(path, "/static") {
        staticServer.ServeHTTP(w, r)
        return true
    }

    switch path {
    case "/prometheus/api/v1/write", "/api/v1/write", "/api/v1/push":
        // Prometheus Remote Write 协议入口
        prometheusWriteRequests.Inc()
        if err := promremotewrite.InsertHandler(r); err != nil {
            httpserver.Errorf(w, r, "%s", err)
            return true
        }
        w.WriteHeader(http.StatusNoContent)
        return true
    // ... 其他协议分支
    }
    return false
}

3.2 Cluster 数据分布机制

根据 docs/victoriametrics/Cluster-VictoriaMetrics.md 官方文档:数据在各 vmstorage 节点间均匀分布(evenly spread),不是按 tenant 哈希到特定节点。这意味着:

  • 同一个 tenant 的数据可能分布在多个 vmstorage 节点上
  • 查询时 vmselect 需要 scatter-gather 跨节点聚合
  • 这与"一致性哈希按 tenant 路由"的设计完全不同
Cluster 数据分布模型:

┌────────────┐     ┌──────────────────────────────────────────────┐
│  vminsert  │────▶│            vmstorage 节点池                   │
│  (写入)    │     │                                              │
└────────────┘     │  ┌──────────┐ ┌──────────┐ ┌──────────┐     │
                   │  │ storage1 │ │ storage2 │ │ storage3 │     │
                   │  │  均匀分布 │ │  均匀分布 │ │  均匀分布 │     │
                   │  └──────────┘ └──────────┘ └──────────┘     │
                   └──────────────────────────────────────────────┘

关键点:
- 数据按时间或 ID 范围均匀分片
- 同一个 tenant 的数据可能分布在多个 storage 节点
- 查询时需要跨节点聚合(scatter-gather)

小贴士vs Prometheus 的 Federation

Prometheus Federation 是"每个 Prometheus 实例独立存储"的聚合,物理隔离但资源利用率低。VictoriaMetrics Cluster 是"数据分片均匀分布"的共享存储,逻辑隔离且资源利用率高。

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

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

  • vmselect 解析 URL 路径中的 accountID 信息
  • 将查询请求分发到多个 vmstorage 节点并聚合结果
  • 面试高频提问:查询时如何保证只能访问自己有权限的 tenant 数据?

4.1 vmselect 的初始化

vmselect 是 VictoriaMetrics Cluster 模式的查询入口,读 app/vmselect/main.go 第 49-60 行的 Init 函数,可以看到它初始化了网络存储、Rollup 缓存等组件。

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

// Init 初始化 vmselect
func Init(vmselectMaxConcurrentRequests int, vmselectMaxQueueDuration time.Duration) {
    tmpDirPath := vmstorage.DataPath() + "/tmp"
    fs.MustRemoveDirContents(tmpDirPath)
    netstorage.InitTmpBlocksDir(tmpDirPath)
    promql.InitRollupResultCache(vmstorage.DataPath() + "/cache/rollupResult")

    maxConcurrentRequests = vmselectMaxConcurrentRequests
    maxQueueDuration = vmselectMaxQueueDuration
    concurrencyLimitCh = make(chan struct{}, maxConcurrentRequests)

    initVMUIConfig()
    // ... 更多初始化
}

4.2 多租户查询

根据官方文档,vmselect 支持三种 tenant 指定方式:

  • URL 路径/select/{accountID}/prometheus/api/v1/query
  • HTTP 头AccountIDProjectID
  • 多租户端点/select/multitenant/...(通过 vm_account_id 标签过滤)

设计精髓

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

  • 存储层隔离:每个 tenant 的数据通过目录或分片键隔离
  • 查询层隔离:查询时只扫描对应 tenant 的数据分片
  • 权限验证:通过 vmauth/vmgateway 在入口处做 API Key 和 JWT 验证

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

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

  • 数据在 vmstorage 节点内按分片键分区
  • 不同 tenant 的数据在同一 vmstorage 节点内通过分区键隔离
  • 面试高频提问:不同 tenant 的数据会混在一起吗?如何保证隔离?

5.1 数据目录结构

vmstorage 节点上的数据按分区存储,读 lib/storage/filenames.go,核心目录结构如下:

vmstorage 数据目录结构:

storageDataPath/
│
├── data/                          # 数据根目录(见 filenames.go dataDirname)
│   ├── 20230601/                 # 按月分区(date-based partitioning)
│   │   ├── small/                # 小 Part(未合并)
│   │   └── big/                  # 大 Part(合并后)
│   ├── 20230602/
│   └── ...
│
├── indexdb/                       # 索引数据库(见 filenames.go indexdbDirname)
│   └── ...
│
├── cache/                         # 缓存目录(见 filenames.go cacheDirname)
│   └── ...
│
└── metadata/                     # 元数据目录
    └── ...

关键点:
- 按时间分区(date-based partitioning),不是按 tenant 分区
- 数据在各 vmstorage 节点间均匀分布(evenly spread)
- 同一 tenant 的数据可能跨多个日期分区

注意:vmstorage 数据按时间分区(date-based partitioning),不是按 tenant 目录隔离。不存在 data/{accountID}/{projectID}/ 嵌套目录结构。Tenant 隔离是通过数据分片键和查询时过滤实现的,不是通过文件系统目录实现的。

5.2 Tenant 级别的资源统计

vmstorage 维护每个 tenant 的资源使用统计,读 lib/tenantmetrics/counter_map.goTenantID 结构体和 CounterMap 用于 per-tenant 指标追踪:

// lib/tenantmetrics/counter_map.go(per-tenant 指标统计)
// VictoriaMetrics v1.146.0

// TenantID 定义了指标的 tenant 维度
type TenantID struct {
    AccountID uint32
    ProjectID uint32
}

// CounterMap 是按 tenant 分组的计数器 map
type CounterMap struct {
    metric string
    m      sync.Map
    mt     atomic.Value  // multitenant 的聚合计数器
}

// GetByTenant 返回指定 tenant 的计数器
func (cm *CounterMap) GetByTenant(key *TenantID) *metrics.Counter {
    if key == nil {
        // multitenant:返回聚合计数器
        return cm.getMultitenantCounter()
    }
    // ... 按 tenant key 返回或创建计数器
}

Per-tenant 指标通过 /metrics 端点暴露,文档见 docs/victoriametrics/PerTenantStatistic.md

六、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(匿名租户)
  - accountID 和 projectID 都是 uint32,范围 [0, 2^32)
  - 性能取决于总 active series 数量,非 tenant 数量

七、Enterprise 版本的配额管理

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

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

7.1 资源配额类型

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

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

八、FAQ:常见疑问

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

  • Q1-Q5 围绕 TenantID 结构:accountID/projectID 设计原理
  • Q6-Q10 围绕写入路径:vminsert tenant 解析和数据分布
  • Q11-Q15 围绕查询路径:vmselect tenant 过滤和权限验证
  • Q16-Q20 围绕存储和配额:vmstorage 分区和 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 亿。理论上可以支持数十亿个租户。实际限制取决于总 active series 数量和存储能力。建议单个 vmstorage 节点支持 1000-10000 个活跃租户。

Q4. Cluster 模式下 tenant 数据是如何分布的?

数据在各 vmstorage 节点间均匀分布(evenly spread),不是按 tenant 哈希到特定节点。这意味着同一个 tenant 的数据可能分布在多个 vmstorage 节点上。查询时 vmselect 需要 scatter-gather 跨节点聚合结果。这种设计保证了负载均衡,避免单个节点成为热点。

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

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

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

通过解析 URL 路径中的 accountID。路径格式为 /insert/{accountID}/prometheus/api/v1/write。vminsert 从路径中提取 accountID 字符串,然后通过 lib/auth/auth.goNewToken 方法解析为 Token 结构体。

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

如果不指定 tenant,默认为 0:0(匿名租户)。数据会存储在 accountID=0, projectID=0 的命名空间下。在多租户环境中,建议始终明确指定 tenant,避免数据混淆。

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

通过 remote_write URL 中的路径段指定。配置示例:url: "http://vminsert:8480/insert/123/prometheus/api/v1/write"(只用 accountID,projectID 省略时为 0)。多个 Prometheus 实例可以配置不同的 accountID,实现数据隔离。

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

通过 vmauth 或 vmgateway 在入口处做权限验证。Enterprise 版本支持基于 API Key 的权限管理。每个 API Key 绑定到特定的 tenant 和权限级别。权限验证在 Cluster 入口网关层完成,不是在 vmselect 内部。

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

如果 API Key 没有权限访问该 tenant,返回 403 Forbidden 或空结果集。错误消息取决于具体配置。客户端需要确保使用正确的 API Key 和 tenant ID。

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

正常情况下不会,因为查询路径通过 tenant 过滤。每个 tenant 的数据通过分片键隔离,查询时只扫描对应分片。如果某个 tenant 的写入量突然飙升,可能影响同一节点的 CPU 和磁盘 I/O,但这是资源竞争问题,不是数据隔离问题。

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

通过 /metrics 端点查看 tenant 级别的指标。VictoriaMetrics 暴露各种 per-tenant 统计指标,文档见 docs/victoriametrics/PerTenantStatistic.md。使用 /admin/tenants 接口可以查看所有租户的概览。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

通过多层安全机制保证数据安全:存储层分片隔离、API 层权限验证、网络层 TLS 加密。存储层通过分片键隔离不同 tenant 的数据。API 层通过 API Key 或 JWT 验证权限。网络层通过 TLS 加密传输数据。

全篇必记总纲

VictoriaMetrics 多租户架构的核心是两段式 Token(accountID/projectID)+ 数据均匀分片 + 分片键隔离:写入时 vminsert 接收各协议数据,数据在各 vmstorage 节点间均匀分布,查询时 vmselect 通过 scatter-gather 聚合多节点结果并按 tenant 过滤。Token 结构体定义在 lib/auth/auth.go,per-tenant 指标追踪在 lib/tenantmetrics/。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/auth/auth.go · Token 结构体(AccountID/ProjectID)
  • lib/tenantmetrics/counter_map.go · TenantID + per-tenant 统计
  • app/vminsert/ · 写入入口
  • app/vmselect/ · 查询入口
  • VictoriaMetrics Cluster 架构文档

posted @ 2026-06-29 01:05  左扬  阅读(48)  评论(0)    收藏  举报