3.14 多租户与 SaaS 架构
概述与动机
多租户是指运行你的软件的一个实例,让它同时服务许多不同的客户,在客户共享同一套代码、通常也共享同一套基础设施的同时,将每个客户的数据和配置在逻辑上彼此隔开。每个客户是一个租户。这一个理念正是软件即服务(SaaS)的经济引擎()这种模式下你出售的是对一个正在运行的应用的访问权,而不是一份可安装的副本。当上千个租户共享同一套部署时,你只需打一次补丁、扩展一套系统,下一位客户的边际成本便趋近于零。这就是为什么一个构建良好的多租户产品能够用同一套代码库服务一个两人初创公司,也能服务一个十万坐席的大企业,也是为什么你选择的租户模型会在未来数年内塑造你的利润率、安全态势和运维负担。
对于大型团队而言,利害关系远不止于成本。多租户在你的架构核心处放置了一项硬性要求:租户 A 绝不能看到租户 B 的数据,无论出现什么缺陷、竞态还是配置错误,绝无例外。一次跨租户的数据泄露就可能终结一家公司。与此同时,共享的全部意义在于效率,因此每一个设计决策都处在强隔离(更安全、更昂贵)与密集共享(更便宜、更冒险)之间的一个光谱上。把这件事做对,是一个能够优雅扩展的产品与一个要么在基础设施上拖垮你、要么让你登上头条的产品之间的分野。本章建立在云架构(第 3.11 章)之上,大量借助数据架构(第 3.4 章)和云安全(第 4.3 章),并与可扩展性与韧性(第 3.5 章)以及成本归因(第 9.4 章)相衔接。
企业和政府将门槛进一步抬高。企业买家会就合同性的数据保障进行谈判,要求为其层级提供专属隔离,并期望你能在不停机的情况下将其在不同环境间迁移。政府则会加上数据驻留法规、以密级为驱动的隔离,并且往往要求每个机构都成为其自身的租户,拥有自己的审计边界。此处的租户决策并非实现细节;它们是你对每一个信任你保管其数据的客户所作出的承诺。
关键原则
- 隔离是一个光谱,而非一个开关。 单租户独占(silo)、共享池(pool)和混合(bridge)模型在效率与隔离之间进行权衡;应按层级、按资源逐一选择,而不是一次性为所有情况做出选择。
- 租户上下文是神圣不可侵犯的。 每一次请求、每一条查询、每一行日志、每一个后台任务都必须携带租户标识符,每一次数据访问都必须以该标识符为作用域。
- 跨租户泄露是最重要的失败模式。 设计上要确保单个遗漏的过滤条件不可能暴露另一个租户的数据;要纵深防御,而不是依赖一条 WHERE 子句。
- 吵闹邻居是一个架构问题。 没有配额和公平性机制,一个重度租户就会拖累所有人;要在问题发生之前就做好规划。
- 按租户配置是可扩展的;按租户写代码则不是。 用数据和特性开关来弯曲产品形态,而不是用分支代码。
- 租户生命周期是一项产品功能。 入驻、供给、退出和数据导出都必须是一等公民,必须自动化、可审计。
- 无法归因的东西就无法管理。 可观测性和成本都必须能够按租户切分,否则你在可靠性和利润率两方面都是在盲飞。
建议
按层级选择租户模型,沿隔离与效率的光谱取舍
光谱有三个锚点模型。在单租户独占(专属)模型中,每个租户拥有自己独立的技术栈:独立的计算资源、独立的数据库,有时甚至是独立的账户或网络。隔离最强,一个缺陷的爆炸半径只局限于一个租户,但你要为每个客户的闲置容量付费,并要运维许多份副本。在共享池(共享)模型中,所有租户共享同一套计算资源和数据库,仅通过逻辑和租户标识符来分隔。效率最高,一个租户的边际成本趋近于零,但隔离性如今完全取决于你的代码是否正确无误。混合模型则将两者结合:共享计算资源搭配按租户划分的数据库,或者为小租户提供共享池、为大型或受监管的租户提供专属独占部署。
不要为整个产品只选择一种模型。正确答案通常是一种与你的定价层级相映射的混合模型。把长尾的众多小租户放入一个高效的共享池中,让其经济模型能够成立。为愿意为隔离和合同保障付费的企业客户提供专属或单租户部署,作为高端层级。把这种映射关系写成一份架构决策记录(第 3.11 章),因为”哪些租户共享什么”是一项你的安全、销售和财务团队都要依赖的断言。
有意识地划分数据,让租户作用域不可能被遗忘
数据是多租户成败的关键所在,因此应把分区方式的选择视为一项核心的数据架构决策(第 3.4 章)。三种策略与租户模型相对应。每租户独立数据库提供最强的隔离性,易于按租户备份和恢复,数据导出也很简单,但代价是要运维众多数据库,模式迁移需要逐一分发。共享数据库内每租户独立模式(schema)是一条折中路径:单一服务器,逻辑上分隔,但仍有大量对象需要迁移。由租户列作为键的共享表()每一行都携带一个 tenant_id()是密度最高、成本最低,也是最危险的方案,因为此时任何一条遗漏了租户过滤条件的查询都会在客户之间泄露数据。
如果你选择共享表,就不要依赖开发者记得添加过滤条件。要在一个无法被绕过的层面强制实施租户作用域:数据库层面的行级安全机制,根据会话的租户为每条查询附加强制性的谓词条件;或者一个自动注入租户子句的 ORM 或数据访问层;又或者两者兼备。这里”既系皮带又系背带”是正确的做法。随着租户增多,按租户进行分片就会变得顺理成章:把成组的租户分布到不同的数据库分片上,让任何单一实例都不承载所有人的数据,这同时也限制了单个分片故障的爆炸半径,并让你能够将一个大租户迁移到自己专属的分片上,而无需改变整体模型。
让租户上下文无处不在地传播,并纵深防御跨租户泄露
租户标识符必须伴随每一个工作单元流转。要在边界处(通常来自已认证的会话或子域名)建立该标识符,对其进行校验,并将其贯穿请求上下文、每一次下游服务调用、每一个数据库会话、每一个排队中的任务,以及每一行日志和每一项指标。最危险的空隙往往出现在异步环节:一个后台工作进程在处理任务时没有重新建立租户上下文,一个未按租户键控的缓存,一个信任调用方传入的租户标识符的 webhook 处理器。每一处都是把一个租户的数据提供给另一个租户的路径。
把跨租户隔离当作一项需要纵深防御的安全属性来对待,具体细节交给第 4.3 章处理。应用最小权限原则,使得即便某个组件被攻破,它也只能触及自己正在为之服务的那个租户。永远不要从客户端可控的输入中接受租户标识符用于授权决策;应从已认证的身份中推导出它。按租户对缓存、对象存储前缀和搜索索引进行命名空间隔离,使得一次键冲突不可能跨越边界。然后有意地测试这条边界:编写自动化测试来断言租户 A 的凭据无法读取租户 B 的记录,并定期开展红队演练,尝试从一个租户中”越狱”出去。由你自己的测试套件发现的泄露是一个缺陷;由客户发现的泄露则是一场危机。
用配额、限流和公平性机制来遏制吵闹邻居
当租户共享资源时,一个租户的流量峰值就会变成所有人的宕机。这个”吵闹邻居”问题并非边缘情形;它是共享池在负载下的默认行为。要从一开始就为此进行设计。为重要的资源(每秒请求数、并发任务数、存储量、查询开销)设置按租户的配额,并在边界处和昂贵的内部关口通过限流来强制执行。优先采用能为每个租户分配一份份额的公平调度,而不是让一个租户饿死其余所有人的先到先服务队列。
要让执行方式与你的租户模型相匹配。在共享池中,配额和公平性是主要的防线,因此应对其投入资源。对于一个负载确实超出了公平共享所能吸收范围的租户,答案往往是把它从共享池中提升出来,进入混合或独占部署()这是一项你可以出售的功能,而非一次失败。把这项工作与你的可扩展性与韧性方面的工作(第 3.5 章)联系起来:负载削减、熔断器和背压都需要具备租户感知能力,这样削减一个租户的超额负载才能保护其他租户,而不是拖累整个系统。
用数据而非分叉代码来配置租户
每一位客户都会想要一些稍有不同的东西:他们的标志、他们的工作流规则、他们的集成、一个你没有的字段。能够可持续地满足这些需求的方式是按租户配置:特性开关、设置、权益(entitlement)和扩展点,这些都是数据,在运行时求值,并由同一套代码库共享。摧毁一家 SaaS 企业的路径是按租户编写定制代码:为某个大客户在代码路径中设一个分支、一个分叉或一个特例。有了十个这样的特例,你便不再拥有一个产品,而是拥有十个披着同一件风衣的产品,每一次变更都必须测试十遍。
要划出一条坚定的界线。把你愿意支持的变化维度建模为一等公民的配置项,把这些维度之外的请求要么视为产品路线图上的事项,要么坚决说不。当客户确实需要真正的定制时,给他们扩展点(webhook、一个 API、插件、自定义字段),让他们的逻辑得以运行,而不必分叉你的代码。把真正定制化的部署留给单租户高端层级,在那里隔离正是卖点所在,更高的价格覆盖了运维成本。
让租户生命周期自动化、可观测、且具备成本归因
租户的入驻应当是一个自助式、自动化的流程:供给租户的数据分区、填充默认值、设置权益,并在数秒之内就绪,而不是提交给运维团队的一张工单。退出同样重要,却更容易被忽视。当一个租户离开时,你必须能以可用的格式导出他们的数据,然后可证明地删除,因为合同和隐私法都会要求两者兼备。要在第一天就设计好数据导出和删除;事后把它们补进一个共享表的模式里会非常痛苦。
要按租户对一切进行埋点。用租户标识符为日志、追踪和指标打标签,这样你就能在数秒内回答”这次故障是波及所有租户还是只有一个?“以及”哪个租户在推高这项成本?“。把基础设施成本归因到各租户,这样你就能了解每个客户的真实利润率,并能识别出在当前定价下因其使用量而变得不划算的租户(第 9.4 章)。具备租户感知能力的可观测性和成本归因,能把多租户从一个黑盒变成一个你真正能够运营和定价的系统。
权衡:利与弊
| 租户模型 | 优点 | 缺点 |
|---|---|---|
| 单租户独占(每租户专属技术栈) | 隔离性最强,爆炸半径最小,按租户合规和导出容易,吵闹邻居问题简单 | 成本最高,每租户存在闲置容量,需要运维和打补丁的副本众多 |
| 共享池(完全共享) | 边际成本最低,密度最高,只需扩展和升级一套系统 | 隔离性完全依赖代码的正确性,吵闹邻居风险最大,按租户导出和删除最困难 |
| 混合(分层混合) | 对小租户高效,对大租户提供专属隔离,与定价相映射 | 需要构建和运维更多模型,需要维护层级间的升级路径 |
| 共享数据库、共享模式(租户列) | 存储成本最低,只需一次迁移,运维最简单 | 单个遗漏的过滤条件即可泄露数据;需要行级安全作为最后一道防线 |
| 共享数据库、每租户独立模式 | 逻辑隔离,单一服务器,导出效果尚可 | 模式对象众多,迁移需要逐一分发,每台服务器的扩展有上限 |
| 每租户独立数据库 | 数据隔离性强,可按租户备份和导出 | 数据库数量众多,迁移需要逐一分发,成本更高 |
贯穿每一行的核心张力都是隔离与效率之争。更密集的共享会同时倍增你的利润率和你的风险;更强的隔离则以实实在在的每租户成本换取安全性与简单性。解决之道不是选择一个极端,而是有意识地把每个租户沿光谱放置到合适位置,通常按层级划分:在经济性要求且风险有限的地方,密集地打包小租户;在大型和受监管的租户愿意为此付费、且爆炸半径必须很小的地方,为其提供隔离。然后通过强制实施的租户作用域来让密集共享的一端变得安全,通过自动化来让隔离的一端变得便宜,这样两端都不会像朴素版本那样代价高昂。
与团队讨论的问题
如果明天有一条租户作用域过滤条件缺失了,客户会看到另一个客户的数据吗? 这是区分一个站得住脚的多租户产品与一场即将发生的事故的问题。诚实的检验方法是追踪一条真实的读取路径,并追问是什么在强制执行租户边界:是一条开发者手写的 WHERE 子句,还是有一道后盾()比如数据库行级安全,或者一个无论如何都会注入作用域的数据访问层?带上你真实的查询路径、你的后台任务和你的缓存,因为泄露几乎总是藏在没人设置作用域的那个异步角落里。你还应该带上一项测试的结果()该测试刻意使用租户 A 的会话去请求租户 B 的记录,并断言会被拒绝。如果这条边界仅仅依赖人的警惕性,那么你就存在一个潜在的漏洞,而修复方案(纵深防御)应当立即跃升到待办事项列表的顶部。
每个客户层级实际使用的是哪种租户和数据分区模型,它与我们向客户承诺的内容相符吗? 许多团队会在不知不觉中默认漂移到单一模型,然后才发现他们的定价和架构互相矛盾:企业客户被承诺了共享池无法提供的隔离,或者小客户被安置在昂贵的专属技术栈中,拖垮了利润率。把每个层级映射到其真实的模型(单租户独占、共享池,或混合;每租户数据库、模式,或共享表),并将其与销售团队在合同中做出的数据保障承诺并列比较。凡是出现分歧之处,你面临的要么是合规风险,要么是成本问题,二者都值得在客户或审计员发现之前主动揭示。需要带来的证据是层级到模型的映射关系、按租户的成本,以及你企业合同中的实际措辞。
当一个大租户的负载激增时,谁会受到波及,我们的应对计划是什么? 在共享池中,答案往往是”所有人”,而团队常常直到发生一次事故才意识到这一点。走一遍当你最大的租户运行一次批量导入或遭遇流量激增时会发生什么:按租户的配额和公平调度能否遏制住它,负载削减能否保护邻居,还是整个系统会一起退化?带上负载数据和你上一次吵闹邻居事故的经过,因为伤害你的那个租户通常是可以指名道姓的。这个答案应当同时塑造你的限流投入和分层策略,因为对于一个已超出公平共享能力范围的租户,最干净的解决方案通常是将其提升进入一个你可以为之收费的混合或专属部署。
当一个租户明天退出时,我们能交给他们一份干净的导出,并证明已删除了每一处痕迹,还是会手忙脚乱? 退出是团队最容易忽视的租户生命周期环节,直到合同退出条款或隐私法请求逼迫他们直面这个问题,而到那时,共享模式会让提取和删除变得十分痛苦。对大型团队而言,风险会被放大,因为一个租户的数据分散在主数据库、缓存、对象存储、搜索索引、备份和分析管道之中,每一处都必须以可用的格式导出,然后被可证明地清除。这里存在着真实的相互竞争的考量:那些带来廉价存储的密集共享表,恰恰是让按租户提取和删除变得最困难的表,因此你在存储层节省下来的效率,可能要在退出环节偿还回去。带上一次针对真实租户的退出流程的现场演示、持有租户数据的每一个存储清单,以及你能拿出来证明删除确实发生过的证据。对于企业和政府租户,导出必须经过认证,删除必须可证明,以满足公共记录和隐私法规的要求,因此应把缺失的删除路径当作一个需要立即解决的合规缺陷,而不是等客户离开时才添加的功能。
我们的代码路径中已经存在多少个针对个别客户的一次性特例,我们拒绝逾越的界线又划在哪里? 按租户分叉代码是一家 SaaS 企业悄无声息地从”一个产品”变成”许多个共用一个名字的产品”的方式,在这种情况下,每一次变更都必须针对每一个特例进行测试,随着客户增多,迭代速度会不断衰减。这里的张力在于,一个有真实需求的大客户很难拒绝,而在代码路径中开一个分支感觉比构建一套配置能力更快,于是特例便以”一个又一个合理的例外”的方式不断累积。带上一份诚实的清单:在代码库中搜索客户名称和特定层级的分支,统计它们的数量,并估算每一个特例给一次无关变更所带来的额外测试和评审成本。讨论应当在两者之间划出一条坚定的界线:一类是你建模为一等公民配置项的变化(特性开关、权益、扩展点),另一类是你保留给单租户高端层级、由更高价格覆盖运维成本的真正定制化工作。对于要求深度定制的企业买家,持久的答案是提供能够运行其逻辑、而无需分叉你的代码的扩展点,这样治理和审计在整个客户群中才能保持可控。
我们能否按租户分辨出一次事故让谁付出了什么代价,以及哪些客户在当前定价下是不划算的? 一旦你的日志、追踪、指标和基础设施成本不携带租户维度,多租户就会立刻变成一个黑盒,因为届时你既无法回答一次故障是一个租户的问题还是整个客户群的问题,也无法指出哪个租户的使用量使其按合同价格来看是亏损的。对大型团队而言,这种归因能力正是”运营这个平台”与”凭猜测运营”之间的分野,并直接塑造着可靠性响应和定价两方面的工作。相关考量会与埋点成本和基数相互制衡:按租户为一切打标签并非没有代价,高基数指标会给你的可观测性预算带来压力,因此你需要审慎地选择按租户切分哪些内容、对哪些内容进行采样。带上你当前的租户标签覆盖率、一条能把云成本归因到单一租户的真实查询,以及能让你说出你利润率最低的客户是谁的利润表。在企业和政府场景中,按租户的成本和限定审计范围的可观测性还会反哺计费分摊(chargeback)、容量规划,以及每个机构或业务单元有权享有的审计边界,因此租户维度既是一项运营需求,也同样是一项治理需求。
行业视角
初创企业。 从第一天起就交付单一的共享池,并把你稀缺的工程注意力投入到那件事后无法补救的事情上:强制实施的租户作用域。一个带行级安全的托管 Postgres、每张表上的 tenant_id,以及在边界处解析的租户上下文,能在不需要一个运维团队的情况下为你带来安全的密度。不要投机性地构建单租户独占层级或按租户的基础设施;只有当一个付费的企业潜在客户让隔离的代价物有所值时,才添加混合层级。
中小企业。 在没有平台专家、预算又紧张的情况下,应当依靠你的云平台和框架已经提供的能力,而不是自己动手构建隔离机制。带行级安全的托管数据库、能为你划分租户作用域的平台即服务,以及携带租户身份信息的身份认证提供方,通常比手工打造的等价物更便宜、更安全。在每一层都把多租户视为一项”买还是造”的决策,把定制工作留给只有你自己能编写的租户边界测试。
大型企业。 问题在于跨多个团队的组合治理:一种分层混合架构、一份把每个层级映射到其租户和数据分区模型的架构决策记录,以及按租户的成本归因,让财务了解每个账户的真实利润率。要将租户上下文的传播和作用域后盾标准化,使得没有一个团队需要重新发明它们;明确地为隔离和生命周期自动化编列预算;并保留一条有支持、有定价的路径,供租户在成长或合规需求变化时在层级之间无停机迁移。
政府。 采购、数据驻留和公共问责驱动着模型的选择。用策略即代码(policy-as-code)把每个机构的数据钉在境内区域,把敏感或高密级的工作负载独占部署到拥有自己审计边界的独立账户中,并为每个机构提供自己的身份集成、保留规则和审计轨迹,使得一个机构的审计员永远看不到另一个机构的活动。退出必须产出经过认证的导出和可证明的删除,以满足公共记录和隐私法规,你所签署的租户保障承诺,也必须是你的架构真正能够兑现的。
示例
初创企业。 一家十五人的初创公司从第一天起就把产品构建为单一的共享池,这个选择是正确的。所有租户共享一个带有行级安全的托管 Postgres 数据库,每张表上都有 tenant_id,行级安全在数据库层面强制实施租户谓词,因此一个被遗忘的过滤条件不可能造成泄露;应用在边界处从子域名解析出租户,并将其贯穿每一次请求和后台任务。入驻是自助式的:一次新的注册会供给其租户记录、填充默认值,并在数秒内上线。两名工程师就能运维整个平台,因为只有一套系统需要运营。当他们的第一位真正的企业潜在客户要求专属数据库和合同性的隔离保障时,他们添加了一个混合层级:同一套代码库,但这个租户拥有自己专属分片上的自己的数据库,售价覆盖了成本。
大型企业。 一家为大型金融机构提供服务的 SaaS 供应商运行着一套分层混合架构。数以千计的中小型客户生活在按租户分片的区域性共享池中,配额和公平调度控制着吵闹邻居。顶级银行客户获得隔离云账户中的单租户部署,拥有专属数据库、按租户的加密密钥,以及写入主协议中的合同性数据驻留和隔离保障。一项平台能力可以在租户成长或合规需求变化时,在层级之间无停机地迁移该租户。每个租户的成本都通过标签归因,让财务了解每个账户的真实利润率,具备租户标签的可观测性让值班工程师能在数秒内分辨出一个告警是一个租户的问题,还是整个客户群的问题。
政府。 一家国家级平台提供商将众多政府机构作为独立租户托管,并把隔离当作一项法律要求,而非一种偏好。数据驻留法规将每个机构的数据钉在境内区域,由策略即代码强制执行,阻止任何资源出现在不被允许的区域(第 3.11 章)。密级驱动着模型的选择:处理敏感材料的机构获得完全独占的部署,位于拥有自己审计边界的独立账户中,而较低密级的工作负载则共享一个受治理的共享池。每个机构都是其自身的租户,拥有自己的身份集成、自己的保留和导出规则,以及一条限定在其边界内的审计轨迹,因此一个机构的审计员永远看不到另一个机构的活动。退出会产出经过认证的数据导出和可证明的删除,因为这些记录受公共记录和隐私法规的约束。
商业案例:动机、投资回报率与总拥有成本
多租户的核心商业理由是利润率。单租户模型()为每个客户部署一份全新副本()意味着基础设施和运维成本大致随客户数量线性增长,你的工程师则把日子花在为众多副本打补丁上。共享的多租户模型打破了这种关联:你只需打一次补丁,扩展一套系统,并将客户密集地打包,使得下一个租户的边际成本趋近于零。这正是让一家 SaaS 企业收入增长速度远快于成本增长速度的原因,也是投资者和董事会把一个干净的多租户架构视为一家可扩展公司之代理指标的原因。
回报体现在三个方面:运营杠杆(一个团队运营整个客户群)、更快的交付速度(一次修复同时发布给所有租户,因此迭代速度不会随客户增多而衰减),以及定价灵活性(为长尾提供共享方案,为企业提供高端隔离方案,两者都来自同一套代码库)。要把这些回报与你必须诚实地拨付预算的成本相权衡:构建强制实施的租户隔离、配额、生命周期自动化和按租户的可观测性所需的工程投入,以及保持边界完整所需的纪律。做错这件事的代价是不对称且严重的,因为单一一次跨租户数据泄露就可能引发监管处罚、大规模客户流失,以及远超你通过共享节省下来的基础设施成本的声誉损害。向领导层陈述这个案例时,应把上行空间描述为利润率和可扩展性,把下行风险描述为生存危机。要把服务水平协议和合同性的数据保障锚定在你的架构真正能够交付的租户模型上,因为一个你的架构无法兑现的承诺是一项负债,而不是一笔生意。
反模式与陷阱
- 靠约定实现租户作用域。 依赖开发者记得在每条查询上添加租户过滤条件,没有数据库层面或数据访问层的后盾;一次遗漏就是一次泄露。
- 信任客户端提供的租户标识符。 从请求输入中接受租户用于授权决策,而不是从已认证的身份中推导,这会让调用方能够请求别人的数据。
- 未加限定作用域的后台工作。 任务、缓存、webhook 和导出丢失了租户上下文,因为有人只为同步请求路径设置了作用域。
- 按租户分叉代码。 为某个大客户在代码路径中开特例,直到你在同一个名字下维护着许多发生分歧的产品,每一次变更的成本都变成了十倍。
- 没有吵闹邻居防御机制。 运行一个没有按租户配额或公平性机制的共享池,让第一个流量激增的租户拖垮所有人。
- 把退出当作事后补救。 构建系统时没有数据导出和可证明的删除,之后在客户的退出条款或隐私法请求面前失败,因为共享模式让提取变得痛苦。
- 按租户盲飞。 日志、指标和成本都不携带租户维度,因此你无法分辨这是谁的事故,也无法分辨哪个租户是不划算的。
成熟度模型
- 第 1 级,启动: 多租户是即兴且被动应对的。租户隔离依赖手写的过滤条件,没有后盾,模型是”一刀切”的,没有配额,入驻是手工的,日志和成本都不携带租户维度。团队是从事故和险些酿成泄露的经历中学到吵闹邻居问题的。
- 第 2 级,发展: 基本做法开始出现,但在各团队之间并不一致。已经选定了一种租户模型,租户上下文在主请求路径中得到传播,一道数据库层面或数据访问层的后盾在核心表上强制实施作用域,基本的按租户配额也已存在。入驻部分实现了自动化,日志携带租户标识符,但异步路径、导出和成本归因因服务而异,且没有任何文档记录。
- 第 3 级,标准化: 租户模型的方法已被记录并在整个组织中强制执行。分层的租户模型与定价相映射,小租户使用共享池,企业和受监管租户使用隔离部署;租户作用域被纵深地强制实施,并被有意地测试;配额和公平调度控制着吵闹邻居;包括导出和可证明删除在内的租户生命周期已经自动化;可观测性和成本都按租户切分。每个团队都遵循同一套租户标准,而不是各自为政。
- 第 4 级,管理: 租户体系被对照基线进行度量和控制。按租户的隔离性、公平性、延迟和成本被作为指标进行跟踪,并设有共识目标:跨租户测试覆盖率、配额违规和吵闹邻居事故的发生率、入驻和退出耗时、按租户的利润率都被对照基线进行汇报,将租户提升到更高层级或对一个不划算的租户重新定价的决策都基于这些证据而非轶事。驻留和密级的合规性被持续监控,偏离标准会触发一套有文档记录的应对流程。
- 第 5 级,编排: 租户模型被持续改进并在整个组织中集成。跨租户边界通过常态化的红队演练接受检验,租户在成长或合规需求变化时可以无停机地在层级之间移动,按租户的利润率反哺定价和容量规划,驻留和密级规则由策略而非评审来强制执行。模型会随着客户构成和监管形势的变化而调整,所学到的经验教训作为同一个闭环反哺产品、安全和财务。
讨论思路
- 你的每个客户层级今天在隔离与效率的光谱上分别位于何处?是否有任何一个层级用错了模型,不符合你所承诺的保障或你所需要的利润率?
- 如果你必须向审计员证明租户 A 无法访问租户 B 的数据,你现在能拿出什么证据,其中有多少是自动化的、又有多少只是口头断言?
- 你的哪些异步路径(任务、缓存、webhook、导出、搜索索引)会重新建立租户上下文,哪些只是继承上下文或信任调用方?
- 当一个租户超出公平共享的能力范围时,你是否拥有一条有支持、有定价的提升路径,进入混合或专属部署,还是默认的答案就是一次事故?
- 你能否把基础设施成本归因到具体租户,精确到能够说出你利润率最低的客户是谁,这是否会改变你的定价方式?
- 对于一个政府或受监管的租户,你能否通过策略来强制实施数据驻留和密级驱动的隔离,并以经过认证的导出和可证明的删除来完成其退出?
关键要点
- 多租户()由一个实例服务众多租户()是 SaaS 的经济引擎:它驱动着你的利润率,也把跨租户隔离置于你架构的核心位置。
- 把隔离与效率之争视为一个光谱并对其分层:把小租户打包进一个高效的共享池,把大型和受监管的租户隔离在他们愿意为之付费的混合或独占部署中。
- 绝不要让租户作用域仅仅依赖于一条手写的过滤条件;用行级安全或数据访问层进行纵深实施,并有意地测试这条边界。
- 让租户上下文贯穿每一次请求、每一个任务、每一个缓存和每一条日志,并防御那些泄露容易藏身的异步路径。
- 用按租户的配额、限流和公平调度来遏制吵闹邻居,并把那些超出共享池能力的租户提升出去,而不是任由他们拖累共享池。
- 用按租户的配置而非按租户的代码来弯曲产品形态,并让租户生命周期以及按租户的可观测性与成本归因成为一等公民。
参考资料与延伸阅读
- Tom Kwok, Thao Nguyen, and Linh Lam, A Software as a Service with Multi-tenancy Support for an Electronic Contract Management Application (IEEE International Conference on Services Computing)
- Frederick Chong and Gianpaolo Carraro, Architecture Strategies for Catching the Long Tail (Microsoft)
- Amazon Web Services, SaaS Lens, AWS Well-Architected Framework and SaaS Tenant Isolation Strategies
- Microsoft, Multitenant SaaS architecture and patterns (Azure Architecture Center)
- Google Cloud, Architecture for Multi-tenant SaaS Applications
- Cor-Paul Bezemer and Andy Zaidman, Multi-Tenant SaaS Applications: Maintenance Dream or Nightmare? (Proceedings of the Joint ERCIM Workshop on Software Evolution)
- The Open Web Application Security Project, OWASP Application Security Verification Standard (access-control and multi-tenancy requirements)
- Martin Kleppmann, Designing Data-Intensive Applications (partitioning, sharding, and data isolation)