多租户 JVM 技术前瞻

  • 崔康

2013 年 10 月 14 日

话题:JavaJVM云计算DevOps语言 & 开发架构

IBM 多租户 JVM(Multitenant JVM)目前随着 IBM JAVA 8 beta 版已经发布。通过在一个多租户 JVM 上运行多个应用程序,云系统可以加速应用系统的启动时间、减少应用系统内存消耗。主要开发者 Graeme Johnson 和 Michael Dawson撰文介绍了多租户云系统 JVM 背后的技术,并讨论了主要成本以及其带来的收益。

文章首先介绍了多租户 JVM 诞生的背景:

对于运行系统、提供服务所需的基础设施,云供应商必须权衡其成本消耗及其可带来的收益。这种利弊权衡迫使供应商寻找多种系统架构。云供应商目前可从无共享架构到多用户共享架构范围内进行选择。无共享架构中,供应商提供面向单一用户的软件、硬件及应用程序。多用户共享架构中,多个用户共同使用供应商提供的同一款应用软件,并共享底层软件、硬件。

选择框架主要需要权衡密集度 (density) 与隔离性 (isolation)。密集度指特定软硬件可提供的系统和服务的数目。共享度越高,密集度就越高。密集度越高,供应商成本越低。同时,共享度的增高将降低租户(tenants,指交付的单个系统或服务)间的隔离性。隔离性描述了单个租户对活动与其他租户数据的影响程度。

对于基于 JAVA 的租户,可按是否共享 JVM 对框架进行划分。任何共享顶层应用的框架一定共享了 JVM。共享 JVM 既可以节省内存又可以节省处理时间。但是在传统的 JVM 技术中,共享 JVM 将导致基础架构层失去隔离性,从而要求顶层应用提高自身隔离性以满足架构需求。本文介绍的多用户共享架构已在 IBM 最新发布的 JAVA 8 测试版中进行实验,并且成功。这个架构具有传统共享 JVM 架构优点,此外还表现出优于传统共享 JVM 框架的隔离性。

使用多用户共享 JVM 最大的优点是部署可以避免多个标准 JVM 相关的内存消耗。这种内存消耗主要来自:

  • Java 堆消耗成百上千兆的内存。而且即使对象是相同的,堆对象也不能在 JVM 之间共享。此外,JVM 还倾向于使用所有分配给他们的堆,即使它们只在在很短的时间才会用到峰值量。
  • 因为生成的代码是私有的并且消耗内存,所以 JIT 编译器需要占用几十兆的内存。代码的生成也需要消耗大量的处理器时间,而这会使应用运行变慢。
  • 类的内部结构占用内存(很多组成部分还会存在所有应用中,如 String,Hashtable)。而且这些结构在每一个 JVM 中都会有一个实例。
  • 每个 JVM 会给每个核分配一个默认的垃圾收集辅助线程,同时每个 JVM 还有多个编译线程。垃圾收集活动和编译活动可以一个或多个的 JVM 中同时出现,但是考虑到 JVM 会竞争有限的处理器时间,这种现象不是非常理想。

多租户 JVM 可以减少内存占用、降低处理成本,除此之外,相对于传统的在单个 JVM 上运行多个应用的方式,多租户 JVM 还可以提供了更好的隔离性。

另外一个益处来自启动时间的缩短:当第一个租户启动后,后续启动的应用将需要少于第一个的启动时间,因为 JVM 已经在运行了。启动时间的减少有利于运行周期短的程序的使用。而且我们在脚本处理时经常会用到这类程序。

当然,鱼和熊掌不可兼得。多租户 JVM 的缺点包括:

  • 相比于多个应用运行在单个隔离的 JVM 这种传统方式,多租户 JVM 的主要弊端来自于隔离性的降低。例如,多租户 JVM 中一个原生崩溃(native crash)可以影响到所有租户。
  • 为了执行 JVM 工作我们必须要执行多租户扩展(multitenancy extensions),这会对性能带来不好的影响。然后,这影响会随着租户数量的增加而下降——因为通过在同一个系统运行多个 JVM 我们可以避免处理器和内存的浪费。

接着,文章简单介绍了如何启动多租户 JVM。

为了和其它租户共享运行时间,应用程序使用者需要在启动应用程序时在启动应用程序时加入一个单变量,例如:java -Xmt -jar one.jar

这样操作的结果会使应用程序好像运行在其专用的 JVM 一样。但实际上它和其它应用程序运行在同一个 JVM 中。多租户 JVM 扩展使以这种方式启动应用成为可能,并且它还为共享 JVM 的多租户提供隔离性。当一个租户启动后,JVM 启动器会选择定位在现有的共享 JVM 后台程序(javad)或者在必要的时候启动,当第二个租户启动时,这个租户会找到现有的共享 JVM 后台程序并且用这个 JVM 进行运行,这样做的结果是这两个租户共同的引导程序会出现在 javad 过程。这种布置使租户可以共享大部分运行时的结构。我们只需要对命令行进行有限的修改,就可以实现通过多租户共享 JVM 运行现有的应用程序,这一方法很简单。

多租户 JVM 目前还有一些限制,主要包括:

  • JNI natives——多租户共享 JVM 并没有给 JNI native 提供隔离性。因此,含有用户添加的 JNI native 的应用程序在多用户共享 JVM 中运行并不一定稳定。这些应用程序可能会影响整个 JVM 的运行并获取其他租户的信息。考虑到原生代码需要足够的“信任”(例如众所周知的中间件),这个风险可能是可以接受的。此外,操作系统只允许共享 JVM 进程下载一份共享库的副本,而且这个副本是原生代码的所在地。这就导致当多个租户共享同一个库时,他们不能载入相同的原生代码。
  • JVMTI(Java 虚拟机工具接口):因为调试和分析功能会影响到所有共享 JVM 服务器的租户,所有多租户 JDK 目前还不支持这些功能。这也是我们将来工作中要解决的问题。
  • GUI 项目:诸如 SWT 的库在原生层层是全局声明的,所有多租户 JDK 目前还不支持这些库。

在 11 月 1 日开幕的QCon 全球企业开发大会(上海站),参与者将有兴趣聆听 Java 专题“Java Innovation”讲师的经验分享:

即将发布的 Java 8 可以说是自 1995 年 Java 初发布以来最大的语言层面的改动之一.其中最主要的变化就是 Project Lambda 的引入,而 Project Lambda 背后的核心就是 invokedynamic 这个新增的 JVM instruction。

本主题以面向普通开发人员的角度针对 Project Lambda 对 Java 语言带来的变化,以及 invokedynamic 是什么,它的来源及与 Lambda 的关系进行阐述.附带会介绍 invokedynamic 对 JVM 上其它语言比如 JRuby 的影响。

JavaJVM云计算DevOps语言 & 开发架构