传统的Java应用部署模式,一般遵循“硬件->操作系统->JVM->Java应用”这种自底向上的部署结构,其中JEE应用可以细化为“硬件->操作系统->JVM->JEE容器->JEE应用”的部署结构。这种部署结构往往比较重,操作系统、JVM和JEE容器造成的overhead很高,而很多时候一个Java应用并不需要跑满整个硬件的资源,导致这种模式的资源利用率是比较低的。

而另一方面,硬件虚拟化技术逐渐成熟:VMware Hypervisor、Xen、KVM、Power LPAR等技术能够帮助我们在同一个硬件上部署多个操作系统实例,而时下流行的OS Container技术如LXC、Docker等,则是把操作系统虚拟化为多个实例,实现更轻量级的虚拟化。无论哪个层面的虚拟化,其目的都是对资源利用率更加高效的追求,从而成为如今构建云计算平台底层架构的基础技术。

Java应用也可以通过同样的思路来实现高密度的部署。JVM虚拟化是比OS虚拟化更高一层的做法,可以更大程度的提高资源利用率,降低平均应用的部署成本。本文将介绍Multi-tenant JVM这一方案实现高密度Java应用部署的一些特点和思路。

背景介绍

早在2004年,Sun公司就提出过Java应用虚拟化这方面的想法。当时Grzegorz Czajkowski领导了一个叫做巴塞罗那的研究项目,该项目基于Java HotSpot虚拟1.5版本开发了Multi-Tasking Virtual Machine(MVM)。MVM的目的旨在提高Java程序的启动速度,节省内存开销。不过自从Sun被甲骨文收购后,我们没有听到关于该项目的任何新的进展。

尽管我们没有看到MVM成功产品化,不过它却留下两个JSR规范:JSR121和JSR284。对于JSR284,目前在java.net上有一个实现它的孵化项目。

从2009年开始,我所在的IBM Java团队开始研究Java应用的SaaS化方案,即让一个应用实例服务于多个租户。为了保证多个租户在使用同一个应用实例时候数据的隔离,该方案在应用这个层面做了一些Bytecode Instrument(BCI)的工作,主要通过改写getstatic/putstatic使每个租户有独立的类的静态数据拷贝而没有相互影响。但是,该方案在Bytecode层面更改带来的额外性能开销, 以及Java Reflection等访问带来的安全性/正确性的问题。 而且,除了数据上的隔离,也需要针对关键性的资源譬如CPU、Heap、IO等资源的使用进行管理,于是该方案下沉到了JVM层面,形成现在的多租户JVM(Multi-tenant JVM)方案。

Multi-tenant JVM是JVM层面的虚拟化,其思路是把多个Java应用部署在同一个JVM上,让这些应用共享底层的GC、JIT、Java运行时库等基础组件。除了IBM的团队之外,爱尔兰的Waratek公司也实现了多租户的JVM。和IBM Multi-tenant JVM类似,Waratek允许多个应用运行在同一个CloudVM上,每一个应用运行在一个叫Java Virtual Container(JVC)的容器里。从现有公开的资料开看,IBM Multi-tenant JVM是基于Java 7的,而Waratek是基于Java 6的,两者支持的CPU架构和平台也有所不同。

此外,JEE方面在两年前也有讨论计划增加对PaaS和多租户的支持,这项提议旨在定义PaaS环境下如何使得JEE应用支持多租户,保证不同租户在使用这些应用时相互隔离,以及资源方面的管理(如JMS资源),不过该项提议已经推迟到JEE 8。

除了提升部署密度之外,多租户的另一项好处在于应用启动的加速。快速的程序启动受益于不同的应用共享同一个JVM,我们称之为javad。Java核心的类库在javad运行后,不再需要被重新装载和定义。你也许可以用Nailgun来加速你的启动时间,但Nailgun的问题是没有安全的数据隔离,这包括类的静态数据以及Java属性值,而且Nailgun在易用性等方面也不如Multi-tenent JVM。

多租户JVM的实现思路

跟传统JVM相比,多租户JVM的主要工作围绕隔离而进行,其针对JVM/JDK的改动主要实现三个方面的目标:

  1. 租户之间的数据隔离
  2. Java类库支持多租户语境
  3. 资源管理隔离

租户之间的数据隔离

让每个租户应用拥有独立的类静态数据拷贝,这个目标主要通过修改getstatic/putstatic字节码指令实现。下面是一个简单的例子:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

每一个运行在Multi-tenant JVM上的程序都有不同System.out实例。就java.lnag.System内部实现来说,out是其类静态变量:

public final class System {

    // The standard input, output, and error streams.
    // Typically, these are connected to the shell which
    // ran the Java program.
    /**
     * Default input stream
     */
    public static final InputStream in = null;
    /**
     * Default output stream
     */
    public static final PrintStream out = null;
    /**
     * Default error output stream
     */
    public static final PrintStream err = null;
……
}

Multi-tenant JVM对于标准的JVM行为进行的更改如下:

  • 每一个租户第一次使用java/lang/System时,都会触发它的初始化,也就是<clinit>。而一般的JVM,java/lang/System只会被初始化一次。
  • <clinit>的执行,对于每一个静态成员变量存取,都被重新定向到了具体的租户存贮空间。比如对于out = null赋值,putstatic执行时实际上会找到当前的租户,然后把值存到该租户的空间去 ,getstatic有着类似的道理。

Java类库支持多租户语境

这部分主要通过改造类库实现,具体的功能包括:

  • System.exit(code) 调用只会使当前租户退出,而不会令整个JVM退出。而租户申请的一些诸如File/Socket句柄之类系统资源,会随着租户的推出而被释放。
  • 租户A不可能通过类似如下
ThreadGroup group = Thread.currentThread().getThreadGroup();
ThreadGroup parent = group.getParent();

枚举线程的办法获得租户B的线程。不同租户的线程分属于不同的线程组。

  • Java属性值的隔离,比如同样的语句System.getProperty(“name”)对于不同的租户可能是不同的值。

资源管理隔离

这是Multi-tenant JVM很重要的功能。在Multi-tenant JVM上,Heap/CPU/Disk IO/Net IO这些资源的使用是受资源策略保护的,比如你可以去限制某个租户它的CPU最少可以使用20%,而在系统空闲时,最大可以用到100%。

Multi-tenant JVM通过Token Bucket来对IO(Disk/Net)和CPU资源进行管理。对于IO而言,Multi-tenant JVM截获IO有关的OS API调用,使得IO发生之前受制于我们预先规定的资源策略。我们举个网络IO的例子,例如Java程序从Socket的读取操作,JDK内部的实现通过JNI实际上会对应到系统的API调用

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

在recv调用发生之前,Multi-tenant JVM通过资源策略保证租户的IO使用带宽不会超过给它设定的限制。关于网络IO,我们这里有一个很好的演示:

用简单的-Xlimit:netIO=6M参数限制运行在Multi-tenant JVM上的Ftp Server带宽上限读写各为6Mib/s。

关于对CPU管理,Multi-tenant JVM实现的基本的思路是,把租户线程所花费的CPU时间量化为Tokens,运行时每一个租户线程都会被周期性检查是否其当前CPU时间的使用超过了给它设定的限制。如果超过,当前线程会被挂起,直到满足限制为止。周期性检查的代码是由Multi-tenant JVM插入到租户线程里去的,对于用户程序而言完全是透明的。

Multi-tenant JVM对于Heap的管理建立在Balanced GC Policy基础之上。同一般的Java程序类似,你可以使用-Xms/-Xmx为租户程序设定最大/最小的堆内存值。Balanced GC Policy基于Region对Heap进行管理,每个租户程序根据-Xms/-Xmx的设定来为其分配Region,而租户对象的分配也必然只能发生在它自己拥有的区域内。

多租户JVM的用法与限制

IBM发布的Java 7 R1默认支持多租户JVM,在命令行上添加-Xmt参数即可启用。由于多租户JVM对JVM的变更,JNI Native Libraries、JVMTI以及GUI programs在多租户状态下的使用是受限制的。Multi-tenant JVM并未实现对JNI的隔离,所以不同的租户应用不能装载依赖同样的JNI Native Lib,所有发生在JNI Native Lib里的IO,不会受限于该租户资源消费策略。同样的情况适用于CPU以及Memory。

Multi-tenant JVM目前没有实现对JVMTI Agent的改造用以支持我们前面所描述的静态数据的隔离,这可能会对用户如果想调试Java核心类库代码(不是用户代码)造成困扰。

关于GUI,Multi-tenant JVM没有实现底层对于UI程序消息队列的隔离,所以不支持在同一个Multi-tenant JVM运行大于1个的GUI程序。

还有一点,不要在非Daemon线程里写 “暴力”的死循环代码,例如:

while(true)
{
try () {
    ....
} catch(Throwable t) {
{
}

}

最后需要注意的是,当开启IO资源控制时,尽量一次写出更多的字节,避免影响程序的IO性能。

总结

Multi-tenant JVM目前在应用启动时间和更小的内存占用开销方面已经被证实有效。根据目前的一些基准测试结果来看,对于简单的应用,相较于一般JVM,Multi-tenant JVM可以获得5~6倍的运行个数。后续计划发布的版本仍然会集中在提高启动时间、更小的内存开销这两个方面,也会陆续有一些性能的报告发布。

长远来看,Multi-tenant JVM会基于用户、IBM产品线以及技术社区等的反馈,做进一步的提高,以及解决一些目前所存在的局限性,比如对于JNI隔离的支持,JVMTi的多租户支持等等。

高密度Java应用部署的一些实践的更多相关文章

  1. 高性能、高流量Java Web站点打造的22条建议

    @http://www.csdn.net/article/2013-12-20/2817861-22-recommendations-for-building-effective-high-traff ...

  2. Asp.NetCore程序发布到CentOs(含安装部署netcore)--最佳实践(二)

    Asp.NetCore程序发布到CentOs(含安装部署netcore)--最佳实践(一) 接上一篇 3. Nginx配置反向代理 3.1 cnetos 安装nginx 首先,我们需要在服务器上安装N ...

  3. 编写高质量java代码151个建议

    http://blog.csdn.net/aishangyutian12/article/details/52699938 第一章  Java开发中通用的方法和准则 建议1:不要在常量和变量中出现易混 ...

  4. [转载]Java 应用性能调优实践

    Java 应用性能调优实践 Java 应用性能优化是一个老生常谈的话题,笔者根据个人经验,将 Java 性能优化分为 4 个层级:应用层.数据库层.框架层.JVM 层.通过介绍 Java 性能诊断工具 ...

  5. rocketmq高可用集群部署(RocketMQ-on-DLedger Group)

    rocketmq高可用集群部署(RocketMQ-on-DLedger Group) rocketmq部署架构 rocketmq部署架构非常多,都是为了解决一些问题,越来越高可用,越来越复杂. 单ma ...

  6. [ 高并发]Java高并发编程系列第二篇--线程同步

    高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...

  7. paip.java gui swt/jface 最佳实践

    paip.java gui swt/jface 最佳实践 1. 工具:Eclipse +jigloo4 1 2. 安装插件: 1 1. IMPORT swt lib 2 2. 新建立窗体 2 3. 运 ...

  8. 100个高质量Java开发者博客

    ImportNew注:原文中还没有100个.作者希望大家一起来推荐高质量的Java开发博客,然后不段补充到这个列表.欢迎你也参与推荐优质的Java开发博客.(声明一下:我们的数学不是体育老师教的!:) ...

  9. [转] JVM 调优系列 & 高并发Java系列

    1.JVM调优总结(1):一些概念:http://www.importnew.com/18694.html 2.JVM调优总结(2):基本垃圾回收算法:http://www.importnew.com ...

随机推荐

  1. 开发软件设计模型 visual studio UML

    http://www.ibm.com/developerworks/cn/rational/rationaledge/content/feb05/bell/ http://msdn.microsoft ...

  2. vue-cli + webpack

    vue-cli + webpack 关于vue.js vue.js是一套构建用户界面的 轻型的渐进式前端框架.它的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件.使用vue可以给你 ...

  3. JQ实现3D拖拽效果

    <!DOCTYPE HTML> <html onselectstart='return false'> <head> <meta http-equiv=&qu ...

  4. php调用whois接口域名查询

    由两部分组成,一个index.php文件,一个whois的接口文件: <html> <head> <title>域名到期查询</title> <s ...

  5. 防止横竖屏时,iphone自动缩放的一段js代码

    function orientation_change() {     var viewport = document.querySelector('meta[name="viewport& ...

  6. map(function, sequence)

    map(function, sequence) :对sequence中的item依次执行function(item),见执行结果组成一个List返回: >>> lt = range( ...

  7. nginx 域名rewrite跳转

    转自:http://blog.csdn.net/xingfujie/article/details/7337832 需求:nginx规则,所有对OA.bccom.info的访问,redirect到uc ...

  8. [string]Codeforces158C Cd and pwd commands

    题目链接 题意很清楚 和linux的语句是一样的 pwd输出路径 cd进入 ..回上一层目录 此题完全是string的应用 String的用法 vector<string> s; int ...

  9. [模拟]Codeforces509C Sums of Digits

    题目链接 题意:给n个数a[i], 要求b[i]每位数的和等于a[i], 并且b[i]要严格递增 求最小的b[i] b[0]最小一定是X9999...这样的形式 后面的b[i]位数一定大于等于前一个 ...

  10. easyui源码翻译1.32--TimeSpinner(时间微调)

    前言 扩展自$.fn.spinner.defaults.使用$.fn.timespinner.defaults重写默认值对象.下载该插件翻译源码 时间微调组件的创建基于微调组件.它和数字微调类似,但是 ...