在上一篇中我们谈到了应用层面嘚多租户架构涉及到 PaaS、JVM、OS 等,与之相应的是数据层也有多租户的支持
在一台服务器上运行单个应用实例,它为多个租户提供服务
在SaaS實施过程中,有一个显著的考量点就是如何对应用数据进行设计,以支持多租户而这种设计的思路,是要在数据的共享、安全隔离和性能间取得平衡
传 统的应用,仅仅服务于单个租户数据库多部署在企业内部网络环境,对于数据拥有者来说这些数据是自己“私有”的,它符合自己所定义的全部安全标准而在 云计算时代,随着应用本身被放到云端导致数据层也经常被公开化,但租户对数据安全性的要求并不因之下降。同时多租户应用在租户数量增多的情况下,会
比单租户应用面临更多的性能压力本文即对这个主题进行探討:多租户在数据层的框架如何在共享、安全与性能间进行取舍,同时了解一下市面上一些常见的数据 厂商怎样实现这部分内容
在 MSDN 的这篇文章 中,系统的总结了数据层的三种多租户架构:
- 共享数据库、独立 Schema
- 共享数据库、共享 Schema、共享数据表
独立数据库是一个租户独享一个数據库实例它提供了最强的分离度,租户的数据彼此物理不可见备份与恢复都很灵活;共享数据库、独立 Schema 将每个租户关联到同一个数据庫的不同 Schema,租户间数据彼此逻辑不可见上层应用程序的实现和独立数据库一样简单,但备份恢复稍显复杂; 最后一种模式则是租户数据茬数据表级别实现共享它提供了最低的成本,但引入了额外的编程复杂性(程序的数据访问需要用
tenantId 来区分不同租户)备份与恢复也更複杂。这三种模式的特点可以用一张图来概括:
模式3:共享数据库、共享 Schema、共享数据表
在这种情况下所有租户共享数据表存放数据,不哃租户的数据通过 tenant_id 鉴别器来区分但目前的 Hibernate 4 还不支持这个多租户鉴别器策略,要在 5.0 才支持但我们是否有可选的替代方案呢?答案是使用 Hibernate Filter.
為了区分多个租户我在 Schema 的每个数据表需要添加一个字段 tenant_id 以判定数据是属于哪个租户的。
我们在 OR-Mapping 配置文件中使用了 Filter以便在进行数据查询時,会根据 tenant_id 自动查询出该租户所拥有的数据
不过 Filter 只是有助于我们读取数据时显示地忽略掉 tenantId,但在进行数据插入的时候我们还是不得不顯式设置相应 tenantId 才能进行持久化。这种状况只能在 Hibernate5 版本中得到根本改变
基于独立 Schema 模式的多租户实现,其数据表无需额外的 tenant_id通过 ConnectionProvider 来取得所需的 JDBC 连接,对其来说一级缓存(Session 级别的缓存)是安全的可用的一级缓存对事物级别的数据进行缓存,一旦事物结束缓存也即失效。但昰该模式下的二级缓存是不安全的因为多个 Schema 的数据库的主键可能会是同一个值,这样就使得
在共享数据表的模式下的缓存, 可以同时使用 Hibernate嘚一级缓存和二级缓存, 因为在共享的数据表中主键是唯一的,数据表中的每条记录属于对应的租户在二级缓存中的对象也具有唯一性。Hibernate 分别为 EhCache、OSCache、SwarmCache 和 JBossCache 等缓存插件提供了内置的 CacheProvider 实现读者可以根据需要选择合理的缓存,修改
Hibernate 配置文件设置并启用它以提高多租户应用的性能。
EclipseLink 是 Eclipse 基金会管理下的开源持久层服务项目为 Java 开发人员与各种数据服务(比如:数据库、web services、对象XML映射(OXM)、企业信息系统(EIS)等)交互提供了一个可扩展框架,目前支持的持久层标准中包括:
在完整实现 JPA 标准之外针对 SaaS 环境,在多租户的隔离方面 EclipseLink 提供了很好的支持以及灵活地解决方案
- 隔离的容器/应用服务器
- 共享容器/应用服务器的应用程序隔离
对于多租户数据源隔离主要有以下方案
本节重点介绍多租户在 EclipseLink Φ的共享数据表和一租户一个表的实现方法,并也以酒店多租户应用的例子展现共享数据表方案的具体实践
或者在EclipseLink描述文件orm.xml定义对象与表映射时进行限制,两者是等价的
租户区分列定义好后,在运行时环境需要配置具体属性值以确定当前操作环境所属的租户。
三种方式的属性配置按优先生效顺序排序如下
按共享粒度可以作如下区分,
用户需要通过 eclipselink.session-name 提供独立的会话名确保每个租户占有独立的会话和緩存。
这种级别下共享 session,共享 L2 cache, 用户需要自己设置缓存策略以设置哪些租户信息是不能在二级缓存共享的。
清单 16. 设置缓存
清单 17. 多个分区列
- 租户区分列的名字和对应的上下文属性名可以取任意值由应用程序开发者设定。
- 租户区分列可以映射到实体对象也可以不
这种多租戶类型使每个租户的数据可以占据专属它自己的一个或多个表,多租户间的这些表可以共享相同 Schema 也可使用不同的前者使用前缀(prefix)或后綴(suffix)命名模式的表的租户区分符,后者使用租户专属的 Schema 名来定义表的租户区分符
酒店多租户应用实例(EclipseLink 共享(单)表)
清单 23. 多条件多結果查询
若用 JPQL 实现则示例如下:
部分测试数据如下(MySQL):
运行附件 MT_Test_Hotels.zip 中的测试代码(请参照 readme)来看看多租户的一些典型场景。
清单 25. 运行测试代码
通过共享表的测试数据以及运行结果可以看到对于多个不同的租户(hotel_admin),在添加、查找、更新操作没有显示声明租户标识的情况下EntityManager 可以根據自身的租户属性配置
实现租户分离。在本实例EntityManager 初始化时利用到 hotel_admin 登录后的会话上下文进行租户判断,这里不再赘述
注:上文中提及的铨部源码都可以在附件中找到。
独立数据库和独立Sechma的模式为每个租户备份数据比较容易,因为他们存放在不同的数据表中只需对整个數据库或整个Schema进行备份。
在 共享数据表的模式下可以将所有租户的数据一起备份,但是若要为某一个租户或按租户分开进行数据备份僦会比较麻烦。通常需要另外写sql脚本根据 tenant_id来取得对应的数据然后再备份但是要按租户来导入的话依然比较麻烦,所以必要时还是需要备份所有并为以后导入方便
独立数据库:性能高,但价格也高需要占用资源多,不能共享性价比低。
共享数据库独立 Schema:性能中等,泹价格合适部分共享,性价比中等
共享数据库,共享 Schema共享数据表:性能中等(可利用 Cache 可以提高性能),但价格便宜完全共享,性價比高如果在某些表中有大量的数据,可能会对所有租户产生性能影响
对于共享数据库的情况下,如果因为太多的最终用户同时访问數据库而导致应用程序性能问题可以考虑数据表分区等数据库端的优化方案。
为了支持多租户应用共享模式的应用程序往往比使用独竝数据库模式的应用程序相对复杂,因为开发一个共享的架构导致在应用设计上得花较大的努力,因而初始成本会较高然而,共享模式的应用在运营成本上往往要低一些每个租户所花的费用也会比较低。
多租户数据层方案的选择是一个综合的考量过程包括成本、数據隔离与保护、维护、容灾、性能等。但无论怎样选择OR-Mapping 框架对多租户的支持将极大的解放开发人员的工作,从而可以更多专注于应用逻輯最后我们以一个 Hibernate 和 EclipseLink 的比较来结束本文。
|
|
共享数据库独立 Schema
|
共享数据库,共享 Schema共享数据表
|