在 Spanner 中实现多租户

本文档介绍了在 Spanner 中实现多租户的各种方法。此外,还讨论了数据管理模式和租户生命周期管理。

多租户是指软件应用的单个实例或少数实例为多个租户或客户提供服务。这种软件模式可以从单个租户或客户扩容到成百上千个。这种方法是云计算平台的基础,其中底层基础架构在多个组织之间共享。

您可以将多租户视为基于共享计算资源(例如数据库)的一种分区方式。类比是公寓建筑内的租户:共享基础架构,但具有专用的租户空间。多租户应用是最重要的(即便不是全部)软件即服务 (SaaS) 应用。

本文档适用于将 Spanner 上的多租户应用作关系型数据库来实现的架构师、数据架构师和工程师。使用该上下文,它概述了存储多租户数据的各种方法。“租户”、“客户”和“组织”这几个术语在文章中可互换使用,以指示访问多租户应用的实体。

作为示例,本文使用人力资源 (HR) SaaS 提供商来实现其在 Google Cloud 上的多租户应用。在此示例中,HR SaaS 提供商的多个客户必须访问多租户应用。这些客户称为租户。

Spanner 是 Google Cloud 的全代管式企业级分布式一致数据库,它结合了关系型数据库模型与非关系型数据库横向扩缩能力的优势。Spanner 具有关系语义 - 具有架构、强制数据类型、强一致性、多语句 ACID 事务,以及实现 ANSI 2011 SQL 的 SQL 查询语言。

Spanner 为计划内维护或区域故障提供零停机时间,可用性服务等级协议 (SLA) 提供 99.999% 的可用性。它提供高可用性和可伸缩性,支持现代多租户应用。本文讨论了使用 Spanner 实现多租户的不同架构方法。

租户数据映射条件的条件

在多租户应用中,每个租户的数据都是使用底层 Spanner 数据库中的多种架构方法进行隔离。以下列表概述了将租户的数据映射到 Spanner 所使用的不同架构方法:

  • 实例:一个租户专门位于一个 Spanner 实例中,该租户只有一个数据库。
  • 数据库:一个租户位于包含多个数据库的单个 Spanner 实例的一个数据库中。
  • 架构:一个租户位于数据库内的专有表中,并且多个租户可以位于同一数据库中。
  • :租户数据是数据库表中的行。这些表与其他租户共享。

上述条件称为数据管理模式,多租户数据管理模式部分对此进行了详细讨论。该讨论基于以下条件:

  • 隔离:多个租户的数据隔离程度是多租户的主要考虑因素。隔离取决于为其他类别的条件选择,例如,某些监管和合规性要求可能确定更大的隔离程度。
  • 敏捷性:就实例、数据库或表的创建而言,租户可以轻松进行添加和撤销活动。
  • 操作:实现典型的特定于租户的数据库操作和管理活动(例如定期维护、日志记录、备份或灾难恢复操作)的可用性或复杂性。
  • 扩缩:能够无缝扩缩来满足未来的发展需求。每个模式的说明都包含该模式可支持的租户数量。
  • 性能:能够为每个租户分配专有资源、解决相邻用户争用现象,以及为每个租户启用可预测的读写性能租户。
  • 法规和合规性:能够满足合规标准严格监管的行业及国家/地区(要求完全隔离资源和维护操作)的要求,例如:要求将个人身份信息实际上只存储在法国的法国数据驻留要求。

下一部分介绍了与这些条件相关的每种数据管理模式。为一组特定租户选择数据管理模式时,请使用相同的条件。

多租户数据管理模式

以下部分介绍了四种主要数据管理模式:实例、数据库、架构和表。

实例

为了提供完全隔离,实例数据管理模式会将每个租户的数据存储在自己的 Spanner 实例和数据库中。一个 Spanner 实例可以有一个或多个数据库。在此模式中,仅创建一个数据库。对于之前讨论的 HR 应用,系统会为每个客户组织创建一个单独的 Spanner 实例和一个数据库。

如下图所示,在数据管理模式下,每个实例有一个租户。

在实例数据管理模式下,每个实例有一个租户。

为每个租户使用单独的实例可让您使用单独的 Google Cloud 项目为不同租户实现单独的信任边界。此外还有一个好处,系统还会根据每个租户的位置(单区域或多区域)选择每个实例配置,以优化位置灵活性和性能。

该架构可以轻松扩缩到任意数量的租户。SaaS 提供商可以在所需区域中创建任意数量的实例,没有任何硬性限制。

下表概述了实例数据管理模式如何影响不同条件。

标准 实例 - 每个实例数据管理模式拥有一个租户
隔离
  • 最高隔离级别
  • 不共享任何数据库资源
敏捷性
  • 针对以下各项的入职和离职需要进行相当多的设置或停用操作:
    • Spanner 实例
    • 特定于实例的安全
    • 特定于实例的连接
  • 系统可通过基础架构即代码 (IaC) 自动完成入职和离职流程。
运维
  • 每个租户有独立备份
  • 单独的灵活备份时间表
  • 运营开销更高
    • 大量要管理和维护的实例(扩缩、监控、日志记录和性能调整)
扩缩
  • 高可扩缩性数据库
  • 通过添加节点可以无限增长
  • 租户数量不限
  • 每个租户可用的 Spanner 实例
性能
  • 单个实例中的每个租户
  • 无资源争用
  • 已为每个租户定制性能调整和问题排查
监管和合规性要求
  • 将数据存储在特定区域中
  • 实现企业或政府要求的特定安全、备份或审核流程

总而言之,关键要点如下:

  • 优点:最高级别的隔离
  • 缺点:最大运营开销

实例数据管理模式最适合以下各情况:

  • 不同的租户分布在广泛的区域中,需要本地化解决方案。
  • 某些租户的监管和合规性要求需要更高级别的安全和审核协议。
  • 租户大小有很大差异,因此在大量的高流量租户之间共享资源可能会导致争用且相互降级。

数据库

在数据库数据管理模式中,每个租户都位于单个 Spanner 实例中的数据库中。多个数据库可位于单个实例中。如果一个实例不足以满足租户的数量,可以创建多个实例。此模式意味着多个租户共享一个 Spanner 实例。

Spanner 的硬性限制限制为每个实例拥有 100 个数据库。此限制意味着,如果 SaaS 提供商需要扩容到超过 100 位客户,则客户需要创建和使用多个 Spanner 实例。

对于 HR 应用,SaaS 提供商会使用 Spanner 实例中单独的数据库创建和管理每个租户。

如下图所示,在数据管理模式下,每个数据库拥有一个租户。

在数据库数据管理模式下,每个数据库拥有一个租户。

在数据库数据管理模式下,可以在数据库级层上针对不同租户的数据实现逻辑隔离。但是,由于它是单个 Spanner 实例,因此所有租户数据库都共享同一区域配置以及底层计算和存储设置。

下表概述了数据库数据管理模式如何影响不同条件。

标准 数据库 — 每个数据库数据管理模式拥有一个租户
隔离
  • 在数据库级层实现完全的逻辑隔离
  • 共享底层基础架构资源
敏捷性
  • 需要投入精力创建或删除数据库以及任何特定的安全控制措施
  • 自动化入职和离职流程通过 IaC
运维
  • 每个租户有独立备份
  • 灵活的投放时间设定
  • 与实例模式相比,运营开销更低
    • 一个实例中最多可监控 100 个数据库
扩缩
  • 高可扩缩性数据库
  • 无限实例数目
  • 允许数千个节点
  • 每个实例最多有 100 个数据库
    • 每 100 个租户创建一个新的 Spanner 实例
性能
  • 多个数据库之间会出现的资源争用
    • 数据库分布在多个 Spanner 实例节点上
    • 数据库共享基础架构
  • 相邻用户争用会影响性能
监管和合规性要求
  • 位置区域必须与实例区域匹配才能满足任何特定数据驻留的监管要求

总而言之,关键要点如下:

  • 优点:隔离级别更高
  • 缺点:每个实例的租户数量有限;位置不灵活

数据库数据管理模式最适合以下各情况:

  • 多个客户的数据驻留在同一位置(例如法国或英国),并且/或者受到同一个监管机构的监管。
  • 租户需要基于系统的数据隔离和备份/恢复,但支持基础架构资源共享。

架构

在架构数据管理模式下,实现单个架构的单个数据库用于多个租户,而每个租户的数据使用一组单独的表。通过在表名称中将 tenant ID 添加为前缀或后缀,可以区分这些表。

与前面两种选项(实例管理模式和数据库管理模式)相比,在为每个租户使用一组单独的表这种数据管理模式下,隔离级别明显更低。在此模式下入职也更加简单,它涉及创建新的表以及关联的引用完整性和索引。

一个重要的警告是,通过 Identity and Access Management (IAM) 控制的 Spanner 访问权限仅在实例或数据库级层提供。不能在表级层提供访问权限。每个数据库的表数量上限为 5000 个。对许多客户而言,该限制会限制他们对应用的使用。

此外,对每个客户使用单独的表可能会导致大量架构更新操作积压。需要很长时间才能解决此类积压问题。

对于 HR 应用,SaaS 提供商可以为每个客户创建一组表,并在表名称中将 tenant ID 用作前缀,例如 customer1_employeecustomer1_payrollcustomer1_department

如下图所示,在架构数据管理模式下,每个租户使用一组表。

在架构数据管理模式下,每个租户使用一组表。

下表概述了架构数据管理模式如何影响不同条件。

标准 架构 - 每个租户数据管理模式拥有一组表
隔离
  • 隔离级别低
  • 无表级层安全性
敏捷性
  • 客户入职非常容易
    • 新建表
    • 创建关联的键和索引
  • 客户离职意味着删除表
    • 可能会对数据库中的其他租户造成临时负面影响
运维
  • 租户不需要进行单独的操作
  • 备份、监控和日志记录必须作为单独的应用功能或实用程序脚本来实现
扩缩
  • 数千个节点
  • 租户数量无限制
  • 单个数据库只能有 5000 个表
    • 每个数据库中租户的唯一楼层数量(5000/<租户的表数量>)
    • 当数据库中的表超过 5000 个时,请为其他租户添加一个新数据库
性能
  • 可能出现较高级别的资源争用情况
  • 通过为每组表单独设计索引来确保良好性能
监管和合规性要求
  • 位置区域必须匹配才能满足任何特定数据驻留的监管要求
  • 实现特定的安全性和审核控制措施会影响同一数据库中的所有租户

总而言之,关键要点如下:

  • 优点:入职流程非常简单
  • 缺点:运营开销更高;在表级层没有提供安全控制措施

架构数据管理模式最适合以下各情况:

  • 面向不同部门供应的内部应用,与简化维护相比,对数据进行严格的安全隔离并非突出问题。
  • 无需根据法律或法规要求对数据进行严格隔离的多租户应用。

虽然可以在数据库中创建多组表(每组代表一个租户),但从数据库的角度来看,这是最不理想的模式。主要原因是这些表必须遵循命名惯例。应用和任何数据库工具(例如 IDE 和架构迁移工具)都必须了解命名惯例。此外,如果每个租户的表数量相当大,架构数据管理模式不会提供显著扩缩。

更好的方法是转为让每个租户拥有一个数据库并增加实例数,或者改用表数据管理模式。

最终的数据管理模式为多个租户提供一组通用表。每个表包含多个租户的数据。这种数据管理模式表示在多个租户之间共享多租户的极端级别(从基础架构到架构再到数据模型)。在表中,行根据主键进行划分,其中 tenant ID 是键的第一个元素。从扩缩角度来看,Spanner 为此模式提供最佳支持,因为它可以无限扩缩表。

对于 HR 应用,工资表的主键可以是 customerIDpayrollID 的组合。

如下图所示,在表数据管理模式下,多个租户使用一个表。

在表数据管理模式下,多个租户使用一个表。

与架构模式类似,表模式下的数据访问权限无法分别针对不同的租户进行控制。使用较少的表意味着当每个租户都有自己的数据库表时,架构更新操作的完成速度更快。此方法在很大程度上简化了入职、离职和操作。

下表概述了表数据管理模式如何影响不同条件。

标准 表 - 在数据管理模式下,多个租户使用一个表
隔离
  • 隔离级别最低
  • 没有租户级层安全性
敏捷性
  • 入职时不需要进行数据库设置
    • 应用可以直接将数据写入现有表中
  • 离职意味着删除表中客户的行
运维
  • 租户无需进行单独的操作,包括备份、监控和日志记录
  • 随着租户数量的增加,开销很少,甚至没有
扩缩
  • 可扩缩到数千个节点
  • 可以适应任何级别的租户增长
  • 支持的租户数量不限
性能
  • 该模式非常适合 Spanner 环境
  • 内部分布式存储、处理和负载平衡操作可以轻松适应这种模式
  • 如果主键空间不是精心设计的,则有可能出现较高级别的资源争用情况(相邻用户争用)
    • 可以防止出现并发和分布问题
  • 遵循最佳做法非常重要
  • 删除租户的数据可能会对负载产生临时影响
监管和合规性要求
  • 位置必须符合任何特定的数据驻留监管要求
  • 该模式不能提供系统级层的分区(与实例或数据库模式相比)
  • 实现任何特定的安全性和审核控制措施会影响所有租户

总而言之,关键要点如下:

  • 优点:高度可扩缩;运营开销较低
  • 缺点:资源高度争用;每个租户缺少安全控制措施

此模式最适合以下各情况:

  • 面向不同部门供应的内部应用,与简化维护相比,对数据进行严格的安全隔离并非突出问题。
  • 使用免费层级应用功能最大程度地增加租户的资源共享范围,同时减少资源预配。

数据管理模式和租户生命周期管理

概括来讲,下表比较了所有标准中的各种数据管理模式。

实例 数据库 架构
隔离 完成 完成 最低
敏捷性 最高
操作简便性
扩缩 有限 可能非常有限
性能* 可能很高
法规和合规性 最高

* 性能在很大程度上取决于架构设计查询最佳做法。此处的值只是平均预期值。

特定多租户应用的最佳数据管理模式可满足基于标准的大部分要求。如果不需要特定标准,您可以忽略其所在的行。

结合使用的数据管理模式

通常,单个数据管理模式足以满足多租户应用的要求。在这种情况下,该设计可以采用单一数据管理模式。

但某些多租户应用需要同时使用多种数据管理模式,例如,支持免费层级、多租户层级和企业层级的多租户应用。

  • 免费层级:

    • 必须经济实惠
    • 必须设置数据量上限
    • 通常支持有限功能
    • 表数据管理模式是良好的免费层级候选项:
      • 租户管理很简单
      • 无需创建特定的或专属的租户资源
  • 常规层级

    • 适用于没有特别强烈的扩缩要求或隔离要求的付费客户
    • 架构数据管理模式或数据库数据管理模式是良好的标准层级候选项
      • 表和索引专用于租户
      • 在数据库数据管理模式下,备份很轻松
      • 架构数据管理模式不支持备份
        • 租户备份必须作为 Spanner 外部的实用程序实现
  • Enterprise 层级

    • 通常是高端层级,在各个方面实现完全自主性
    • 租户拥有专用的资源,资源包括专用的扩缩和完全隔离机制
    • 实例数据管理模式非常适合企业层级

最佳做法是将不同的数据管理模式保留在不同的数据库中。尽管可以在 Spanner 数据库中组合不同的数据管理模式,但这样做很难实现应用的访问逻辑和生命周期操作。

应用设计部分概述了使用一种数据管理模式或多种数据管理模式时适用的一些多租户应用设计注意事项。

管理租户生命周期

租户具有生命周期。因此,您必须在多租户应用中实现相应的管理操作。除了创建、更新和删除租户等基本操作之外,请考虑以下与数据相关的其他操作:

  • 导出租户数据

    • 删除租户时,最好是先导出其数据,并使数据集可供访问。
    • 使用表或架构数据管理模式时,多租户应用系统必须实现将数据导出或映射到数据库的功能(数据库导出)。
  • 备份租户数据

    • 使用实例或数据库数据管理模式并为个别租户备份数据时,请使用数据库的导出或备份功能。
    • 使用架构或表数据管理模式以及为单个租户备份数据时,多租户应用必须实现此操作。Spanner 数据库无法确定哪些数据属于哪个租户。
  • 移动租户数据

    • 切换租户的数据管理模式(或在实例之间或数据库之间切换使用同一数据管理模式的租户)需要从表数据管理模式中提取数据,然后将该数据插入数据库数据管理模式。

    • 缓解相邻用户争用形势是移动租户的另一个原因。

应用设计

在设计多租户应用时,请实现租户感知业务逻辑。这意味着应用每次运行业务逻辑时,都必须始终位于已知租户的上下文中。

从数据库的角度来看,应用设计意味着必须根据租户所处的数据管理模式运行每个查询。以下各部分重点介绍多租户应用设计的一些主要概念。

动态租户连接和查询配置

将租户数据动态映射到租户应用请求时会使用映射配置:

  • 对于数据库数据管理模式或实例数据管理模式,连接字符串便足以访问租户的数据。
  • 对于架构数据管理模式,必须确定正确的表名称。
  • 对于表数据管理模式,必须对数据库执行查询。使用适当的谓词来检索特定租户的数据。

租户可以处于四种数据管理模式中的任何一种模式。下面的映射实现方案解决了同时使用所有数据管理模式的多租户应用在一般情况下的连接配置。当给定租户使用一种模式中时,某些多租户应用会针对所有租户使用一种数据管理模式。以下映射隐式涵盖了此情况。

如果租户执行业务逻辑(例如,员工使用租户 ID 登录),则应用逻辑必须确定租户的数据管理模式、指定租户 ID 数据的位置,以及(可选)表命名约定(适用于架构模式)。

此应用逻辑需要租户到数据管理模式的映射。在以下代码示例中,connection string 是指租户数据所在的数据库。该示例标识 Spanner 实例和数据库。对于数据管理模式实例和数据库,以下代码足以连接和执行查询:

tenant id -> (data management pattern,
             database connection string,
             [table_prefix])

架构和表数据管理模式需要进行额外设计。

架构数据管理模式

对于架构数据管理模式,同一数据库中有多个租户。每个租户都有自己的一组表。这些表通过其名称进行区分。哪个表属于哪个租户是确定的。

一种方法是在表名称前面加上租户 ID,例如,对于 ID 为 356 的租户,EMPLOYEE 表称为 T356_EMPLOYEE。在将查询发送到映射返回的数据库之前,应用必须先在每个表的前面添加前缀 Ttenant ID

另一种方法是将 table_prefix 添加到查询使用的映射开头,以便它为租户找到正确的表。

您也可以使用混合方法:如果数据管理模式是架构模式,并且表前缀为空,则系统会执行默认映射(在表名称的前面添加租户 ID)。

表数据管理模式

表数据管理模式需要类似的设计。在此模式中,只有一个架构。租户数据存储为行。如需正确访问数据,请在每个查询中附加谓词以选择适当的租户。

如需查找适当的租户,一种方法是让每个表中都有一个名为 TENANT 的列。列值为 tenant ID。每个查询必须将谓词 AND TENANT = tenant ID 附加到现有的 WHERE 子句,或者添加具有谓词 AND TENANT = tenant IDWHERE 子句。

为了连接到数据库并创建适当的查询,租户标识符必须在应用逻辑中可用。它可以作为参数传入或存储为线程上下文。

某些生命周期操作要求您修改租户到数据管理模式的映射配置 - 例如,在数据管理模式之间移动租户时,必须更新数据管理模式和数据库连接字符串。您可能还需要更新表前缀。

查询生成和归因

多租户应用的基本底层原则是多个租户可以共享单个云资源。上述数据管理模式属于此类别,但单个租户被分配给单个 Spanner 实例的情况除外。

资源共享不仅仅是共享数据。还会共享监控和日志记录 - 例如,在表数据管理模式和架构数据管理模式中,针对所有租户的所有查询都记录在同一审核日志中。

如果已记录某个查询,则必须检查查询文本以确定针对哪个租户执行了查询。在表数据管理模式下,您必须解析谓词。在架构数据管理模式下,您必须解析其中一个表名称。

在数据库数据管理模式或实例数据管理模式中,查询文本没有任何租户信息。如需获取这些模式的租户信息,您必须查询租户到数据管理模式映射表。

通过确定给定查询的租户而不解析查询文本,可以更轻松地分析日志和查询。为了统一识别所有数据管理模式中的查询,一种方法是向具有 tenant ID 和(可选)label 的查询文本添加注释。

以下查询选择由 TENANT 356 识别的租户的所有员工数据。为了避免解析 SQL 语法并从谓词中提取租户 ID,系统会将租户 ID 作为注释添加。您无需解析 SQL 语法即可提取注释。

select * from EMPLOYEE
  -- TENANT 356
  where TENANT = 'T356';

select * from T356_EMPLOYEE;
  -- TENANT 356

采用这种设计时,对租户运行的每个查询都会归因于该租户,而与数据管理模式无关。如果已切换租户的数据管理模式,则查询文本可能会更改,但归因在查询文本中保持不变。

上述代码示例只是其中一种方法。另一种方法是将 JSON 对象作为注释而非标签和值插入:

select * from T356_EMPLOYEE;
  -- {"TENANT": 356}

租户访问生命周期操作

根据您的设计理念,多租户应用可以直接实现前面介绍的数据生命周期操作,它也可以创建单独的租户管理工具。

与实现策略无关,生命周期操作可能必须在没有同时运行应用逻辑的情况下运行,例如,将切换租户的数据管理模式时,应用逻辑无法运行,因为数据不在单个数据库中。当数据位于单个数据库时,从应用的角度来看,需要执行两项额外操作:

  • 停止租户:停用所有应用逻辑访问权限,同时允许执行数据生命周期操作。
  • 启动租户:应用逻辑可以访问租户的数据,同时将停用会影响应用逻辑的生命周期操作。

虽然不常使用,但紧急租户关停操作可能是另一项重要的生命周期操作。如果您怀疑遭到入侵,并且需要禁止对租户数据的一切访问(不仅是应用逻辑,还有生命周期操作),请使用此关停操作。入侵可能来自数据库内部,也可能来自数据库外部。

移除紧急状态的匹配生命周期操作也必须可用。此类操作可能需要两个或多个管理员同时登录,才能实现双向控制

应用隔离

各种数据管理模式支持不同程度的租户数据隔离。从最高隔离级别(实例)到最低隔离级别(表),您可以使用不同程度的隔离。

在多租户应用的上下文中,必须做出类似的部署决策:所有租户是否都使用相同的应用部署访问其数据(可能在不同的数据管理模式中)?例如,单个 Kubernetes 集群可能支持所有租户,并且当租户访问其数据时,同一集群运行业务逻辑。

或者,与数据管理模式相同,不同的租户可能被定向到不同的应用部署。大型租户可能有权访问其专属的应用部署,而小型租户或免费层级中的租户则共享一个应用部署。

您可以使用数据库数据管理模式,使所有租户共享一个应用部署,而不是直接将本文讨论的数据管理模式与等效应用数据管理模式进行匹配。您可以使用数据库数据管理模式,并且所有租户共享一个应用部署。

多租户是一种重要的应用设计数据管理模式,尤其是在资源效率起着至关重要的作用时。Spanner 支持多种数据管理模式,您可以使用它实现多租户应用。Spanner 具有极强的可扩缩性和严格的服务等级协议 (SLA),因此它非常适合大型多租户应用部署。

后续步骤