跳至正文
Starone

参考架构

示意

多租户 SaaS 平台

一种 SaaS 架构,将控制平面与应用平面分离,并让租户身份贯穿从边缘到数据存储的每一层。

问题

设想一款销售给众多组织的软件产品,客户既有使用自助套餐的小团队,也有带着安全问卷和采购流程的大型企业。所有客户都期望自己的数据对其他任何客户不可见,而大客户还期望有单点登录、审计日志和可预期的性能。

为每个客户单独部署一套,容易理解,但运行和升级的成本很高。让所有客户共用一套部署,效率高,却把隔离、资源的公平使用和按客户配置都变成了工程问题。架构必须决定每一档客户处在这条光谱的哪个位置。

设计必须满足的需求

  • 一个租户绝不能读取或修改另一个租户的数据,而且这一点必须在应用代码之下的层面强制执行。
  • 新租户必须能够自动开通,不需要工程师执行手工步骤。
  • 企业租户必须能够使用自己的身份提供方,并通过自动化的账号开通来管理用户。
  • 一个租户的高负载使用不得拖累其他租户的体验。
  • 功能、限额和计费必须由租户的套餐决定,并且无需部署即可调整。
  • 用量、错误和性能必须能够归属到具体的租户。

设计

组件

多租户 SaaS 架构
  1. 租户感知的边缘

    • 自定义域名
    • 租户解析
    • WAF
    • 按租户限流
  2. 身份与 SSO

    • SAML 与 OIDC
    • SCIM 账号开通
    • 角色与权限
    • MFA
  3. 控制平面

    只管理租户,从不承载其流量

    • 租户生命周期
    • 套餐与权益
    • 计费与计量
    • 功能开关
  4. 应用平面

    • 无状态服务
    • 后台任务
    • 租户上下文
    • Webhook
  5. 数据隔离

    按套餐层级选择隔离模型

    • 行级安全
    • 每租户独立 Schema
    • 专属数据库
    • 每租户独立密钥
  6. 按租户的可观测性

    • 用量指标
    • 带租户标签的链路
    • 吵闹邻居告警
    • 审计日志
控制平面管理租户、套餐与计费,应用平面为租户提供服务。租户身份在边缘解析,并贯穿每一层,直至数据存储和遥测数据。
01

租户感知的边缘

边缘根据子域名、自定义域名或令牌解析请求属于哪个租户,并拒绝无法归属的请求。它按租户限流,使某个客户的流量突增被控制在其自身范围内。自定义域名及其证书会自动签发和续期。

02

身份与单点登录

身份层为较小的客户提供密码加 MFA 登录,为企业客户提供 SAML 或 OIDC 联合登录以及 SCIM 账号开通。角色和权限按租户定义。每个签发的令牌都以签名声明的形式携带租户标识,下游服务信任这一声明,而不采信任何由客户端提供的值。

03

控制平面

控制平面负责租户的生命周期:注册、开通、套餐变更、暂停和删除。它保存套餐和权益的目录,是每个租户可以使用哪些功能的事实来源。它与应用平面分开部署、分开扩展,因此一方发生故障不会拖垮另一方。

04

应用平面

无状态服务处理产品流量,并在每个请求开始时,根据经过验证的令牌一次性建立租户上下文。该上下文会传递到数据库会话、后台任务、缓存和对外发出的事件。工作队列经过分区,使某个租户的积压不会让其他租户的任务得不到处理。

05

数据隔离

标准租户共用一个 PostgreSQL 数据库,其中的行级安全策略把每一条查询限制在当前租户范围内。要求更严格的租户可以被放入专属的 Schema 或专属的数据库,由路由层负责选择。缓存键、对象存储路径和搜索索引都按租户加前缀或分区。

06

计量与计费

服务发出用量事件,这些事件按租户聚合为套餐定价所依据的各项数量。计费集成通过支付服务商把这些数量转化为账单,权益检查也读取同一份数据来执行限额。用量事件是幂等的,因此重试不会导致重复计费。

07

按租户的可观测性

每一行日志、每一个指标和每一条链路追踪都带有租户标识。这让支持人员可以看到某一个客户的使用体验,也让工程师能在其他客户察觉之前发现“吵闹的邻居”。安全相关操作的审计日志向每个租户的管理员开放。

取舍

设计决策

决策理由
默认采用共享数据库加行级安全每个租户一个数据库可以提供最强的隔离,但成本、迁移时间和运维工作量会随着每一个客户成倍增加。共享加行级安全让实例规模保持精简、升级保持简单,并依靠由数据库强制执行的策略,使应用代码中漏掉一个过滤条件不至于演变成数据泄露。
专属 Schema 或数据库作为一个档位提供,而不是基线出于合同或数据驻留方面的原因,有些客户需要共享模式无法提供的隔离。支持不止一种隔离模型会增加一个路由层,并使迁移更复杂,因此只留给那些需求确实值得这样做的租户。
控制平面独立于应用平面把租户管理并入产品服务,起步阶段构建更快。把它分离出来意味着要部署两套系统,换来的是开通、权益和计费有了明确的归属,可以按自己的节奏变更,出故障时也不影响客户流量。
租户上下文只从签名令牌中获取从请求头或请求体接受租户标识很方便,却是跨租户访问缺陷的常见来源。只从经过验证的声明中获取,可以消除这一类错误,代价是一些内部工具和支持流程需要更有意识地设计。
权益在运行时判定,而不是按套餐分别部署为不同套餐交付不同的构建版本,会使测试碎片化并拖慢发布。单一代码库加运行时权益检查,会在请求路径上增加一次查询,而这次查询有缓存,并且让套餐和限额可以不经发布就调整。

预期成效

  • 租户隔离由平台强制保证,而不是依赖开发者的自觉
  • 从注册到租户可用的开通过程实现自动化
  • 满足企业的身份要求,而无需为每个客户做定制开发
  • 用量和性能可以逐个租户地理解

该设计旨在达成的目标。仅为示意,并非客户项目的实测结果。

代表性技术

  • TypeScript
  • Next.js
  • Node.js
  • Spring Boot
  • PostgreSQL
  • Redis
  • Kafka
  • Kubernetes
  • Terraform
  • OIDC / SAML / SCIM
  • OpenTelemetry
全部参考架构

有一套必须在生产环境中正常运行的系统?

告诉我们您正在构建什么。工程师会阅读您的留言,并在两个工作日内回复。