浅析多租户在 Java 平台和某些 PaaS 上的实现

多租户综述

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

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

在这种架构上,应用程序被设计成能将自己的数据、配置进行虚拟的分区,以便每个租户都感觉到自己是在一个私有的、可定制化的应用实例上工作。

这背后代表的是资源的伸缩能力。即在同样硬件配置,不同租户在数据分离的情况下,共享同样的应用程序,还随着租户数量的提升,应用程序的水平扩展,并维持着类似的性能指标(一致响应时间等)。这同时意味着资源使用效率的提升,以及节省 IT 资产的投入。

在共享与安全之间取得平衡,是多租户架构最主要的关注点。更多的资源共享有益于灵活性的上升和总体成本的下降,但单个租户的安全性需求要有额外的技术手段来保证。

多租户的设计牵涉到两层:

  • 数据
  • 应用 / 平台

数据层的多租户能力是一个很大的主题,主要是指不同的租户如何共享或分离数据,并满足安全性。本文则主要关注的是应用 / 平台层的多租户规范与方案。

Java PaaS 的多租户概况

Java 平台已经在 J2EE 中提供了部分多租户能力。作为 Oracle 要给 Java 平台提供 PaaS 的目标中的一部分,多租户特性在 Java EE 7 最初的规范中有多处体现,见表 1。

表 1. Java EE 7 中涉及多租户的 JSR
名称 规范 相关简述
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 的影响就包括:

  • 支持共享数据库、分离 schema:通过元数据可以对 schema/ 表映射进行重配置。
  • 支持共享数据库、共享 schema:厂商可以使用 tenantId 实现数据行级别的隔离。
  • 编程模型的影响:将对本地查询和直接 JDBC 访问进行限制。

但在 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 中,与云相关的最重要的两个特性就是多租户与模块化。具体说就是:

  • 多租户:指的是在一个 Java 虚拟机(JVM)中安全运行多个应用的能力。
  • 模块化:指的是把 JDK 重新组织为一套尽管互相依赖但却是定义清晰的模块。Java 开发人员的一个替代选择是使用 OSGi。

来自 Red Hat 的 Mark Little 认为下一个 Java 版本支持这两个特性将使得进行大规模云部署变得可行。

同时,Oracle 的规范领导 Linda Demichiel 提到,即使因为云端应用还没完全做好标准化的准备而导致 Java EE 7 被迫放弃 PaaS 部分,但诸如 Oracle、Red Hat、IBM 和 CloudBees 等供应商已经开始提供在云上运行 Java EE 程序的能力。

因而本文的剩余部分将试图对几种比较流行的 Java 平台解决方案中涉及多租户的部分做出阐述。

Cloud JVM

前面提到,预期 Java 8 中对多租户的支持需要在 JVM 这一层次中做到。那么它的细节如何,又原因何在?

对于一个传统的 J2EE 容器(任意一个应用服务器)来说,它启动一个 JVM,然后不同的 WEB 应用部署其上,供并发用户访问。这已经在概念上接近了多租户。见图 1。

图 1. J2EE 部署图
图 1. J2EE 部署图

但这种结构在实际中很少被使用到,因为各 Web App 之间没有隔离机制。对于多数 J2EE 应用来说,上图的模式通常被简化为仅有一个应用,运行在一个 JVM 上,后台一个数据库 /schema 为之服务,即“one app per app-server per JVM”。但无论哪种模式,都依然离多租户的数据 / 配置隔离以及共享需求,和应用程序的水平扩展能力需求相差甚远。主要原因在于:

  1. Web 应用彼此的运行时内存空间不是独立的;
  2. 数据库是分离的,但缺乏共享;
  3. 对加载的类无法完全彼此分开;
  4. 当需要水平扩展时,可以做集群(cluster)和负载平衡,但这种架构下存在很多技术难点,例如 session 复制就相当脆弱。

另外传统的 J2EE 应用也与云时代的多租户在业务模式上也有不同。传统 J2EE 模式强调的是多用户,本身的应用实例是一个;而多租户则在同一个应用实例上做了复制,是多个应用实例(彼此内容相同),一个实例服务于一个租户。

随之而来的是传统的 J2EE 为了应对多租户场景,会为每一个应用启动一个 JVM,每个 JVM 彼此独立,消耗各自的资源。这无法完美实现多租户的一个典型特征:在最大共享资源的基础上做好各实例间的隔离。

因此,现在有一些 IT 厂商做出了自己的尝试。Waratek 是澳大利亚的一家公司,他们将在新南威尔士大学期间进行的一个关于元循环(meta-circular,是指使用语言自身来实现其运行环境)抽象机解释器和动态编译框架的项目进行了商业化,开发出 Waratek Cloud VM。这个基于 Java 语言自身开发的虚拟机给运行在虚拟架构上的 Java 程序提供了一种高密集度宿主模型,即多个应用程序可以同时运行在单个的 JVM 上,每个应用有自己的 Java 虚拟容器(Java Virtual Container,JVC)。如图 2。

图 2. Waratek Cloud VM
图 2. Waratek Cloud VM

Waratek 在 JVM 内部构建了一个多租户的虚拟容器架构,每个 JVC 包含一个完全隔离又可控的共享 JVM 镜像——它是一个元循环的虚拟机,和其它 JVC 一起分享主机 VM 的环境(堆、类、JIT)。就如 hypervisor 对物理机一样,JVC 对 Java 平台也做了虚拟化。它具有以下特点:

  • 轻量级
  • 一个 JVM 可以 host 几十甚至上千个 VC,每个 VC 的大小可以从 1MB 到数 GB
  • VC 执行的每一方面都是隔离的,例如 CPU 优先级,内存上限和带宽限额等
  • VC 使用的每一方面都是可计量的,例如 CPU 使用了多少 Hz,内存 /IO 使用了多少 byte
  • VC 提供了像电力千瓦 - 时的计算指标:CPU 消耗了多少 GHz- 时,内存消耗了多少 GB- 时
  • 每个 .war/.ear 都有自己的 VC

每个 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

当前市面上支持 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 中给出一些关于它们的具体数据。

表 2. PaaS 对比图
  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 的多租户主要体现在应用服务器这个层面,又有两类部署模型:

  1. 专用容器部署:一个租户应用有一个或多个专用的 Java 应用服务器,应用对容器是一对多的关系。
  2. 共享容器部署:多个租户应用共享一组 Java 应用服务器,每个 Java 容器为不止一个租户提供服务。应用对容器是多对多的关系。

第一种模式是比较传统的租户模式,它在安全性隔离上做的很好,定义了一个租户所能访问的资源,应用本身修改小或不用修改,但不够灵活。而对于第二种共享容器多租户模型来说,通常使用基于 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 中能看到这部分功能。


展开阅读全文

没有更多推荐了,返回首页