单租户 SaaS 给每个客户一整套独立的应用和数据库。多租户 SaaS 让所有客户共用一套应用和数据库,靠租户 ID 区分,而且每条查询都得检查它。单租户换来的是隔离、按客户的控制权和更省事的合规,代价是要运维一大堆副本。多租户更便宜,运维也简单得多,但把客户彼此隔开就成了代码的活儿。大多数产品从多租户起步,只给少数真正需要的客户提供独享实例。
做B2B SaaS,租户架构这个问题迟早会找上门,通常就是某个大客户问“我们的数据能不能和别人分开放”的那一天。这种讨论我经历过不止一次。答案基本就两个:给每个客户独立的实例(单租户,多实例),或者让所有人共享一个实例(多租户,单实例)。没有哪个是“正确答案”,只是坏起来的方式不一样。
基础设施设计#
单租户,多实例#
这条路的吸引力就在“隔离”二字,几乎所有好处都由此而来。每个客户的数据都在自己独立的环境里,安全审查和合规谈判会轻松很多。一次糟糕的部署或一个吃资源的任务,只会影响一个客户,而不是全部。每个实例可以独立扩容、调优、定制;资源专用,性能不会因为隔壁租户在跑什么而波动。维护也能按客户逐个安排,停机影响小而可控。定价合理的话,还能让基础设施成本对上每个租户的实际用量。
代价是:所有东西你都要运维N份。资源开销不断累积,实例之间的配置慢慢漂移,让整个集群的版本和数据保持一致本身就成了一个项目。扩容、隔离、成本管理的问题不会消失,只会成倍增加。
多租户,单实例#
它是默认选项不是没有原因的:一套部署、一个要备份的数据库、生产环境只有一个版本。资源利用率高,运维简单,所有客户同时拿到更新和安全补丁。
难点是共享的另一面。一个租户用得猛,所有人的性能都会被拖累;按客户定制的空间有限;数据隔离不再靠基础设施,而是落在你的应用代码里,一个bug就可能变成跨租户的数据泄露。扩容瓶颈一来就是所有客户一起受影响,维护也要更小心,因为再也没有“只影响这一个客户”的爆炸半径了。
多租户 vs 多实例#
这个选择也常被说成“多租户 vs 多实例”,其实是同一件事从另一面看。“租户”说的是客户:一套软件有几个客户在共用。“实例”说的是部署:你跑了几套软件。单租户产品就是每个客户一套的多实例部署,多租户产品通常是大家共用的单实例。所以有人要“多实例”,意思就是要单租户。
实际上它更像一条光谱,而不是二选一,真正的决定大多落在数据库上。最共享的一端,是所有客户的数据行放在同一批表里,每行带一个 tenant_id 列,每条查询都按它过滤。运行成本最低,也最容易出错。往前一步,是在共享数据库里给每个客户单独一个 schema:隔离更干净,也能按客户备份,代价是每次迁移都要按 schema 的数量跑一遍。再往前,应用共享,但每个客户有自己的数据库:不用给每个客户跑一整套技术栈,数据隔离也够强,是很常见的折中。另一端就是完全的单租户,应用和数据库都是独享的。AWS 的 SaaS 指南把两端叫作 silo(筒仓)和 pool(池),两者混用叫 bridge(桥接),向客户的安全团队解释你的架构时,这套说法挺好用。
单租户 vs 多租户一览#
| 单租户(多实例) | 多租户(单实例) | |
|---|---|---|
| 数据隔离 | 每个客户独立的基础设施 | 靠代码或数据库策略保证 |
| 每个客户的成本 | 高:每个客户一整套技术栈 | 低:大家共用一套 |
| 运维 | 部署、升级、监控面板都按客户数量来 | 各一份 |
| 吵闹的邻居 | 没有 | 一个重度租户可能拖慢所有人 |
| 定制 | 可按客户定制,甚至版本都能不同 | 基本只到配置和功能开关 |
| 发布 | 按客户逐个推 | 所有人同时 |
| 一次糟糕的部署会影响 | 一个客户 | 所有客户 |
| 安全审查和数据驻留 | 好回答 | 要多费口舌 |
什么时候用哪种#
除非有具体原因把你推开,否则就选多租户。客户多、每家付得不多、用户自己注册、团队小到三十套环境就能把人拖垮,这些情况下,多租户都是正确的默认选项。
单租户只在少数几种情况下值回它的开销。医疗、金融、政府类客户常常要求数据物理隔离,或者必须放在某个国家,而在安全审查里,独享实例是对此最简单也最老实的回答。如果只有几家签企业合同的大客户,他们负担得起自己那一套,往往也期待有一套。再就是客户需要自己的发布节奏、锁定某个版本,或者深度定制的时候,分开的实例就不再是开销,而成了你卖的产品的一部分。
大多数团队最后两种都用:绝大多数客户用多租户产品,少数需要并且愿意付钱的客户,作为企业版给独享实例。只要多租户版本从第一天起处处带着租户 ID,以后把某个客户单独迁到自己的数据库,就是一次迁移,而不是重写。
让多租户的隔离更难被打破#
多租户里最让人睡不着的 bug,是忘了加租户过滤的查询。它不会报错,只会悄悄把别人的数据返回给你,而代码评审也不可能每次都抓到。如果你用的是 Postgres,行级安全(RLS)可以把这道检查挪到数据库里,这样就算查询忘了过滤,也只能看到当前客户的数据行:
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON invoices
USING (tenant_id = current_setting('app.tenant_id')::uuid);然后每个请求在事务里用 SET LOCAL app.tenant_id = '…' 设置租户。这样连接池里的连接不会把上一个客户的 ID 带进下一个请求,忘了设置租户的请求会直接报错,而不是拿到数据。有两个坑:应用要用一个不是表所有者的角色连接,因为除非再加上 FORCE ROW LEVEL SECURITY,表的所有者会绕过这些策略;也永远不要用超级用户连接,超级用户无论如何都会绕过。
单租户从哪里开始?#
真要走单租户,工具决定它是“可以管”还是一场慢动作灾难。三十套环境,靠手是管不过来的。
核心是容器加基础设施即代码。Docker把应用打包成对每个租户都完全一致的形态,Kubernetes负责这一堆实例的部署和扩缩容;用Terraform、Pulumi或AWS CloudFormation把一个租户的完整技术栈定义成代码,第31个客户的开通就是一个PR,而不是一个报废的周末。如果你更贴近虚拟机的世界,Ansible、Chef、Puppet这类配置管理工具可以承担同样的角色。CI/CD流水线(Jenkins、GitLab CI/CD)在这里比平时更重要,因为每次发布都要在所有实例上滚动完成,不能靠人盯。
再往后就看产品形态了。有些工作负载可以直接用无服务器(AWS Lambda、Azure Functions)把服务器这一层整个抹掉。Amazon RDS、Azure SQL Database这类托管数据库,能把你从“每个租户手动维护一个库”的苦役里救出来。有了一个集群之后,监控(Prometheus、Grafana、New Relic)就不再是可选项:哪个实例不舒服,你得赶在客户开口之前知道。身份这块交给Auth0或Okta这样的IAM服务,就不用每个租户重造一遍认证。不管你用什么语言,多半都有面向SaaS的框架或库能帮你省掉一部分这类底层活儿;托管方面,AWS、Azure、Google Cloud Platform这些大厂云都能轻松接住。
别想着第一天就把清单买齐。先上容器和IaC,租户多起来再加监控和IAM,剩下的,让真实的痛点来决定。
长话短说#
单租户,也就是多实例,是每个客户一套独立的应用和数据库:隔离强、能按客户控制,代价是要运维一整个集群。多租户是大家共用一套:便宜、简单,隔离靠代码保证。大多数产品应该从多租户起步,处处带上租户 ID 并让数据库来强制执行,再给少数需要并愿意付钱的客户提供独享实例。如果真要跑单租户,让它保持可控的关键是容器和基础设施即代码。
