本页介绍了在 Spanner 中实现多租户的各种方法。此外,还讨论了数据管理模式和租户生命周期管理。本文档适用于将 Spanner 上的多租户应用作关系型数据库来实现的架构师、数据架构师和工程师。使用该上下文,它概述了存储多租户数据的各种方法。“租户”“客户”和“组织”这几个术语在文章中可互换使用,以指示访问多租户应用的实体。本页提供的示例基于人力资源 (HR) SaaS 提供商在 Google Cloud上实现其多租户应用。其中一个要求是,HR SaaS 提供商的多个客户必须访问多租户应用。这些客户称为租户。
多租户
多租户是指软件应用的单个实例或少数实例为多个租户或客户提供服务。这种软件模式可以从单个租户或客户扩容到成百上千个。这种方法是云计算平台的基础,其中底层基础架构在多个组织之间共享。
您可以将多租户视为基于共享计算资源(例如数据库)的一种分区方式。类比是公寓建筑内的租户:租户共享基础架构(例如水管和电线),但每个租户在公寓中都有专用的租户空间。多租户应用是最重要的(即便不是全部)软件即服务 (SaaS) 应用。
Spanner 是 Google Cloud的全代管式企业级分布式一致数据库,它结合了关系型数据库模型与非关系型数据库横向扩缩能力的优势。Spanner 具有关系语义,具有架构、强制数据类型、强一致性、多语句 ACID 事务,以及实现 ANSI 2011 SQL 的 SQL 查询语言。它为计划内维护或区域故障提供零停机时间,可用性服务等级协议 (SLA) 提供 99.999% 的可用性。它提供高可用性和可伸缩性,支持现代多租户应用。
租户数据映射条件的条件
在多租户应用中,每个租户的数据都是使用底层 Spanner 数据库中的多种架构方法进行隔离。以下列表概述了将租户的数据映射到 Spanner 所使用的不同架构方法:
- 实例:一个租户专门位于一个 Spanner 实例中,该租户只有一个数据库。
- 数据库:一个租户位于包含多个数据库的单个 Spanner 实例的一个数据库中。
- 表:一个租户位于数据库内的专有表中,并且多个租户可以位于同一数据库中。
- 行:租户数据是数据库表中的行。这些表与其他租户共享。
上述条件称为数据管理模式,多租户数据管理模式部分对此进行了详细讨论。该讨论基于以下条件:
- 数据隔离:多个租户的数据隔离程度是多租户的主要考虑因素。例如,数据是需要物理隔离还是逻辑隔离,以及是否可以为每个租户的数据设置独立的 ACL(访问控制列表)。隔离取决于为其他类别的条件选择。例如,某些监管和合规性要求可能决定更大的隔离程度。
- 敏捷性:就实例、数据库、表或行的创建而言,租户可以轻松进行添加和撤销活动。
- 操作:实现典型的特定于租户的数据库操作和管理活动的可用性或复杂性。例如定期维护、日志记录、备份或灾难恢复操作。
- 扩缩:能够无缝扩缩来满足未来的发展需求。每个模式的说明都包含该模式可支持的租户数量。
- 效果:
- 资源隔离:能够为每个租户分配专有资源、解决相邻用户争用现象,以及为每个租户启用可预测的读写性能。
- 每位租户的最低资源量:每位租户的平均最低资源量。这并不一定意味着您需要为每个租户至少支付此金额,而是您需要为所有 N 个租户总计支付至少 N * 此金额。
- 资源效率:能够使用其他租户的闲置资源来节省总体费用。
- 用于延迟时间优化的地理位置选择:能够为每个租户选择特定的复制拓扑,以便将每个租户的数据放置在可为租户提供最佳延迟时间的位置。
- 法规和合规性:能够满足需要完全隔离资源和维护操作的高度受监管行业和国家/地区的要求。例如,法国的数据驻留要求规定,个人身份信息必须完全在法国境内进行实际存储。金融行业通常需要使用客户管理的加密密钥 (CMEK),并且每个租户可能都希望使用自己的加密密钥。
下一部分介绍了与这些条件相关的每种数据管理模式。为一组特定租户选择数据管理模式时,请使用相同的条件。
多租户数据管理模式
以下部分介绍了四种主要数据管理模式:实例、数据库、表和行。
实例
为了提供完全隔离,实例数据管理模式会将每个租户的数据存储在自己的 Spanner 实例和数据库中。一个 Spanner 实例可以有一个或多个数据库。在此模式中,仅创建一个数据库。对于之前讨论的 HR 应用,系统会为每个客户组织创建一个单独的 Spanner 实例和一个数据库。
如下图所示,在数据管理模式下,每个实例有一个租户。
为每个租户使用单独的实例可让您使用单独的Google Cloud 项目为不同租户实现单独的信任边界。此外还有一个好处,系统还会根据每个租户的位置(单区域或多区域)选择每个实例配置,以优化位置灵活性和性能。
该架构可以扩缩到任意数量的租户。SaaS 提供商可以在所需区域中创建任意数量的实例,没有任何硬性限制。
下表概述了实例数据管理模式如何影响不同条件。
条件 | 实例 - 每个实例数据管理模式拥有一个租户 |
---|---|
数据隔离 |
|
敏捷性 |
|
运维 |
|
扩缩 |
|
性能 |
|
监管和合规性要求 |
|
总而言之,关键要点如下:
- 优点:最高级别的隔离
- 缺点:运营开销最大,并且由于每个租户的最低 PU 数量为 100,因此费用可能较高。不支持跨租户共享资源。
实例数据管理模式最适合以下各情况:
- 不同的租户分布在广泛的区域中,需要本地化解决方案。
- 某些租户的监管和合规性要求需要更高级别的安全和审核协议。
- 租户大小有很大差异,因此在大量的高流量租户之间共享资源可能会导致争用且相互降级。
数据库
在数据库数据管理模式中,每个租户都位于单个 Spanner 实例中的数据库中。多个数据库可位于单个实例中。如果一个实例不足以满足租户的数量,可以创建多个实例。此模式意味着多个租户共享一个 Spanner 实例。
Spanner 的硬性限制限制为每个实例拥有 100 个数据库。此限制意味着,如果 SaaS 提供商需要扩容到超过 100 位客户,则客户需要创建和使用多个 Spanner 实例。
对于 HR 应用,SaaS 提供商会使用 Spanner 实例中单独的数据库创建和管理每个租户。
如下图所示,在数据管理模式下,每个数据库拥有一个租户。
在数据库数据管理模式下,可以在数据库级层上针对不同租户的数据实现逻辑隔离。但是,由于它是单个 Spanner 实例,因此除非使用地理分区功能,否则所有租户数据库都共享同一复制拓扑以及底层计算和存储设置。您可以使用 Spanner 地理分区功能在不同位置创建实例分区,并为同一实例中的不同数据库使用不同的实例分区。
下表概述了数据库数据管理模式如何影响不同条件。
条件 | 数据库 — 每个数据库数据管理模式拥有一个租户 |
---|---|
数据隔离 |
|
敏捷性 |
|
操作 |
|
扩缩 |
|
性能 |
|
监管和合规性要求 |
|
总而言之,关键要点如下:
- 优点:中等级别的数据隔离和资源隔离;中等级别的资源效率;每个租户可以拥有自己的备份和 CMEK。
- 缺点:每个实例的租户数量有限;如果不使用地理分区功能,位置不灵活。
数据库数据管理模式最适合以下各情况:
- 多个客户的数据驻留在同一位置,或者受到同一个监管机构的监管。
- 租户需要基于系统的数据隔离,并能够备份和恢复其数据,但支持基础架构资源共享。
- 租户需要有自己的 CMEK。
- 成本是一项重要考量因素。每个租户所需的最小资源低于实例的费用。租户最好能使用其他租户的空闲资源。
表
在表数据管理模式下,实现单个架构的单个数据库用于多个租户,而每个租户的数据使用一组单独的表。通过在表名称中将 tenant ID
添加为前缀、后缀或命名架构,可以区分这些表。
与前面两种选项(实例管理模式和数据库管理模式)相比,在为每个租户使用一组单独的表这种数据管理模式下,隔离级别明显更低。初始配置涉及创建新的表以及关联的引用完整性和索引。
每个数据库的表数量上限为 5,000 个。对部分客户而言,该限制可能会限制他们对应用的使用。
此外,对每个客户使用单独的表可能会导致大量架构更新操作积压。需要很长时间才能解决此类积压问题。
对于 HR 应用,SaaS 提供商可以为每个客户创建一组表,并在表名称中将 tenant ID
用作前缀。例如,customer1_employee
、customer1_payroll
和 customer1_department
。或者,他们可以使用租户 ID 作为命名架构,并将表命名为 customer1.employee
、customer1.payroll
和 customer1.department
。
如下图所示,在表数据管理模式下,每个租户使用一组表。
下表概述了表数据管理模式如何影响不同条件。
条件 | 表 - 每个租户数据管理模式拥有一组表 |
---|---|
数据隔离 |
|
敏捷性 |
|
运维 |
|
扩缩 |
|
性能 |
|
监管和合规性要求 |
|
总而言之,关键要点如下:
- 优点:可伸缩性和资源效率适中。
- 缺点:
- 中等级别的数据隔离和资源隔离。
- 如果不使用新的地理位置细分功能,则营业地点设置不灵活。
- 无法单独监控租户。唯一可用的表级资源消耗信息是表大小统计信息。
- 租户无法拥有自己的 CMEK 和备份。
表数据管理模式最适合以下各情况:
- 法律上无需对数据进行隔离的多租户应用,但您希望进行逻辑隔离和安全控制。
- 成本是一项重要考量因素。每位租户的最低费用低于按数据库计算的费用。
行
最终的数据管理模式为多个租户提供一组通用表,其中每行都属于特定租户。这种数据管理模式表示在多个租户之间共享多租户的极端级别(从基础架构到架构再到数据模型)。在表中,行根据主键进行划分,其中 tenant ID
是键的第一个元素。从扩缩角度来看,Spanner 为此模式提供最佳支持,因为它可以无限扩缩表。
对于 HR 应用,工资表的主键可以是 customerID
和 payrollID
的组合。
如下图所示,在行数据管理模式下,多个租户使用一个表。
与所有其他模式不同,行模式下的数据访问权限无法分别针对不同的租户进行控制。使用较少的表意味着当每个租户都有自己的数据库表时,架构更新操作的完成速度更快。此方法在很大程度上简化了入职、离职和操作。
下表概述了行数据管理模式如何影响不同条件。
条件 | 行 - 每个租户数据管理模式拥有一组行 |
---|---|
数据隔离 |
|
敏捷性 |
|
运维 |
|
扩缩 |
|
性能 |
|
监管和合规性要求 |
|
总而言之,关键要点如下:
- 优点:高度可扩缩;运营开销较低;简化了架构管理。
- 缺点:资源争用激烈;每个租户缺少安全控制措施和监控。
此模式最适合以下各情况:
- 面向不同部门供应的内部应用,与简化维护相比,对数据进行严格的安全隔离并非突出问题。
- 使用免费层级应用功能最大程度地增加租户的资源共享范围,同时减少资源预配。
数据管理模式和租户生命周期管理
概括来讲,下表比较了所有标准中的各种数据管理模式。
实例 | 数据库 | 表 | 行 | |
---|---|---|---|---|
数据隔离 | 完成 | 高 | 中 | 低 |
敏捷性 | 低 | 中 | 中 | 最高 |
操作简便性 | 高 | 高 | 低 | 低 |
扩缩 | 高 | 有限(除非在达到限制时使用其他实例) | 受限(除非在达到上限时使用其他数据库) | 最高 |
性能1 - 资源隔离 | 高 | 低 | 低 | 低 |
效果1 - 每个租户的最低资源 | 高 | 中高 | 中 | 无每位租户的最低要求 |
性能1 - 资源效率 | 低 | 高 | 高 | 高 |
效果1 - 针对延迟时间优化选择位置 | 高 | 中 | 中 | 中 |
法规和合规性 | 最高 | 高 | 中 | 低 |
1 性能在很大程度上取决于架构设计和查询最佳实践。此处的值只是平均预期值。
特定多租户应用的最佳数据管理模式可满足基于标准的大部分要求。如果不需要特定标准,您可以忽略其所在的行。
结合使用的数据管理模式
通常,单个数据管理模式足以满足多租户应用的要求。在这种情况下,该设计可以采用单一数据管理模式。
某些多租户应用需要同时使用多种数据管理模式。例如,支持免费层级、多租户层级和企业层级的多租户应用。
免费层级:
- 必须经济实惠
- 必须设置数据量上限
- 通常支持有限功能
- 行数据管理模式是良好的免费层级候选项:
- 租户管理非常简单
- 无需创建特定的或专属的租户资源
常规层级:
- 适用于没有特别强烈的扩缩要求或隔离要求的付费客户。
- 表数据管理模式或数据库数据管理模式是良好的标准层级候选项:
- 表和索引专用于租户。
- 在数据库数据管理模式下,备份非常简单
- 表数据管理模式不支持备份。
- 租户备份必须作为 Spanner 外部的实用程序实现。
Enterprise 层级:
- 通常是高端层级,在各个方面实现完全自主性。
- 租户拥有专用的资源,资源包括专用的扩缩和完全隔离机制。
- 实例数据管理模式非常适合企业层级。
最佳做法是将不同的数据管理模式保留在不同的数据库中。尽管可以在 Spanner 数据库中组合不同的数据管理模式,但这样做很难实现应用的访问逻辑和生命周期操作。
应用设计部分概述了使用一种数据管理模式或多种数据管理模式时适用的一些多租户应用设计注意事项。
管理租户生命周期
租户具有生命周期。因此,您必须在多租户应用中实现相应的管理操作。除了创建、更新和删除租户等基本操作之外,请考虑以下与数据相关的其他操作:
导出租户数据:
- 删除租户时,最好是先导出其数据,并使数据集可供访问。
- 使用行或表数据管理模式时,多租户应用系统必须实现将数据导出或映射到数据库的功能(数据库导出),并实现自定义逻辑以提取与租户对应的部分数据。
备份租户数据:
- 使用实例或数据库数据管理模式并为个别租户备份数据时,请使用数据库的导出或备份功能。
- 使用表或行数据管理模式以及为单个租户备份数据时,多租户应用必须实现此操作。Spanner 数据库无法确定哪些数据属于哪个租户。
移动租户数据:
切换租户的数据管理模式(或在实例之间或数据库之间切换使用同一数据管理模式的租户)需要从一个数据管理模式中提取数据,然后将该数据插入新的数据管理模式。
- 如果应用停机,请执行导出/导入。
- 如果应用不会停机,请执行零停机时间数据库迁移。
缓解相邻用户争用形势是移动租户的另一个原因。
应用设计
在设计多租户应用时,请实现租户感知业务逻辑。这意味着应用每次运行业务逻辑时,都必须始终位于已知租户的上下文中。
从数据库的角度来看,应用设计意味着必须根据租户所处的数据管理模式运行每个查询。以下各部分重点介绍多租户应用设计的一些主要概念。
动态租户连接和查询配置
将租户数据动态映射到租户应用请求时会使用映射配置:
- 对于数据库数据管理模式或实例数据管理模式,连接字符串便足以访问租户的数据。
- 对于表数据管理模式,必须确定正确的表名称。
- 对于行数据管理模式,请使用适当的谓词来检索特定租户的数据。
租户可以处于四种数据管理模式中的任何一种模式。下面的映射实现方案解决了同时使用所有数据管理模式的多租户应用在一般情况下的连接配置。当给定租户使用一种模式中时,某些多租户应用会针对所有租户使用一种数据管理模式。以下映射隐式涵盖了此情况。
如果租户执行业务逻辑(例如,员工使用租户 ID 登录),则应用逻辑必须确定租户的数据管理模式、指定租户 ID 数据的位置,以及(可选)表命名约定(适用于表模式)。
此应用逻辑需要租户到数据管理模式的映射。在以下代码示例中,connection string
是指租户数据所在的数据库。该示例标识 Spanner 实例和数据库。对于数据管理模式实例和数据库,以下代码足以连接和执行查询:
tenant id -> (data management pattern,
database connection string)
表和行数据管理模式需要进行额外设计。
表数据管理模式
对于表数据管理模式,同一数据库中有多个租户。每个租户都有自己的一组表。这些表通过其名称进行区分。哪个表属于哪个租户是确定的。
一种方法是将每个租户的表放入以租户命名的命名空间中,并使用 namespace.name
完全限定表名称。例如,您可以将 EMPLOYEE
表放入 ID 为 356
的租户的命名空间 T356
中,您的应用可以使用 T356.EMPLOYEE
来处理对该表的请求。
另一种方法是在表名称前面加上租户 ID。例如,对于 ID 为 356
的租户,EMPLOYEE
表称为 T356_EMPLOYEE
。在将查询发送到映射返回的数据库之前,应用必须先在每个表的前面添加前缀 tenant
ID
。
如果您想使用其他文本(而非租户 ID),可以维护从租户 ID 到命名架构命名空间或表前缀的映射。
为了简化应用逻辑,您可以引入一级间接。例如,您可以将通用库与应用搭配使用,以自动为来自租户的调用附加命名空间或表前缀。
行数据管理模式
行数据管理模式需要类似的设计。在此模式中,只有一个架构。租户数据存储为行。如需正确访问数据,请在每个查询中附加谓词以选择适当的租户。
如需查找适当的租户,一种方法是让每个表中都有一个名为 TENANT
的列。为了更好地实现数据隔离,此列值应是主键的一部分。列值为 tenant ID
。每个查询必须将谓词 AND TENANT = tenant ID
附加到现有的 WHERE
子句,或者添加具有谓词 AND TENANT = tenant
ID
的 WHERE
子句。
为了连接到数据库并创建适当的查询,租户标识符必须在应用逻辑中可用。它可以作为参数传入或存储为线程上下文。
某些生命周期操作要求您修改租户到数据管理模式的映射配置 - 例如,在数据管理模式之间移动租户时,必须更新数据管理模式和数据库连接字符串。您可能还需要更新表前缀。
查询生成和归因
多租户应用的基本底层原则是多个租户可以共享单个云资源。上述数据管理模式属于此类别,但单个租户被分配给单个 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}
您还可以使用标记将查询归因于租户,并在内置 spanner_sys
表中查看统计信息。
租户访问生命周期操作
根据您的设计理念,多租户应用可以直接实现前面介绍的数据生命周期操作,它也可以创建单独的租户管理工具。
与实现策略无关,生命周期操作可能必须在没有同时运行应用逻辑的情况下运行,例如,将切换租户的数据管理模式时,应用逻辑无法运行,因为数据不在单个数据库中。当数据位于单个数据库时,从应用的角度来看,需要执行两项额外操作:
- 停止租户:停用所有应用逻辑访问权限,同时允许执行数据生命周期操作。
- 启动租户:应用逻辑可以访问租户的数据,同时将停用会影响应用逻辑的生命周期操作。
虽然不常使用,但紧急租户关停操作可能是另一项重要的生命周期操作。如果您怀疑遭到入侵,并且需要禁止对租户数据的一切访问(不仅是应用逻辑,还有生命周期操作),请使用此关停操作。入侵可能来自数据库内部,也可能来自数据库外部。
移除紧急状态的匹配生命周期操作也必须可用。此类操作可能需要两个或多个管理员同时登录,才能实现双向控制。
应用隔离
各种数据管理模式支持不同程度的租户数据隔离。从最高隔离级别(实例)到最低隔离级别(行),您可以使用不同程度的隔离。
在多租户应用的上下文中,必须做出类似的部署决策:所有租户是否都使用相同的应用部署访问其数据(可能在不同的数据管理模式中)?例如,单个 Kubernetes 集群可能支持所有租户,并且当租户访问其数据时,同一集群运行业务逻辑。
或者,与数据管理模式相同,不同的租户可能被定向到不同的应用部署。大型租户可能有权访问其专属的应用部署,而小型租户或免费层级中的租户则共享一个应用部署。
您可以使用数据库数据管理模式,使所有租户共享一个应用部署,而不是直接将本文档讨论的数据管理模式与等效应用数据管理模式进行匹配。您可以使用数据库数据管理模式,并且所有租户共享一个应用部署。
多租户是一种重要的应用设计数据管理模式,尤其是在资源效率起着至关重要的作用时。Spanner 支持多种数据管理模式,您可以使用它实现多租户应用。Spanner 具有极强的可扩缩性和严格的服务等级协议 (SLA)。它非常适合大型多租户应用部署。