Supabase 多租户架构完整指南:从入门到生产级部署
- 2025-12-17 13:28:29
- 技术博客 原创
- 66
Supabase 多租户架构完整指南:从入门到生产级部署
前言
在构建 SaaS 产品时,多租户架构是一个核心决策。本文将深入探讨如何使用 Supabase 构建安全、高效、成本优化的多租户系统,从基础概念到生产级部署,涵盖所有关键知识点。 本文适合:- 正在构建 SaaS 产品的开发者
- 需要为多个客户提供服务的 AI 应用
- 寻求成本优化方案的技术团队
- 对 Supabase 多租户架构感兴趣的开发者
---
目录
- [多租户架构基础](#多租户架构基础)
- [三种多租户方案对比](#三种多租户方案对比)
- [最佳方案:RLS + 独立 API Key](#最佳方案)
- [生产级部署:Supavisor 连接池](#生产级部署)
- [成本分析](#成本分析)
- [实战配置](#实战配置)
- [安全性验证](#安全性验证)
- [总结](#总结)
---
多租户架构基础
什么是多租户?
多租户(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/月
---
方案 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/月
---
最佳方案: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 客户端直连 → 数据库崩溃
发表评论