在本系列的第二篇文章中(使用 IBM 中间件实现 SaaS 解决方案,第 2 部分:启用多租户的方法),我们讨论了启用多租户的三种方法,即虚拟化、中介和共享。本文主要讨论第三种方法,介绍如何使用单一共享应用程序实例同时支持多个客户组织(即租户),实现经济有效性目标。我们要使用 IBM 中间件开发一个经济有效、安全且可配置的多租户应用程序。我们主要关注多租户应用程序设计和实现的三个关键方面:
为了便于读者理解,我们设计了一个示例应用程序 EasyOA,它应该有助于解释如何使用 IBM 中间件产品(尤其是 IBM WebSphere Application Server (WAS)、DB2、Tivoli Directory Server (TDS) 和 WebSphere Process Server (WPS))实现多租户模型。
![]() ![]() |
![]() |
如图 1 所示,基于资源共享的多租户启用模式分为两类:1) 多个应用程序实例和 2) 单一共享应用程序实例。前者为每个租户提供专用的应用程序实例,这些实例在共享的硬件、操作系统或中间件服务器上运行。后者用单一共享应用程序实例支持许多租户,这个实例包含各种资源。这两种模式可以支持的租户数量差异很大。多实例方法用来支持几个(最多几十个)租户,每个租户可能有数百位用户,使用一组服务器。单一共享实例方法用来支持大量小型租户,租户数量常常是数百个、数千个甚至更多。
应该根据特定的应用场景和目标客户的需求选择资源共享模式。通常,大型企业喜欢多实例方法,因为这有助于避免与资源共享相关联的潜在风险。对于面向 SMB 客户(他们的服务预订预算比较少)的大多数服务,为了实现规模效益,业务模型必须通过吸引大量客户增加收入,同时使用非常高效的可伸缩的多租户基础结构支持这些客户,降低每个客户的平均服务交付成本。在这些场景中,多实例模式是不合适的,因为每个租户带来的收入很少,不足以抵偿为租户分配专用的硬件/软件资源的成本。
尽管多租户应用程序在经济有效性方面非常有优势,但是它们也面对许多难题,尤其是与隔离和定制相关的问题。
首先,应该在各个租户之间支持应用程序级隔离。尽管所有租户共享同一个基础结构和应用程序实例,但是从用户体验、服务质量 (QoS) 和管理的角度来看,租户当然希望像专用租户那样访问和使用服务。因此,在体系结构设计的几乎所有部分都应该仔细考虑隔离,包括非功能性和功能性两方面,涉及安全性、性能、可用性和管理等。
第二,应该支持租户在运行时定制自己的服务,而不会影响其他租户。在过去,定制通常需要修改代码和重新部署应用程序。但是,这种定制模式在多租户服务环境中是不现实的。因为所有租户共享同一个应用程序实例,如果一个租户进行这样的定制,所有租户的服务都会受到影响,而且在更新期间可能会中断服务。随着租户数量的增加,中断会越来越频繁,导致非常严重的服务可用性问题。因此,一个租户在运行时的定制不应该影响其他租户,这是多租户应用程序的关键需求之一。
![]() ![]() |
对于 J2EE/SOA 应用程序,有许多种可访问的资源,在用户界面、业务逻辑、过程/工作流和数据模型等层上还有各种运行时工件。为了设计非常高效的多租户应用程序,我们需要通过以下两个步骤小心地管理这些资源:
表 1 简要总结这些重要的资源以及相应的共享、隔离和定制机制。在本系列的后续文章中,我们将详细介绍它们。
组件 | 共享的资源 | 隔离方法 | 可配置方法 |
持久化数据模型 | - | - | - |
数据库服务器(JDBC、SDO 等) | 数据源,连接池 数据库账户 数据库/模式/表/缓冲区池等 | 存储隔离:专用的表/模式或数据库,或共享的表/模式 访问隔离:通过专用数据源和连接实现 DBMS 级访问控制,或通过筛选器实现应用程序级访问控制 | 数据库/表/模式 固定的保留字段 扩展的子表 XML 扩展字段 |
文件和 I/O | 目录 文件 | 专用的目录/文件 共享的文件,使用与租户相关的筛选器 | 目录结构或文件格式 |
目录服务器 (LDAP) | 目录树 模式 | 每个租户专用的子树 共享的树,使用与租户相关的筛选器 | 目录树结构和模式定义 |
业务逻辑 | - | - | - |
身份验证/授权 | 组织结构/特权存储库 登录/授权模块 | 根据持久化数据模型(LDAP、数据库等)隔离的存储库 扩展的感知租户的用户注册插件 | 组织结构、角色、特权及其映射关系 |
全局 Java 对象 | 静态变量 单实例类的变量 | 每个租户专用的(静态)变量,使用容器对象 | N/A |
远程访问(套接字/Http、RMI、CORABA、JNDI、Web 服务) | 远程服务 URI、端口号、用户名、密码等连接参数 | 专用的远程服务 共享的远程服务,把租户信息传播给远程服务 | 连接参数 |
EJB | 有状态 EJB 实例 数据源连接,实体 Bean 的表 队列,发送者的 MDB 标识 | 每个租户专用的有状态 EJB 实例、MDB、队列 共享的实体 Bean,使用与租户相关的筛选器 共享的 MDB,在消息中嵌入租户标识信息 | 共享的实体 Bean 的保留属性 可定制的消息格式 |
日志 | 日志文件位置、内容、格式和配置 | 专用的日志文件 共享的日志文件,使用与租户相关的筛选器 | 日志格式和设置参数 |
缓存 | 缓存容器 | 专用的缓存容器 共享的缓存容器,使用与租户相关的筛选器 | 缓存设置参数 |
UI | - | - | - |
JSP | 应用程序范围的变量(tag、usebean、applicationContext 等) 声明变量 徽标、样式、布局等 | 使用与租户相关的筛选器选择应用程序范围的变量 专用的声明变量 | CSS、布局、图片等 |
servlet | 单线程 servlet servletContext | 单线程 servlet 使用专用的 servlet 使用与租户相关的筛选器选择 servletContext 变量 | N/A |
过程/工作流 | - | - | - |
BPEL 过程模板 | 模板级属性、活动、链接条件等 | 专用的过程模板 共享的模板,在过程实例中使用与租户相关的筛选器 | 过程模板的骨架 |
人工任务 | 动作、任务 UI 等 | 扩展动作,支持根据启动者的租户信息隔离人员选择机制 | UI 元素和共享任务的设置 人员选择规则 |
业务规则 | 规则 | 专用的业务规则 共享的业务规则,使用与租户相关的筛选器作为规则参数 | 业务规则的规则值设置 |
组件 | 共享的资源 | 隔离方法 | 可配置方法 |
持久化数据模型 | - | - | - |
数据库服务器(JDBC、SDO 等) | 数据源,连接池 数据库账户 数据库/模式/表/缓冲区池等 | 存储隔离:专用的表/模式或数据库,或共享的表/模式 访问隔离:通过专用数据源和连接实现 DBMS 级访问控制,或通过筛选器实现应用程序级访问控制 | 数据库/表/模式 固定的保留字段 扩展的子表 XML 扩展字段 |
文件和 I/O | 目录 文件 | 专用的目录/文件 共享的文件,使用与租户相关的筛选器 | 目录结构或文件格式 |
目录服务器 (LDAP) | 目录树 模式 | 每个租户专用的子树 共享的树,使用与租户相关的筛选器 | 目录树结构和模式定义 |
业务逻辑 | - | - | - |
身份验证/授权 | 组织结构/特权存储库 登录/授权模块 | 根据持久化数据模型(LDAP、数据库等)隔离的存储库 扩展的感知租户的用户注册插件 | 组织结构、角色、特权及其映射关系 |
全局 Java 对象 | 静态变量 单实例类的变量 | 每个租户专用的(静态)变量,使用容器对象 | N/A |
远程访问(套接字/Http、RMI、CORABA、JNDI、Web 服务) | 远程服务 URI、端口号、用户名、密码等连接参数 | 专用的远程服务 共享的远程服务,把租户信息传播给远程服务 | 连接参数 |
EJB | 有状态 EJB 实例 数据源连接,实体 Bean 的表 队列,发送者的 MDB 标识 | 每个租户专用的有状态 EJB 实例、MDB、队列 共享的实体 Bean,使用与租户相关的筛选器 共享的 MDB,在消息中嵌入租户标识信息 | 共享的实体 Bean 的保留属性 可定制的消息格式 |
日志 | 日志文件位置、内容、格式和配置 | 专用的日志文件 共享的日志文件,使用与租户相关的筛选器 | 日志格式和设置参数 |
缓存 | 缓存容器 | 专用的缓存容器 共享的缓存容器,使用与租户相关的筛选器 | 缓存设置参数 |
UI | - | - | - |
JSP | 应用程序范围的变量(tag、usebean、applicationContext 等) 声明变量 徽标、样式、布局等 | 使用与租户相关的筛选器选择应用程序范围的变量 专用的声明变量 | CSS、布局、图片等 |
servlet | 单线程 servlet servletContext | 单线程 servlet 使用专用的 servlet 使用与租户相关的筛选器选择 servletContext 变量 | N/A |
过程/工作流 | - | - | - |
BPEL 过程模板 | 模板级属性、活动、链接条件等 | 专用的过程模板 共享的模板,在过程实例中使用与租户相关的筛选器 | 过程模板的骨架 |
人工任务 | 动作、任务 UI 等 | 扩展动作,支持根据启动者的租户信息隔离人员选择机制 | UI 元素和共享任务的设置 人员选择规则 |
业务规则 | 规则 | 专用的业务规则 共享的业务规则,使用与租户相关的筛选器作为规则参数 | 业务规则的规则值设置 |
组件 | 共享的资源 | 隔离方法 | 可配置方法 |
持久化数据模型 | - | - | - |
数据库服务器(JDBC、SDO 等) | 数据源,连接池 数据库账户 数据库/模式/表/缓冲区池等 | 存储隔离:专用的表/模式或数据库,或共享的表/模式 访问隔离:通过专用数据源和连接实现 DBMS 级访问控制,或通过筛选器实现应用程序级访问控制 | 数据库/表/模式 固定的保留字段 扩展的子表 XML 扩展字段 |
文件和 I/O | 目录 文件 | 专用的目录/文件 共享的文件,使用与租户相关的筛选器 | 目录结构或文件格式 |
目录服务器 (LDAP) | 目录树 模式 | 每个租户专用的子树 共享的树,使用与租户相关的筛选器 | 目录树结构和模式定义 |
业务逻辑 | - | - | - |
身份验证/授权 | 组织结构/特权存储库 登录/授权模块 | 根据持久化数据模型(LDAP、数据库等)隔离的存储库 扩展的感知租户的用户注册插件 | 组织结构、角色、特权及其映射关系 |
全局 Java 对象 | 静态变量 单实例类的变量 | 每个租户专用的(静态)变量,使用容器对象 | N/A |
远程访问(套接字/Http、RMI、CORABA、JNDI、Web 服务) | 远程服务 URI、端口号、用户名、密码等连接参数 | 专用的远程服务 共享的远程服务,把租户信息传播给远程服务 | 连接参数 |
EJB | 有状态 EJB 实例 数据源连接,实体 Bean 的表 队列,发送者的 MDB 标识 | 每个租户专用的有状态 EJB 实例、MDB、队列 共享的实体 Bean,使用与租户相关的筛选器 共享的 MDB,在消息中嵌入租户标识信息 | 共享的实体 Bean 的保留属性 可定制的消息格式 |
日志 | 日志文件位置、内容、格式和配置 | 专用的日志文件 共享的日志文件,使用与租户相关的筛选器 | 日志格式和设置参数 |
缓存 | 缓存容器 | 专用的缓存容器 共享的缓存容器,使用与租户相关的筛选器 | 缓存设置参数 |
UI | - | - | - |
JSP | 应用程序范围的变量(tag、usebean、applicationContext 等) 声明变量 徽标、样式、布局等 | 使用与租户相关的筛选器选择应用程序范围的变量 专用的声明变量 | CSS、布局、图片等 |
servlet | 单线程 servlet servletContext | 单线程 servlet 使用专用的 servlet 使用与租户相关的筛选器选择 servletContext 变量 | N/A |
过程/工作流 | - | - | - |
BPEL 过程模板 | 模板级属性、活动、链接条件等 | 专用的过程模板 共享的模板,在过程实例中使用与租户相关的筛选器 | 过程模板的骨架 |
人工任务 | 动作、任务 UI 等 | 扩展动作,支持根据启动者的租户信息隔离人员选择机制 | UI 元素和共享任务的设置 人员选择规则 |
业务规则 | 规则 | 专用的业务规则 共享的业务规则,使用与租户相关的筛选器作为规则参数 | 业务规则的规则值设置 |
组件 | 共享的资源 | 隔离方法 | 可配置方法 |
持久化数据模型 | - | - | - |
数据库服务器(JDBC、SDO 等) | 数据源,连接池 数据库账户 数据库/模式/表/缓冲区池等 | 存储隔离:专用的表/模式或数据库,或共享的表/模式 访问隔离:通过专用数据源和连接实现 DBMS 级访问控制,或通过筛选器实现应用程序级访问控制 | 数据库/表/模式 固定的保留字段 扩展的子表 XML 扩展字段 |
文件和 I/O | 目录 文件 | 专用的目录/文件 共享的文件,使用与租户相关的筛选器 | 目录结构或文件格式 |
目录服务器 (LDAP) | 目录树 模式 | 每个租户专用的子树 共享的树,使用与租户相关的筛选器 | 目录树结构和模式定义 |
业务逻辑 | - | - | - |
身份验证/授权 | 组织结构/特权存储库 登录/授权模块 | 根据持久化数据模型(LDAP、数据库等)隔离的存储库 扩展的感知租户的用户注册插件 | 组织结构、角色、特权及其映射关系 |
全局 Java 对象 | 静态变量 单实例类的变量 | 每个租户专用的(静态)变量,使用容器对象 | N/A |
远程访问(套接字/Http、RMI、CORABA、JNDI、Web 服务) | 远程服务 URI、端口号、用户名、密码等连接参数 | 专用的远程服务 共享的远程服务,把租户信息传播给远程服务 | 连接参数 |
EJB | 有状态 EJB 实例 数据源连接,实体 Bean 的表 队列,发送者的 MDB 标识 | 每个租户专用的有状态 EJB 实例、MDB、队列 共享的实体 Bean,使用与租户相关的筛选器 共享的 MDB,在消息中嵌入租户标识信息 | 共享的实体 Bean 的保留属性 可定制的消息格式 |
日志 | 日志文件位置、内容、格式和配置 | 专用的日志文件 共享的日志文件,使用与租户相关的筛选器 | 日志格式和设置参数 |
缓存 | 缓存容器 | 专用的缓存容器 共享的缓存容器,使用与租户相关的筛选器 | 缓存设置参数 |
UI | - | - | - |
JSP | 应用程序范围的变量(tag、usebean、applicationContext 等) 声明变量 徽标、样式、布局等 | 使用与租户相关的筛选器选择应用程序范围的变量 专用的声明变量 | CSS、布局、图片等 |
servlet | 单线程 servlet servletContext | 单线程 servlet 使用专用的 servlet 使用与租户相关的筛选器选择 servletContext 变量 | N/A |
过程/工作流 | - | - | - |
BPEL 过程模板 | 模板级属性、活动、链接条件等 | 专用的过程模板 共享的模板,在过程实例中使用与租户相关的筛选器 | 过程模板的骨架 |
人工任务 | 动作、任务 UI 等 | 扩展动作,支持根据启动者的租户信息隔离人员选择机制 | UI 元素和共享任务的设置 人员选择规则 |
业务规则 | 规则 | 专用的业务规则 共享的业务规则,使用与租户相关的筛选器作为规则参数 | 业务规则的规则值设置 |
![]() ![]() |
EasyOA 是一个为小型企业开发的简单的办公自动化应用程序。这个应用程序主要包含一个休假申请过程。职员可以通过这个过程提出休假申请和查询自己的休假历史,而经理通过这个过程批准职员的申请。管理员有权查看公司所有用户的操作日志。
EasyOA 是一个典型的 J2EE 应用程序,它基于以下 IBM 中间件:
EasyOA 在设计时考虑到了支持多租户。如图 2 所示,支持的主要多租户特性如下:
本系列的第 4 部分将全面介绍这些特性的体系结构和实现。
![]() ![]() |
多租户设计模式(使用单一共享应用程序实例同时支持多个租户)对于提高 Web 应用程序的经济有效性非常重要。在本文中,我们讨论了总体体系结构设计原则,以及使用 IBM 中间件开发安全且可配置的多租户应用程序的相关技术。本文只是概述,后续文章提供关于这个主题的详细信息。
联系客服