Supabase 多租户架构完整指南:从入门到生产级部署

2025-12-17 13:28:29
技术博客
原创
66
摘要:---

Supabase 多租户架构完整指南:从入门到生产级部署

前言

在构建 SaaS 产品时,多租户架构是一个核心决策。本文将深入探讨如何使用 Supabase 构建安全、高效、成本优化的多租户系统,从基础概念到生产级部署,涵盖所有关键知识点。 本文适合:
  • 正在构建 SaaS 产品的开发者
  • 需要为多个客户提供服务的 AI 应用
  • 寻求成本优化方案的技术团队
  • 对 Supabase 多租户架构感兴趣的开发者
预计阅读时间: 20 分钟

---

目录

  1. [多租户架构基础](#多租户架构基础)
  2. [三种多租户方案对比](#三种多租户方案对比)
  3. [最佳方案:RLS + 独立 API Key](#最佳方案)
  4. [生产级部署:Supavisor 连接池](#生产级部署)
  5. [成本分析](#成本分析)
  6. [实战配置](#实战配置)
  7. [安全性验证](#安全性验证)
  8. [总结](#总结)

---

多租户架构基础

什么是多租户?

多租户(Multi-Tenancy)是指单个应用实例同时服务多个客户(租户),每个租户的数据完全隔离。这是 SaaS 产品的核心架构模式。

为什么需要多租户?

传统方案的问题:
客户 A:独立服务器 + 独立数据库 → $200/月
客户 B:独立服务器 + 独立数据库 → $200/月
客户 C:独立服务器 + 独立数据库 → $200/月
总成本:$600/月
多租户方案:
所有客户:共享服务器 + 共享数据库 → $50/月
节省:91.7%

---

三种多租户方案对比

方案 1:Database-per-Tenant(完全独立)

架构:
租户 A → 数据库 A
租户 B → 数据库 B
租户 C → 数据库 C
优点:
  • ✅ 完全隔离,安全性最高
  • ✅ 易于备份和恢复
  • ✅ 性能互不影响
缺点:
  • ❌ 资源消耗巨大
  • ❌ 管理复杂(N 个数据库)
  • ❌ 成本高昂
成本:
  • 100 租户:$200-400/月
  • 1000 租户:$2000-4000/月
适用场景: 大型企业客户,要求完全隔离

---

方案 2:Schema-per-Tenant(平衡方案)

架构:
PostgreSQL (单数据库)
├── Schema: tenant_a
│   ├── users
│   ├── conversations
│   └── messages
├── Schema: tenant_b
└── Schema: tenant_c
优点:
  • ✅ 较好的隔离性
  • ✅ 比完全独立节省资源
  • ✅ 便于管理
缺点:
  • ❌ 仍需较多资源(每个 Schema 独立表)
  • ❌ 连接池管理复杂
  • ❌ Schema 数量有限制
成本:
  • 100 租户:$80-150/月
  • 1000 租户:$800-1500/月
适用场景: 中型 SaaS,需要较强隔离

---

方案 3:RLS + 共享表(推荐)⭐⭐⭐⭐⭐

架构:
PostgreSQL (单数据库 + 单 Schema)
├── conversations (共享表)
│   ├── tenant_id: A → 租户 A 的数据
│   ├── tenant_id: B → 租户 B 的数据
│   └── tenant_id: C → 租户 C 的数据
└── RLS 策略自动过滤
优点:
  • ✅ 资源占用最小
  • ✅ 成本最低(节省 90-95%)
  • ✅ 管理简单
  • ✅ 数据库级安全保障
  • ✅ 无限租户
缺点:
  • ⚠️ 需要正确配置 RLS
  • ⚠️ 需要优化索引
成本:
  • 100 租户:$10-20/月
  • 1000 租户:$40-60/月
适用场景: 大多数 SaaS 产品,追求成本优化

---

最佳方案:RLS + 独立 API Key

三层安全隔离

┌─────────────────────────────────────────┐
│ 第 1 层:API Key 隔离                    │
│ 每个租户独立的 sk_xxx key               │
│ Kong Gateway 验证和拒绝无效请求          │
└─────────────────────────────────────────┘
              ↓
┌─────────────────────────────────────────┐
│ 第 2 层:JWT Token 隔离                  │
│ JWT 包含 tenant_id                       │
│ 只能获取自己租户的 token                 │
└─────────────────────────────────────────┘
              ↓
┌─────────────────────────────────────────┐
│ 第 3 层:RLS 数据隔离                    │
│ PostgreSQL 强制执行行级安全              │
│ 即使 SQL 注入也无法跨租户访问            │
└─────────────────────────────────────────┘

数据库结构

-- 租户表
CREATE TABLE tenants (
    id UUID PRIMARY KEY,
    name TEXT NOT NULL,
    slug TEXT UNIQUE NOT NULL,
    plan TEXT DEFAULT 'free'
);
-- 对话表(所有租户共享)
CREATE TABLE conversations (
    id UUID PRIMARY KEY,
    tenant_id UUID NOT NULL REFERENCES tenants(id),
    user_id UUID NOT NULL,
    title TEXT,
    created_at TIMESTAMP DEFAULT NOW()
);
-- 创建索引(关键!)
CREATE INDEX idx_conversations_tenant_id
ON conversations(tenant_id);
-- 启用 RLS
ALTER TABLE conversations ENABLE ROW LEVEL SECURITY;
-- RLS 策略:只能访问自己租户的数据
CREATE POLICY "tenant_isolation" ON conversations
    FOR ALL
    USING (tenant_id = current_setting('app.tenant_id')::uuid);

API Key 管理

-- API Keys 表
CREATE TABLE tenant_api_keys (
    id UUID PRIMARY KEY,
    tenant_id UUID REFERENCES tenants(id),
    api_key TEXT UNIQUE NOT NULL,
    permissions JSONB DEFAULT '{"read": true, "write": true}',
    rate_limit INTEGER DEFAULT 1000,
    is_active BOOLEAN DEFAULT true
);
-- 生成 API Key
CREATE FUNCTION generate_api_key()
RETURNS TEXT AS $$
DECLARE
    prefix TEXT := 'sk_';
    random_part TEXT;
BEGIN
    random_part := encode(gen_random_bytes(32), 'base64');
    random_part := regexp_replace(random_part, '[^a-zA-Z0-9]', '', 'g');
    RETURN prefix || substring(random_part, 1, 48);
END;
$$ LANGUAGE plpgsql;
-- 为租户创建 API Key
SELECT generate_api_key();
-- 返回:sk_x1y2z3a4b5c6d7e8f9g0h1i2j3k4l5m6n7o8p9q0r1s2t3u4v5w6

---

生产级部署:Supavisor 连接池

为什么需要连接池?

问题: PostgreSQL 使用进程模型,每个连接消耗 ~10MB 内存
1,000 客户端直连 → 1,000 PostgreSQL 连接 → 10 GB 内存 ❌
10,000 客户端直连 → 数据库崩溃 
      
发表评论
评论通过审核后显示。
流量统计