租户系统使用
[一]概述
常规的应用系统,大多数为所有数据存储在同一个数据库中,通过用户名连接访问操作数据。而多租户系统,本质就是打破常规数据库使用模式的壁垒,在同一个系统中实现数据的隔离以及跨 Schema 或者跨数据的访问。
常见的多租户系统原理并不复杂,即通过前端向后端传递一个标识,后端根据租户模式的不同,利用这个标识来访问同表中的不同数据、不同 Schema 中的数据或者不同数据库中的数据。
Dante Cloud 中多租户完全基于 Hibernate ORM 提供的多租户机制实现。目前支持表级(DISCRIMINATOR
)、Schema 级(SCHEMA
)以及数据库级(DATABASE
)三种多租户模式。
DISCRIMINATOR
:共享数据库,独立Schema,共享数据表SCHEMA
:共享数据库,独立SchemaDATABASE
:独立数据库
正如前文所述,多租户的原理并不复杂,深究下来也仅仅是类似于数据层的条件操作。但是,将这个机制与实际业务结合,就可以变换出多种多样的使用方法,再加之不同用户的需求差异会非常大。例如:是系统整体多租户还是部分功能多租户,是单一层级的多租户还是多层级多层次的多租户。所以指望一套系统的多租户可以满足大多数用户的需求,是非常难以实现的。就好比一个管理功能无法做到绝对通用,可以满足 100% 用户的需求一样。
与其编写用户使用时要么需要大量修改、要么需要大量删减调整的、很难通用的多租户功能,反倒不如将底层封装好,业务功能完全由用户自己设计实现来得便捷。
Dante Cloud 也是采用这种思想,在多租户系统的功能实现方面,仅做底层的封装以及简单功能实现(比如,DATABASE
模式数据源的管理,以及前后端租户标识的传递,多级数据缓存多租户),其它实际的业务功能完全由用户自主决定和设计实现。
重要
个人观点,你的业务系统一旦迫切需要使用多租户,那么你的实际业务需求大概率不会是简单的或者通用化的需求。如果有这种需求存在,在实际的开发人员都未必完全搞得清晰的情况下,指望一款开源产品可以完全满足你的需求,这就有点像天方夜谭了。当然,不排除我本人认知比较窄,更不排除确实有可以做到的产品。如果你找到了这种产品,烦请告诉我一声,我也去好好学习学习。
[二]唯一标识传递
所谓的唯一标识,就是租户标识,就是用来区分具体数据或者指定存储位置的标识。
- 在
DISCRIMINATOR
模式下,唯一标识就是数据表中一个字段中的值 - 在
SCHEMA
模式下,唯一标识就是用来标识具体的SCHEMA
值 - 在
DATABASE
模式下,唯一标识就是用来可以代表某个数据库连接的值
不管是哪种模式,唯一标识就是一个简单的字符串。这个值通常由前端传递给后端,后端拿到唯一标识后进行相应的数据处理。因为微服务系统核心是面向 REST 接口的,所以使用 HTTP 请求 Header 来传递唯一标识是最为便捷的。
在 Dante Cloud 系统中,调用系统接口时,通过在请求头中包含自定义的 X-Herodotus-Tenant-Id
来传递多租户唯一标识(租户ID)
在 Dante Cloud 前端工程中,在配置文件中,如果指定了参数 VITE_MULTI_TENANCY_ID
的值,这个值就是多租户 ID,就会开启多租户模式。
注意
在 Dante Cloud 前端工程中,参数 VITE_MULTI_TENANCY_ID
的值是什么,那么当前租户ID就是什么。如果你想前端可以支撑租户ID切换或者其他多租户的需求,就像前文所述需要自己实现,当前 VITE_MULTI_TENANCY_ID
方式,仅是系统平台能力的一种展示。
[三]多级数据缓存
Dante Cloud 的数据缓存默认已经实现了多租户支持,自动与数据层多租户适配,无需做任何额外的操作和配置。
[四]DISCRIMINATOR
表级多租户(DISCRIMINATOR
)是最简单的多租户模式。
基本原理就是在数据表中,增加一个存储租户ID(唯一标识)的字段。之后所有的数据操作都增加一个租户ID的条件。
这种模式比较简单,即使没有底层支持,自己也可以自己实现。无非就是数据层操作的改造工作量有点大。有了底层支持,可以大量简化相应改造的工作量。
因为,Dante Cloud 的多租户是使用 Hibernate 实现,默认的数据层使用的是 JPA,所以想要实现表级多租户,只需要在具体的 JPA 实体中,增加一个字段和注解即可。示例代码如下:
@TenantId
private String tenantId;
说明
表级多租户(DISCRIMINATOR
)模式,是 Dante Cloud 默认的多租户模式,默认是开启状态。但是,目前没有在任何 JPA 实例中添加 @TenantId
,原因如前文所述,请结合自身需求手动修改。
[五]SCHEMA
要使用 SCHEMA
级多租户,在 Dante Cloud 系统中,进需要修改一个配置参数值,具体配置如下:
herodotus:
data:
multi-tenant:
approach: schema
注意
正常情况下,SCHEMA
多租户模式,还是在同一个数据库进行操作,所以数据库连接都是同一个,因此也无需像 DATABASE
模式一样,配置多个数据源连接。
但是不同的数据库,对于 SCHEMA
的定义和使用方式是不同的,例如:Oracle 中的 schema 是指的用户,Postgresql 中的 schema 是指的数据空间。所以,不同数据库下使用 SCHEMA
多租户会有较大差异。
[六]DATABASE
数据库级多租户(DATABASE
)模式,是最为复杂的和繁琐的模式。要开启数据库级多租户,需要以下步骤:
[1]新建数据库
根据自己使用数据库级多租户的需求,提前创建好多租户对应的的数据库以及数据库用户账号。
重要
其实最理想的情况就是可以通过系统平台来动态创建租户数据库,但这也仅仅是理想状态。数据库软件创建用户和数据库,都需要使用超管的账号和权限才能完成,很多企业都是由专门的运维人员负责。如果要实现系统自动创建数据库,至少需要系统代码由使用超管账号的权限,这是非常高危操作。数据库是企业重要的数据资产,正规的企业没有哪个会允许一个系统可以这么玩。所以如果你有类似的需求,只能自己去实现。
[2]初始数据源信息
将新建数据库的账号等信息,初始化到 Dante Cloud 的 sys_tenant_datasource
数据表中。并指定好具体数据源对应的租户ID
[3]修改系统配置
herodotus:
data:
multi-tenant:
approach: database
package-to-scan:
- cn.herodotus.stirrup
- 首先需要将系统多租户模式指定为
DATABASE
模式 - 指定
DATABASE
模式涉及的表。因为使用的是 JPA,所以指定扫描的 JPA 实体就是指定具体的表。
警告
DATABASE
模式下,必须要在配置 package-to-scan
中指定具体扫描的、包含数据表实体的代码包。否则,该模式不会生效。
[4]手动初始化租户数据库
基于 Spring Data JPA,租户数据库是无法实现自动建表和数据初始化的。所以,需要用户自己手动初始化租户数据库中使用的表以及初始化数据。
完成以上操作,就可以使用 DATABASE
模式的多租户。
说明
每个人由于经验和知识的差异,对于同一问题的理解和掌握各有千秋。以上内容即使作者个人理解,如果有认知局限或者知识缺乏的情况,欢迎提出 ISSUE,作者会积极改进和完善。