多租户架构设计

SaaS产品的架构基础

50分钟
高级多租户架构设计SaaS

🎯学习目标

  • 1理解多租户架构的核心概念
  • 2掌握数据隔离的实现方式
  • 3学会多租户系统的资源管理
1

开篇:为什么需要多租户

如果你的AI产品要服务多个企业客户,每个客户的数据需要隔离。多租户架构就是解决这个问题的。

本节课讨论如何设计一个安全、高效的多租户系统。

2

多租户架构概述

**什么是多租户?** 一个应用实例服务多个客户(租户),每个租户的数据和配置相互隔离。

**三种隔离模型**:

**1. 独立数据库** - 每个租户一个数据库 - 隔离性最强 - 成本最高

**2. 共享数据库,独立Schema** - 共享数据库实例,每个租户一个Schema - 隔离性中等 - 成本适中

**3. 共享数据库,共享Schema** - 通过租户ID区分数据 - 隔离性最弱 - 成本最低

**选择依据**: - 安全要求高的选独立数据库 - 成本敏感的选共享Schema - 大部分SaaS选择共享Schema+租户ID

💡 多租户三种隔离模型:独立数据库、独立Schema、共享Schema,各有取舍。

3

数据隔离实现

**共享Schema方案实现**:

**1. 数据库层面**: - 每个表添加tenant_id字段 - 创建复合索引:(tenant_id, id) - 查询必须带tenant_id条件

**2. 应用层面**: - 请求上下文传递租户信息 - 中间件自动注入租户条件 - ORM层自动过滤

**3. 安全层面**: - API层验证租户身份 - 越权访问检测 - 数据导出审核

**关键原则**: - 永远不要相信客户端传的tenant_id - 从认证Token中提取租户信息 - 所有查询必须带租户过滤

4

代码示例:多租户中间件

Express中间件自动注入租户过滤:

javascript
// 多租户中间件
function tenantMiddleware(req, res, next) {
  // 从JWT Token中获取租户ID(不信任客户端)
  const token = req.headers.authorization?.split(' ')[1];
  const decoded = verifyToken(token);

  if (!decoded.tenantId) {
    return res.status(401).json({ error: 'Tenant not found' });
  }

  // 注入到请求上下文
  req.tenantId = decoded.tenantId;
  next();
}

// 数据库查询封装
class TenantAwareRepository {
  constructor(model, tenantId) {
    this.model = model;
    this.tenantId = tenantId;
  }

  async findAll(filter = {}) {
    // 自动添加租户过滤
    return this.model.findAll({
      where: {
        ...filter,
        tenantId: this.tenantId,  // 关键:自动注入
      },
    });
  }

  async create(data) {
    // 自动添加租户ID
    return this.model.create({
      ...data,
      tenantId: this.tenantId,  // 关键:自动注入
    });
  }
}

// 使用示例
app.get('/api/documents', tenantMiddleware, async (req, res) => {
  const repo = new TenantAwareRepository(Document, req.tenantId);
  const docs = await repo.findAll();
  res.json(docs);
});
5

资源配额管理

**为什么需要配额?** 防止单个租户占用过多资源,影响其他租户。

**配额维度**:

**1. 调用量限制** - 每日/每月API调用次数 - 每分钟请求频率(Rate Limit) - 并发请求限制

**2. 数据量限制** - 文档数量上限 - 向量存储容量 - 数据库存储空间

**3. 功能限制** - 可用模型(大模型额外付费) - 高级功能(如自定义Agent) - 导出数据量

**实现方式**: - Redis计数器:实时统计调用量 - 配置表:存储租户配额 - 中间件:配额检查和拒绝

6

代码示例:配额检查中间件

实现API调用配额限制:

javascript
import Redis from 'ioredis';
const redis = new Redis();

// 配额检查中间件
function quotaMiddleware(quotaConfig) {
  return async (req, res, next) => {
    const tenantId = req.tenantId;
    const today = new Date().toISOString().split('T')[0];

    // 1. 检查日配额
    const dailyKey = `quota:${tenantId}:daily:${today}`;
    const dailyCount = await redis.incr(dailyKey);
    await redis.expire(dailyKey, 86400);  // 24小时过期

    if (dailyCount > quotaConfig.dailyLimit) {
      return res.status(429).json({
        error: 'Daily quota exceeded',
        limit: quotaConfig.dailyLimit,
        used: dailyCount,
      });
    }

    // 2. 检查频率限制(每分钟)
    const minuteKey = `quota:${tenantId}:minute:${Date.now() / 60000 | 0}`;
    const minuteCount = await redis.incr(minuteKey);
    await redis.expire(minuteKey, 60);

    if (minuteCount > quotaConfig.rateLimit) {
      return res.status(429).json({
        error: 'Rate limit exceeded',
        limit: quotaConfig.rateLimit,
      });
    }

    next();
  };
}

// 使用
app.get('/api/chat',
  tenantMiddleware,
  quotaMiddleware({ dailyLimit: 1000, rateLimit: 10 }),
  chatHandler
);
7

实战:AI SaaS的多租户方案

**场景**:企业知识库SaaS

**架构设计**:

**数据隔离**: - 向量数据库:Pinecone namespace(每个租户一个命名空间) - 文档存储:共享S3,路径区分(/tenant-{id}/) - 元数据存储:共享数据库,tenant_id字段

**配额设计**: - 基础版:1000次查询/天,1000文档 - 专业版:10000次查询/天,10000文档 - 企业版:无限制,按量付费

**安全设计**: - JWT认证,租户ID在Token中 - API Key绑定租户 - 所有查询自动注入租户过滤 - 管理后台跨租户操作需审批

8

合规考虑

某些行业(医疗、金融)要求数据物理隔离,必须选择独立数据库方案。多租户架构选择需要考虑合规要求。

📝课后小结

多租户架构有三种隔离模型:独立数据库、独立Schema、共享Schema。共享Schema方案成本最低但需要严格的租户过滤。配额管理防止资源滥用。架构选择需要考虑合规要求。

1三种隔离模型:独立数据库、独立Schema、共享Schema
2共享Schema需要所有查询带tenant_id过滤
3租户ID必须从认证Token获取,不信任客户端
4配额管理包括调用量、数据量、功能限制

课后练习

1

多租户架构中,最经济的隔离方案是?

A. 独立数据库
B. 独立Schema
C. 共享Schema+租户ID
D. 独立服务器

答案:共享Schema+租户ID

共享Schema通过tenant_id区分数据,资源利用率最高,成本最低。

2

为什么租户ID不能信任客户端传入?

A. 影响性能
B. 客户端可能伪造,导致数据越权访问
C. 增加代码复杂度
D. 服务器不接收客户端数据

答案:客户端可能伪造,导致数据越权访问

恶意用户可能修改tenant_id访问其他租户数据,必须从认证Token中安全获取租户信息。