多租户(Multi Tenancy/Tenant)是一种软件架构,其定义是:
在一台服务器上运行单个应用实例,它为多个租户提供服务。
在这种架构上,应用程序被设计成能将自己的数据、配置进行虚拟的分区,以便每个租户都感觉到自己是在一个私有的、可定制化的应用实例上工作。
这背后代表的是资源的伸缩能力。即在同样硬件配置,不同租户在数据分离的情况下,共享同样的应用程序,还随着租户数量的提升,应用程序的水平扩展,并维持着类似的性能指标(一致响应时间等)。这同时意味着资源使用效率的提升,以及节省 IT 资产的投入。
在共享与安全之间取得平衡,是多租户架构最主要的关注点。更多的资源共享有益于灵活性的上升和总体成本的下降,但单个租户的安全性需求要有额外的技术手段来保证。
多租户的设计牵涉到两层:
数据层的多租户能力是一个很大的主题,主要是指不同的租户如何共享或分离数据,并满足安全性。本文则主要关注的是应用 / 平台层的多租户规范与方案。
Java 平台已经在 J2EE 中提供了部分多租户能力。作为 Oracle 要给 Java 平台提供 PaaS 的目标中的一部分,多租户特性在 Java EE 7 最初的规范中有多处体现,见表 1。
名称 | 规范 | 相关简述 |
---|---|---|
JSR342 | Java EE 7 | 平台将定义应用元数据描述符,用以描述 PaaS 的执行环境,例如多租户,资源共享,服务质量(QoS),和应用间的依赖关系。 |
JSR338 | JPA 2.1 | 数据层的多租户支持。 |
JSR340 | Servlets 3.1 | 安全,会话状态,资源和 web 应用中其它隔离需求的多租户支持。 |
JSR343 | Java Message Server 2.0 | 消息服务的多租户支持。 |
JSR345 | EJB 3.2 | 改进 EJB 架构实现数据层的多租户支持。 |
Java EE 7 对 PaaS 模型支持的规范要求:对 PaaS 环境中的同一个 PaaS 应用能够被多个租户使用,每个租户使用不同的应用程序实例,同时又能共享资源。
PaaS 规范中要求每个租户都有一个 tenantId,这个 ID 对于某个 PaaS 供应商的全部租户来说,是唯一的。sevlet 容器可以将外部请求正确映射到对应的租户实例,并在随后的所有业务流程中以 tenantId 为处理的依据,保证不同租户所使用资源的隔离。同时租户的界面等是可以定制化的。
例如带给 JPA 的影响就包括:
但在 2012 年 8 月 30 号,Oracle 的 Java EE 7 规范主管 Linda Demichiel 在博客中宣布因为云领域相关的应用方式不够成熟,以及 Java EE 7 版本的发布压力,将把 PaaS 和多租户支持的部分推迟到 Java EE 8(以下翻译引用自 InfoQ):
尽管我们的愿望很美好,但是在我们日程表中,云相关的进展仍然很慢。一部分原因在于构建分配(provisioning)、多租户(multi-tenancy)、弹性(elasticity)等领域以及应用部署部分仍然不成熟;一部分原因是我们保守的做事方式,我们尽力把事情做‘正确’,但是在开展这项工作时,我们在云领域仍然缺乏足够的行业经验。因此,我们认为,若要提供对标准化的基于 PaaS 的编程方式和多租户的完善的支持,就可能会将 Java EE 7 的发布推迟到 2014 年春天。该时间是两年之后,比规划晚了一年。在我们看来,拖得时间太长了。
因此我们向 Java EE 7 专家组提议调整我们的计划,坚持我们当前的目标发布日期,而将我们日程表中 PaaS 和多租户支持的部分推迟到 Java EE 8。”
那么在 Java 8 中,与云相关的最重要的两个特性就是多租户与模块化。具体说就是:
来自 Red Hat 的 Mark Little 认为下一个 Java 版本支持这两个特性将使得进行大规模云部署变得可行。
同时,Oracle 的规范领导 Linda Demichiel 提到,即使因为云端应用还没完全做好标准化的准备而导致 Java EE 7 被迫放弃 PaaS 部分,但诸如 Oracle、Red Hat、IBM 和 CloudBees 等供应商已经开始提供在云上运行 Java EE 程序的能力。
因而本文的剩余部分将试图对几种比较流行的 Java 平台解决方案中涉及多租户的部分做出阐述。
前面提到,预期 Java 8 中对多租户的支持需要在 JVM 这一层次中做到。那么它的细节如何,又原因何在?
对于一个传统的 J2EE 容器(任意一个应用服务器)来说,它启动一个 JVM,然后不同的 WEB 应用部署其上,供并发用户访问。这已经在概念上接近了多租户。见图 1。
但这种结构在实际中很少被使用到,因为各 Web App 之间没有隔离机制。对于多数 J2EE 应用来说,上图的模式通常被简化为仅有一个应用,运行在一个 JVM 上,后台一个数据库 /schema 为之服务,即“one app per app-server per JVM”。但无论哪种模式,都依然离多租户的数据 / 配置隔离以及共享需求,和应用程序的水平扩展能力需求相差甚远。主要原因在于:
另外传统的 J2EE 应用也与云时代的多租户在业务模式上也有不同。传统 J2EE 模式强调的是多用户,本身的应用实例是一个;而多租户则在同一个应用实例上做了复制,是多个应用实例(彼此内容相同),一个实例服务于一个租户。
随之而来的是传统的 J2EE 为了应对多租户场景,会为每一个应用启动一个 JVM,每个 JVM 彼此独立,消耗各自的资源。这无法完美实现多租户的一个典型特征:在最大共享资源的基础上做好各实例间的隔离。
因此,现在有一些 IT 厂商做出了自己的尝试。Waratek 是澳大利亚的一家公司,他们将在新南威尔士大学期间进行的一个关于元循环(meta-circular,是指使用语言自身来实现其运行环境)抽象机解释器和动态编译框架的项目进行了商业化,开发出 Waratek Cloud VM。这个基于 Java 语言自身开发的虚拟机给运行在虚拟架构上的 Java 程序提供了一种高密集度宿主模型,即多个应用程序可以同时运行在单个的 JVM 上,每个应用有自己的 Java 虚拟容器(Java Virtual Container,JVC)。如图 2。
Waratek 在 JVM 内部构建了一个多租户的虚拟容器架构,每个 JVC 包含一个完全隔离又可控的共享 JVM 镜像——它是一个元循环的虚拟机,和其它 JVC 一起分享主机 VM 的环境(堆、类、JIT)。就如 hypervisor 对物理机一样,JVC 对 Java 平台也做了虚拟化。它具有以下特点:
每个 VC,有自己的堆空间。Waratek 提供了自动垃圾收集服务,在一台主机上,只有一个垃圾回收器,它负责给多个 VC 上的堆空间进行内存无用对象清理。相比较于多 App/ 多 JVM 模式引起的多个垃圾回收器进程同时工作,Waratek 在 CPU 资源的竞用上好很多。
VC 在运行时的内存片彼此隔离则保证了应用间的安全性。传统的 J2EE 多应用由于堆内存空间是共用的,很难避免一个正常运行的应用的内存区域被恶意侵占。或者由于一个应用调用了 System.exit(0),导致同一个 JVM 上所有的应用被迫一起退出。
对于核心的 JDK 库,每个 VC 不需要重复加载,而是彼此共享,这极大的简化了应用的启动。
单个 VC 的资源可配置和可计量对于云应用至关重要,这等同于对物理机器和操作系统的虚拟化后,每个虚机的可配置和可计量。它支持通过对主机的 JVM 进行 JVMTI(Java Virtual Machine Tool Interface)调用获取每个 VC 上应用程序的资源耗费情况。
同时,这类 JVC 对于 Scala、Jython 和 JRuby 等运行于 JVM 之上的语言也是同样直接支持的,而且由于是二进制代码级别的兼容,因而无需为迁移到 Cloud VM 而改动程序。
在解决了共享与隔离的问题后,Waratek 甚至可以让 64 个 JBoss 的应用服务器运行在一个 JVM 之上,这样的性能很惊人。
Waratek 公司的这个产品可以在 RHEL 和 CentOS 上安装。由于它是在 JVM 之上实现的虚拟化,因而不需要再在操作系统层面安装任何虚拟化支持。出于测试目的,可以在一台干净的物理服务器上安装 Linux,然后仅仅运行一个 JVM,并配置多个 JVC,每个 JVC 上运行一个不同的应用服务器实例。
Waratek 公司为它的产品申请了约 150 项专利,其中 50 个已经被授权。目前尚不清楚这些专利会否对 Hotspot、JRockit、J9、Zing 等 JVM 的云化开发构成影响。
回页首
当前市面上支持 Java 语言的 PaaS 很多,包括 Amazon Elastic Beanstalk、CloudBees、Cloud Foundry、Google App Engine、Red Hat OpenShift、Jelastic、Heroku 等。它们各有特点,例如 GAE 线性扩展能力很强,在大规模访问量时依然有良好性能表现,但它对 Java 程序做了诸多限制,像文件与网络 IO 包等就不可使用,以及不支持很多常用的框架(如 Spring、Struts)。Beanstalk 是基于 EC2 的,它提供 Tomcat 实例,并与 Amazon 的 Rest API 集成可访问后台关系数据库。在表 2 中给出一些关于它们的具体数据。
Amazon Elastic Beanstalk | CloudBees | Cloud Foundry | Google App Engine | Heroku | Jelastic | Red Hat OpenShift | |
---|---|---|---|---|---|---|---|
支持语言 | Java, .Net, PHP | Java | Java/Spring, Groovy/Grails, Ruby Rails & Sinatra, Node.js, .Net via extension | Java, Python | Ruby, Java | Java, PHP, Ruby, Scala, Groovy | Java, Java EE, Python, Perl, PHP, Ruby, node.js |
开源 | N | N | Y | N | N | Y | |
存储库 | Maven, Git, SVN | https://github.com/cloudfoundry | Maven, Git, SVN | Git, SSH, Rsync, Maven, sftp | |||
数据库 | MySQL | MongoDB, MySQL and Redis | Amazon RDS (MySQL), MongoDB, Redis, CouchDB... | Maria DB, MySQL, PostgreSQL, MongoDB, CouchDB | MySQL, PostgreSQL, MongoDB |
以 CloudBees、Jelastic 和 OpenShift 为例,对于 Java 应用开发人员来说,它们对纯 Java 的支持极好:即代码无需修改,支持流行的 Java 应用服务器。它们也不会导致平台绑定,而是易于在不同 PaaS 间迁移。
这些 PaaS 的使用模式都比较相似。首先注册账号,然后申请应用程序,获得免费的配额、域名、代码库 URL,再上传自己的代码,进行持续集成。
先来看一下通用的多租户,从 SaaS 角度讲,多租户牵涉到多个层面:底层硬件、操作系统、应用服务器、数据层、应用代码等。每个层面的虚拟化策略,甚至是没有任何虚拟化仅仅是逻辑上的分离策略,都是这个架构的一部分。
PaaS 的多租户主要体现在应用服务器这个层面,又有两类部署模型:
第一种模式是比较传统的租户模式,它在安全性隔离上做的很好,定义了一个租户所能访问的资源,应用本身修改小或不用修改,但不够灵活。而对于第二种共享容器多租户模型来说,通常使用基于 OSGI 的分区、租户资源上下文、和租户组件的按需加载来共享 JVM 资源,能有更低的成本和更高的灵活性,但需要程序上相应的变化。总体来说,决定采用哪种租户架构,还要有更多的考虑。
Jelastic 使用的是基于容器的虚拟化方案,这与传统基于虚机的方式不同。(若不考虑 PHP)它是一个 Java 的宿主系统,对于云用户来说,他们得到的是 J2EE 容器(Tomcat、Glassfish、Jetty)。显而易见,多租户的实现依赖于容器的实例数量,即每个应用程序都是彼此独立的,租户 A 的应用所在的 JVM 不同于租户 B,包括后台的数据库也各不相同。这个隔离的粒度是 App Server 级别的(当然一个租户拥有多个容器实例也很常见)。
顺带一提的是 Jelastic 资源管理方式的不同,即它独特的自动垂直扩展(automatic vertical scaling)能力:基于应用的负载状况,动态分配 RAM 和 CPU,当负载增多时,系统会主动给应用增加资源,而负载减少时,将资源自动返回给操作系统。其背后的机制是在 Java 7 中首先导入并在 Java 6 update 中也加入的 G1(Garbage First)垃圾收集器。
Red Hat 的 OpenShift 使用了一种“multi-tenant in the OS”(而不是“multi-tenant hypervisor”)的方法,它采用了两层多租户机制:
具体说,就是利用 RHEL 操作系统自身的控制组和 SELinux 来实现隔离的安全机制;用 RHEL 的资源管理实现多租户环境设置;用 RHEV(Red Hat Enterprise Virtualization)实现虚拟化;用 JBoss 中间件实现 J2EE 全支持。OpenShift 的基础就是 RHEL。
那么从 Java 应用的角度来看,它的多租户体现在多个 RHEL 实例 + 运行在 JBoss 上的多个应用组合上。这种 RHEL 操作系统级别的虚拟化方案比基于 hypervisor 的虚拟化方案强在无需硬件层面的虚拟能力支持,没有额外开销,但弱点是不够灵活,难以支持其它 OS,例如 windows。
多租户对成本的节省是很显著的,尤其是对于一些分布在不同时区的大企业尤其如此。其内部数据中心的硬件资源利用率能达到很高(例如 70%~80%)的程度。
传统的租户实现通常是专用的现有的 PaaS 在多租户实现上主要是依赖于操作系统或者 Java 容器,同时在底层也常常依赖于 IaaS,多租户的能力和虚拟化的目标(包括了硬件、OS、应用服务器、数据库等)相关。但类似于 Waratek 在 JVM 的虚拟化尝试则是最近的趋势,它对 PaaS 层面的多租户支持提升了资源的利用效率。Oracle 注意倾听了 PaaS 厂商对云平台的建议,我们期待在 Java EE 8 中能看到这部分功能。