[转载]数据层的多租户浅谈(SAAS多租户数据库设计)谦信君

多租户(MultiTenancy/Tenant)是一种软件架构,其定义是:

在一台服务器上运行单个应用实例,它为多个租户提供服务。

在SaaS实施过程中,有一个显著的考量点,就是如何对应用数据进行设计,以支持多租户,而这种设计的思路,是要在数据的共享、安全隔离和性能间取得平衡。

传统的应用,仅仅服务于单个租户,数据库多部署在企业内部网络环境,对于数据拥有者来说,这些数据是自己“私有”的,它符合自己所定义的全部安全标准。而在云计算时代,随着应用本身被放到云端,导致数据层也经常被公开化,但租户对数据安全性的要求,并不因之下降。同时,多租户应用在租户数量增多的情况下,会比单租户应用面临更多的性能压力。本文即对这个主题进行探讨:多租户在数据层的框架如何在共享、安全与性能间进行取舍,同时了解一下市面上一些常见的数据厂商怎样实现这部分内容。

独立数据库是一个租户独享一个数据库实例,它提供了最强的分离度,租户的数据彼此物理不可见,备份与恢复都很灵活;共享数据库、独立Schema将每个租户关联到同一个数据库的不同Schema,租户间数据彼此逻辑不可见,上层应用程序的实现和独立数据库一样简单,但备份恢复稍显复杂;最后一种模式则是租户数据在数据表级别实现共享,它提供了最低的成本,但引入了额外的编程复杂性(程序的数据访问需要用tenantId来区分不同租户),备份与恢复也更复杂。这三种模式的特点可以用一张图来概括:

上图所总结的是一般性的结论,而在常规场景下需要综合考虑才能决定那种方式是合适的。例如,在占用成本上,认为独立数据库会高,共享模式较低。但如果考虑到大租户潜在的数据扩展需求,有时也许会有相反的成本耗用结论。

而多租户采用的选择,主要是成本原因,对于多数场景而言,共享度越高,软硬件资源的利用效率更好,成本也更低。但同时也要解决好租户资源共享和隔离带来的安全与性能、扩展性等问题。毕竟,也有客户无法满意于将数据与其他租户放在共享资源中。

目前市面上各类数据厂商在多租户的支持上,大抵都是遵循上文所述的这几类模式,或者混合了几种策略,这部分内容将在下面介绍。

JSR338定义了JPA规范2.1,但如我们已经了解到的,Oracle把多租户的多数特性推迟到了JavaEE8中。尽管这些曾经在JavaOne大会中有过演示,但无论是在JPA2.0(JSR317)还是2.1规范中,都依然没有明文提及多租户。不过这并不妨碍一些JPAprovider在这部分领域的实现,Hibernate和EclipseLink已提供了全部或部分的多租户数据层的解决方案。

Hibernate是当今最为流行的开源的对象关系映射(ORM)实现,并能很好地和Spring等框架集成,目前Hibernate支持多租户的独立数据库和独立Schema模式。EclipseLink也是企业级数据持久层JPA标准的参考实现,对最新JPA2.1完整支持,在目前JPA标准尚未引入多租户概念之际,已对多租户支持完好,其前身是诞生已久、功能丰富的对象关系映射工具OracleTopLink。因此本文采用Hibernate和EclipseLink对多租户数据层进行分析。

Hibernate是一个开放源代码的对象/关系映射框架和查询服务。它对JDBC进行了轻量级的对象封装,负责从Java类映射到数据库表,并从Java数据类型映射到SQL数据类型。在4.0版本Hibenate开始支持多租户架构——对不同租户使用独立数据库或独立Sechma,并计划在5.0中支持共享数据表模式。

在Hibernate4.0中的多租户模式有三种,通过hibernate.multiTenancy属性有下面几种配置:

如果是独立数据库,每个租户的数据保存在物理上独立的数据库实例。JDBC连接将指向具体的每个数据库,一个租户对应一个数据库实例。在Hibernate中,这种模式可以通过实现MultiTenantConnectionProvider接口或继承AbstractMultiTenantConnectionProvider类等方式来实现。三种模式中它的共享性最低,因此本文重点讨论以下两种模式。

对于共享数据库,独立Schema。所有的租户共享一个数据库实例,但是他们拥有独立的Schema或Catalog,本文将以多租户酒店管理系统为案例说明Hibernate对多租户的支持和用使用方法。

publicclassTenantIdResolverimplementsCurrentTenantIdentifierResolver{publicStringresolveCurrentTenantIdentifier(){returnLogin.getTenantId();}}属性指定了ConnectionProvider,即Hibernate需要知道如何以租户特有的方式获取数据连接,SchemaBasedMultiTenantConnectionProvider类实现了MultiTenantConnectionProvider接口,根据tenantIdentifier获得相应的连接。在实际应用中,可结合使用JNDIDataSource技术获取连接以提高性能。

publicclassSchemaBasedMultiTenantConnectionProviderimplementsMultiTenantConnectionProvider,Stoppable,Configurable,ServiceRegistryAwareService{privatefinalDriverManagerConnectionProviderImplconnectionProvider=newDriverManagerConnectionProviderImpl();@OverridepublicConnectiongetConnection(StringtenantIdentifier)throwsSQLException{finalConnectionconnection=connectionProvider.getConnection();connection.createStatement().execute("USE"+tenantIdentifier);returnconnection;}@OverridepublicvoidreleaseConnection(StringtenantIdentifier,Connectionconnection)throwsSQLException{connection.createStatement().execute("USEtest");connectionProvider.closeConnection(connection);}……}与表guest对应的POJO类Guest,其中主要是一些getter和setter方法。

为了区分多个租户,我在Schema的每个数据表需要添加一个字段tenant_id以判定数据是属于哪个租户的。

根据上图在MySQL中创建DATABASEhotel。

我们在OR-Mapping配置文件中使用了Filter,以便在进行数据查询时,会根据tenant_id自动查询出该租户所拥有的数据。

不过Filter只是有助于我们读取数据时显示地忽略掉tenantId,但在进行数据插入的时候,我们还是不得不显式设置相应tenantId才能进行持久化。这种状况只能在Hibernate5版本中得到根本改变。

基于独立Schema模式的多租户实现,其数据表无需额外的tenant_id。通过ConnectionProvider来取得所需的JDBC连接,对其来说一级缓存(Session级别的缓存)是安全的可用的,一级缓存对事物级别的数据进行缓存,一旦事物结束,缓存也即失效。但是该模式下的二级缓存是不安全的,因为多个Schema的数据库的主键可能会是同一个值,这样就使得Hibernate无法正常使用二级缓存来存放对象。例如:在hotel_1的guest表中有个id为1的数据,同时在hotel_2的guest表中也有一个id为1的数据。通常我会根据id来覆盖类的hashCode()方法,这样如果使用二级缓存,就无法区别hotel_1的guest和hote_2的guest。

在共享数据表的模式下的缓存,可以同时使用Hibernate的一级缓存和二级缓存,因为在共享的数据表中,主键是唯一的,数据表中的每条记录属于对应的租户,在二级缓存中的对象也具有唯一性。Hibernate分别为EhCache、OSCache、SwarmCache和JBossCache等缓存插件提供了内置的CacheProvider实现,读者可以根据需要选择合理的缓存,修改Hibernate配置文件设置并启用它,以提高多租户应用的性能。

EclipseLink是Eclipse基金会管理下的开源持久层服务项目,为Java开发人员与各种数据服务(比如:数据库、webservices、对象XML映射(OXM)、企业信息系统(EIS)等)交互提供了一个可扩展框架,目前支持的持久层标准中包括:

EclipseLink前身是OracleTopLink,2007年Oracle将后者绝大部分捐献给了Eclipse基金会,次年EclipseLink被Sun挑选成为JPA2.0的参考实现。

注:目前EclipseLink2.5完全支持2013年发布的JPA2.1(JSR338)。

在完整实现JPA标准之外,针对SaaS环境,在多租户的隔离方面EclipseLink提供了很好的支持以及灵活地解决方案。

应用程序隔离

数据隔离

对于多租户数据源隔离主要有以下方案

本节重点介绍多租户在EclipseLink中的共享数据表和一租户一个表的实现方法,并也以酒店多租户应用的例子展现共享数据表方案的具体实践。

EclipseLinkAnnotation@Multitenant

与@Entity或@MappedSuperclass一起使用,表明它们在一个应用程序中被多租户共享,如清单10。

@Entity@Table(name="room")@Multitenant...publicclassRoom{}表1.Multitenant包含两个属性Annotation属性描述缺省值booleanincludeCriteria是否将租户限定应用到select、update、delete操作上trueMultitenantTypevalue多租户策略,SINGLE_TABLE,TABLE_PER_TENANT,VPD.SINGLE_TABLE共享数据表(SINGLE_TABLE)Metadata配置依靠租户区分列修饰符@TenantDiscriminatorColumn实现。

@Entity@Table(name="hotel_guest")@Multitenant(SINGLE_TABLE)@TenantDiscriminatorColumn(name="tenant_id",contextProperty="tenant.id")publicclassHotelGuest{}或者在EclipseLink描述文件orm.xml定义对象与表映射时进行限制,两者是等价的。

...属性配置租户区分列定义好后,在运行时环境需要配置具体属性值,以确定当前操作环境所属的租户。

三种方式的属性配置,按优先生效顺序排序如下

例如EntityManagerFactory可以间接通过在persistence.xml中配置持久化单元(PersistenceUnit)或直接传属性参数给初始化时EntityManagerFactory。

......或者

按共享粒度可以作如下区分,

用户需要通过eclipselink.session-name提供独立的会话名,确保每个租户占有独立的会话和缓存。

EntityManagerFactory的默认模式,此级别缺省配置为独立二级缓存(L2cache),即每个mutlitenant实体缓存设置为ISOLATED,用户也可设置eclipselink.multitenant.tenants-share-cache属性为真以共享,此时多租户Entity缓存设置为PROTECTED。

这种级别下,一个活动的EntityManager不能更换tenantId。

这种级别下,共享session,共享L2cache,用户需要自己设置缓存策略,以设置哪些租户信息是不能在二级缓存共享的。

同样,一个活动的EntityManager不能更换tenantID。

几点说明:

@TenantDiscriminatorColumns({@TenantDiscriminatorColumn(name="tenant_id",contextProperty="tenant.id"),@TenantDiscriminatorColumn(name="guest_id",contextProperty="guest.id")})租户区分列的名字和对应的上下文属性名可以取任意值,由应用程序开发者设定。生成的Schema可以也可以不包含租户区分列,如tenant_id或guest_id。租户区分列可以映射到实体对象也可以不。注意:当映射的时候,实体对象相应的属性必须标记为只读(insertable=false,updatable=false),这种限制使得区分列不能作为实体表的identifier。

persist,find,refresh,namedqueries,updateall,deleteall。

这种多租户类型使每个租户的数据可以占据专属它自己的一个或多个表,多租户间的这些表可以共享相同Schema也可使用不同的,前者使用前缀(prefix)或后缀(suffix)命名模式的表的租户区分符,后者使用租户专属的Schema名来定义表的租户区分符。

依靠数据表的租户区分修饰符@TenantTableDiscriminator实现

@Entity@Table(name=“CAR”)@Multitenant(TABLE_PER_TENANT)@TenantTableDiscriminator(type=SCHEMA,contextProperty="eclipselink-tenant.id")publicclassCar{}或

如前所述,TenantTableDiscriminatorType有3种类型:SCHEMA、SUFFIX和PREFIX。

与另外两种多租户类型一样,默认情况下,多租户共享EMF,如不想共享EMF,可以通过配置PersistenceUnitProperties.MULTITENANT_SHARED_EMF以及PersistenceUnitProperties.SESSION_NAME实现。

或在persistence.xml配置属性。

酒店多租户应用实例(EclipseLink共享(单)表)

数据库Schema和测试数据与上文Hibernate实现相同,关于对象关系映射(ORmapping)的配置均采用JPA和EclipseLink定义的JavaAnnotation描述。

关于几个基本操作

若用JPQL实现则示例如下:

部分测试数据如下(MySQL):

运行附件MT_Test_Hotels.zip中的测试代码(请参照readme)来看看多租户的一些典型场景。

能得到输出片段如下:

注:上文中提及的全部源码都可以在附件中找到。

独立数据库和独立Sechma的模式,为每个租户备份数据比较容易,因为他们存放在不同的数据表中,只需对整个数据库或整个Schema进行备份。

在共享数据表的模式下,可以将所有租户的数据一起备份,但是若要为某一个租户或按租户分开进行数据备份,就会比较麻烦。通常需要另外写sql脚本根据tenant_id来取得对应的数据然后再备份,但是要按租户来导入的话依然比较麻烦,所以必要时还是需要备份所有并为以后导入方便。

独立数据库:性能高,但价格也高,需要占用资源多,不能共享,性价比低。

共享数据库,独立Schema:性能中等,但价格合适,部分共享,性价比中等。

共享数据库,共享Schema,共享数据表:性能中等(可利用Cache可以提高性能),但价格便宜,完全共享,性价比高。如果在某些表中有大量的数据,可能会对所有租户产生性能影响。

对于共享数据库的情况下,如果因为太多的最终用户同时访问数据库而导致应用程序性能问题,可以考虑数据表分区等数据库端的优化方案。

为了支持多租户应用,共享模式的应用程序往往比使用独立数据库模式的应用程序相对复杂,因为开发一个共享的架构,导致在应用设计上得花较大的努力,因而初始成本会较高。然而,共享模式的应用在运营成本上往往要低一些,每个租户所花的费用也会比较低。

多租户数据层方案的选择是一个综合的考量过程,包括成本、数据隔离与保护、维护、容灾、性能等。但无论怎样选择,OR-Mapping框架对多租户的支持将极大的解放开发人员的工作,从而可以更多专注于应用逻辑。最后我们以一个Hibernate和EclipseLink的比较来结束本文。

THE END
1.酒店管理系统(源码+文档+部署+讲解)财务跟踪本文将深入解析 "酒店管理系统" 的项目,探究其架构、功能以及技术栈,并分享获取完整源码的途径。 系统概述 酒店管理系统是一款为酒店行业设计的全面管理软件,旨在通过集成酒店运营的各个关键环节,提高酒店的管理效率和客户满意度。系统提供了从登录系统、首页概览、房价管理、房态监控、财务管理、订单处理、POS 操作、营...https://www.163.com/dy/article/JGG3UO800556A0H0.html
2.住哲云PMS官网酒店管理系统酒店管理软件宾馆管理系统酒店...北京住哲信息技术有限公司,国内杰出的酒店软件开发商,为酒店开放所有API接口,支持微信订房、微信扫码支付,提供360度多渠道营销工具,目前有50000多家酒店用户使用住哲酒店管理系统。https://www.zhuzher.com/
3.旅游景区(文化街)智慧工程规划设计方案20240615200329.pdf4.2.18智能照明系统140 4.2.19IBMS智能建筑管理系统140 4.2.20云计算平台142 4.2.21博物馆智慧平台143 4.2.22弧形幕投影融合161 4.2.23全息幻影成像系统162 4.3酒店智能化系统建设技术要求163 4.3.1综合布线系统163 4.3.2信息发布及引导系统163 4.3.3计算机网络系统(含无线网络覆盖)164 4.3.4程控交换机系统164...https://m.book118.com/html/2024/0615/7140104004006122.shtm
4.考试酒店管理系统 *193. 以下IT/CT分工界面中,属于IT职责范围的是( ) 专线施工 专线勘查 专线验收 移动办公平台建设 *194. 以下NB-IoT接入类指标描述不正确的有( ) RRC连接建立成功率=RRC连接建立成功次数/RRC连接建立尝试次数*100% 以终端发起RRCConectionRequest作为一次RRC连接建立尝试,到终端发送RRCConectionSetup...https://www.wjx.cn/vj/tx3Eevo.aspx
5.数据层的多租户浅谈OSCHINA对于共享数据库,独立 Schema。所有的租户共享一个数据库实例,但是他们拥有独立的 Schema 或 Catalog,本文将以多租户酒店管理系统为案例说明 Hibernate 对多租户的支持和用使用方法。 图2. guest 表结构 这是酒店客户信息表,我们仅以此表对这种模式进行说明,使用相同的表结构在 MySQL 中创建 DATABASE hotel_1 和 ho...https://my.oschina.net/giegie/blog/812138
1.携程酒店管理系统联创号携程酒店管理系统 序 携程酒店订单系统的存储设计完成了从单SQLServer数据库到多IDC容灾的多个阶段,并完成了子数据库和子表。在见证大量商业奇迹的同时,也逐渐暴露出因循守旧、力不从心的状态。基于更高稳定性和高效成本控制而设计的订单存储系统,成为携程在疫情后恢复业务的必然需求。https://www.lian-bj.com/lc/516923.html
2.迅软DSE软件简介迅软DSE应用软件怎么样迅软DSE功能介绍→MAIGOO...十大好用的酒店管理系统软件 常见酒店管理软件有哪些 酒店管理系统软件能帮助我们快捷处理前台、客房、餐饮、财务等事务。本文中maigoo小编分享了十大酒店系统管理软件,包括Opera、西软、别样红,以及住哲、中软好泰、罗盘、绿云、百居易、云掌柜等。这些软件各有特点,已被广泛使用。下面一起详细了解下。 工具软件 财务...https://www.maigoo.com/citiao/356553.html
3....实现酒店客房管理系统springbootvue酒店管理系统该文详细介绍了酒店客房管理系统的开发背景、可行性分析、需求描述、数据库设计和系统架构。系统采用SpringBoot和Vue技术栈,结合MySQL数据库,实现客房管理、入住登记、消费管理等功能,旨在提高酒店运营效率和服务质量。 摘要由CSDN通过智能技术生成 目录 1 问题的提出 5 ...https://blog.csdn.net/m0_51431003/article/details/131569232
4.智慧园区顶层设计(精雅篇)例如,对于智慧园区而言,应用层可以包含云呼叫平台、智慧园区公共信息服务平台、企业信息化工程、信息化平台、市政综合平台、企业信息档案管理、安全防范系统、信息发表系统、能耗监测系统、停车场管理系统、智能家居系统、会议系统、智能照明系统、智能一卡通系统、智能集成管理系统等。最终通过园区门户实现智慧园区或是智慧城市...https://www.360wenmi.com/f/cnkey99cns9l.html
5.酒店可视化监视大屏怎么做帆软数字化转型知识库数据库:将酒店管理系统中的数据如客房预订、入住登记、财务记录等整合到监视大屏中,确保数据的实时性和准确性。 Excel:如果一些数据暂时无法接入数据库,可以通过Excel导入到FineReport中,方便快速整合和展示。 API:通过API接口获取外部数据,如天气预报、交通情况等,将这些数据整合到监视大屏中,帮助管理者更全面地了解外...https://www.fanruan.com/blog/article/238987/
6.数据层的多租户浅谈(SAAS多租户数据库设计)51CTO博客对于共享数据库,独立 Schema。所有的租户共享一个数据库实例,但是他们拥有独立的 Schema 或 Catalog,本文将以多租户酒店管理系统为案例说明 Hibernate 对多租户的支持和用使用方法。 图2. guest 表结构 这是酒店客户信息表,我们仅以此表对这种模式进行说明,使用相同的表结构在 MySQL 中创建 DATABASE hotel_1 和 ho...https://blog.51cto.com/u_9597987/3497587
7.数据层的多租户浅谈对于共享数据库,独立 Schema。所有的租户共享一个数据库实例,但是他们拥有独立的 Schema 或 Catalog,本文将以多租户酒店管理系统为案例说明 Hibernate 对多租户的支持和用使用方法。 图2. guest 表结构 这是酒店客户信息表,我们仅以此表对这种模式进行说明,使用相同的表结构在 MySQL 中创建 DATABASE hotel_1 和 ho...http://www.360doc.com/content/15/0615/12/175498_478248543.shtml