多租户技术,让一个软件实例同时服务于不同的组织,在云计算环境中被广泛运用,极大的节约了基础设施资源。但是,云计算环境中使用最广的Java语言却没有提供相应的多租户功能。为此,云服务提供商不得不对自己的应用服务器进行虚拟化,以此来提供多租户功能。但是这也使得用户必须修改应用来适应各个云服务提供商的多租户技术,增加了工作量,也使得同一个应用无法在不同的云计算平台上使用。因此,为标准Java虚拟机(JVM)添加多租户功能已经变得尤为迫切,让任何Java应用可以不做任何修改,直接实现多租户的功能。
目录
2 技术综述
2.1 多租户技术
2.2 JSR 121
2.3 JSR 284
2.4 JVM的垃圾回收
2.5 本章小结
2 技术综述
2.1 多租户技术
多租户技术是一种软件体系架构设计,目前主要的多租户应用主要是用于软件即服务的应用程序中。在云计算服务中,资源是具有高度可伸缩性的,资源的分配的是由实际环境中,根据应用的实际使用来决定的。而多租户技术则是探讨和实现在基于相同的用户环境和应用程序下,如何确保多租户的各自程序能正常运行以及各用户间的数据的隔离。
多租户技术其实可以看作是一种对应用程序的虚拟化,是多个组织(租户)共同使用一个应用程序实例,但是每一个租户在数据集和配置方面有相应的虚拟划分。通过多租户技术可以达到表面上看每个租户都运行的是一个自我定制的应用,实际是多个租户使用的是运行在同一台服务器上的同一个应用程序实例。基础架构层的多租户结构是一种最简单的软件栈(software stack)概念,一个栈对应了一个租户。从最底层的硬件到最上面的应用程序层,多租户概念存在于软件栈的各个层面。在不同的软件栈层面,它代表了不同的部署密度,所以在不同的场景中,决策者需要选择不同的多租户策略。
图2.1和图2.2显示了在不同软件栈层里的多租户的实现。层级越高,整个软件架构中涉及到的用户配置信息更多,同时其部署维护代价更小,但开发成本相应增加,层级越低的多租户场景与之相反。SO 到 S1 反应了软件架构的共享粒度越来越大,而隔离性却越来越差。
SO:单例情况,完全运行在独立的环境中,没有任何共享部分。
S1:多操作系统,通过虚拟技术,在一台硬件上虚拟出多个操作系统。这里共享的是硬件资源。
S2:多软件栈,多个租户在同一个服务器上运行完全独立的相同应用,每个应用的所属的软件栈都完全不同。共享的是操作系统和硬件资源。
S3:多应用实例,多个租户的应用所属的软件栈底层部分相同,如中间件,数据库一致。这里共享的是数据库,中间件以下的部分。其中共享数据库是指数据库资源,但其中租户的数据集在数据库是以不同分库的形式来达到完全隔离的。
S4:多租户,这里是指支持多租户的应用,但数据集是分开的。
S5:共享多租户,这种形式不仅包含了多租户应用,其它各租户的数据
集也是放在一起的。
多租户的技术不仅为租户带来了方便,也为云计算服务提供商降低了成本,其主要的优势表现在以下几个方面:
1、以服务的形式来提供软件,比起自身维护一套独立的系统成本更低。
2、部署更简洁,启动和运行更快,同时迭代更新更快,更容易。
3、让租户专注于自身服务的质量,而不是应用运行环境。
4、资源共享,减少资源浪费,节约成本。
5、租户使用同共享的环境,方便提供商管理,维护和升级。
2.2 JSR 121
JSR121规范中定义了一套API来隔离应用,使得同时在一个JVM上运行。
通常情况下,一个Java应用程序有一个JRE (Java运行时环境)与之相对应。每一个Java应用对应一个独立的OS进程,多个Java进程通过进程实现完全独立,一个Java进程崩溃了,其它进程不会受到影响。而与通常情况不一样的是,JSR121定义的是多个Java应用运行在一个JRE里,即只存在一个唯一的Java进程,共享一个JVM。图2.3展现了JSR121所定义的多租户环境。
在JSR121中,每一个运行的Java应用被称为一个Isolate,所有Isolate共享同一个JRE, Isolate之间是相互隔离的,它们有各自的生命周期,在运行逻辑中是彼此不受影响。
Java本身是支持多任务,一般情况下, Java的多任务是指多个线程通过调用不同的应用的main()函数。JSR121所定义的JVM在这种情况下,还需要做到以下四点:
1、隔离性:每个租户的应用必须有自己的VM环境。多个租户的应用环境不会相互影响。JSR121允许每个Isolate共享所有JDK的基类,但是不允许Isolate通过这些基类来共享彼此的数据集。它保证了每个Isolate的基本属性是独立,比如静态变量, AWT线程,结束钩子(shutdown hook),Properties,I/O 等。
2、 可靠的终止:当一个租户的应用出错停止里, JVM能够有效的停止该应用,并能回收相应的资源。与线程的stop(), interrup()不一样,提供System.exit()和 System.halt()来实现 Runtime.exit()和 Runtime.halt()。
3、高效:减少资源使用的冗余,在多租户的应用之间最大限度的进行共享。资源的共享主要是指在应用启动过程中的共享,尤其是对JDK基类的共享。Java 5最少需要128M的RAM, JVM启动过程所需的库与系统的核心类占了所消耗的内存大部分。
JSR121中没有明确要求需要实现对JNI (本地接口)的隔离,也就是说JSR121定义的隔离级别主要是指在Java部分。
2.3 JSR 284
JSR284定义了一套在JSR121里没有涉及到的资源管理规范,以此来实现一种“虚拟的 Java虚拟机”,可以说 JSR284 是 JSR121 的一种补充。JSR284规范定义了在一个 JVM 实例上,实现对多个 Java 应用(即 JSR121 里定义的Isolate)进行资源的管理和控制。
JSR284中提到的虚拟与虚拟机中的虚拟概念一致,同样都是把资源分配给虚拟的用户,只是具体谁来负责资源的管理不同,虚拟机(如XEN)则是交给虚拟监视器(Hypervisor,一种运行在基础物理服务器与操作系统之间的是中间件层,可允许多个操作和应用共享硬件资源)来负责的,而JSR284 中定义负责资源管理的是 JVM.JSR284 规范中定义让 JVM 来管理资源,一是方便用户通过Java接口管理不同平台的资源,二是对Java应用更有针对性,比如它可以实现对JVM内存区域中某个应用所使用的HEAP时间进行管理。当然, JSR284中只明确要求必须实现对CPU使用时间的管理,其它资源没有做具体要求。但是它提供了一套框架来实现资源的管理和监控。下面对JSR284 涉及到的重要概念进行简要说明。
1.粒度
JSR284定义的控制级别是基于线程级别的,有别于通常情况下基于进程级别的管理。基于线程级别的资源管理在云计算环境中十分有必要,因为很多时候在一个JVM实例中,可能某个租户的应用在过分消耗某种资源,而导致其它租户的应用无法申请相关资源,从而应用被挂起。针对一个JVM进程实例,常用的进程级别的资源管理是无法深入到JVM内部进行管理和监控的。
2. 资源抽象
JSR284只是定义一套最基本的,用来管理资源的机制。它只是一个高层设计思想,允许开发者对其进行二次开发,以实现具体的如何对各种资源的管理和控制。JSR284 对核心的资源管理接口进行抽象定义。
1)资源域(ResourceDomain)
将资源的使用策略封装在域中,一个资源域包括了一个特定的资源,一组与该资源相关的资源消费者以及相关的消费策略。
2)资源属性(ResourceAttributes)
资源属性接口中描述了相关的RCM属性,包括:
a、disposable (可重用),是指一个资源在被使用后能被返回到资源池中再次被重用,比如JDBC连接。
b、unbounded (无界),当某个资源数量没有特定的限定值时,便称为它是无界的,如CPU资源, 1O资源等,但是内存,文件句柄就是有界的。
c、reservable (预留),针对有界的资源,为了下一个资源请求,可能会先预留一定量的资源。
这三个属性的关系是:无界的资源不可能是预留的;如果一个资源是可重用的,那么它不可能是无界的。JSR284中还定义了其它属性,如Granularity(粒度,资源的基本单位,如内存的粒度字节)等。
3)约束控制(Constraints)与通知(Notifications)
约束控制是指当一个资源请求被调用后的一个反馈。约束控制是与资源域绑定在一起的。而通知则是当一个资源请求被处理后的一个反馈。通知比约束控制在活动周期上更靠后,后者是请求处理前,提供一个preConsume()方法,前者是请求处理后结束时才会的,提供一个postConsume()方法。
2.4 JVM的垃圾回收
Java不同与其它语言,内存的分配与管理工作不需要程序员来关心,是JVM自动完成的。垃圾回收目的是释放那些不用的内存,防止应用程序因为Java堆内存不足,导致不能创建新的对象,从而发生内存溢出(OutOfMemory,OOM)错误退出。垃圾回收是针对那些死对象的,即没有引用指向的对象,但是不是当对象刚死就立即进行回收, JVM会选择一个合适的时刻进行垃圾回收,但可以确定的是死对象最后肯定是会被回收的。下面对垃圾回收中比较重要的概念和常用回收算法进行简要阐述。
1. 对象的状态
1)可达态
是指一个对象创建后,有一个以上的引用指向它,在Java堆中,可以从根出发经过有限的路径访问到该对象,即可称该对象处于可达态。注意,这里是指从根出发。如果对象有引用指向,但却无法从根进行访问,如孤立的循环引用,这种对象也不可称为可达态。
2) 可恢复态
当一个对象不再有任何引用指向它时,它先进入可恢复态。此时处于垃圾回收的准备阶段,如果垃圾回收器进行资源整理后(即调用finalize()方法),有一个以上的引用重新指向它,那么该应用重新变成可达态,否则为不可达态。
3) 不可达态
当垃圾回收器进行资源整理后仍没有引用的指向的对象是为处于不可达态。不可达态的对象为垃圾回收器回收的对象。
2. 主要的垃圾回收形式
1) 引用计数回收器
引用计数回收器会被每个Java堆中的Java对象进行跟踪,一旦发现其被引用了,则其引用计数加一,当引用销毁时减一。如果一个对象的引用计数为零,则表明没有任何引用指向。所以当发现一个对象的引用计数为零时,可以立即进行垃圾回收。但缺陷是需要维护一个大数据集也记录对象的引用数量,对内存是一个消耗。另外对于循环引用,可能会造成计数永远不为零,使得造成一定的内存泄露。
2) 引用跟踪回收器
引用跟踪回收器,是从初始存活对象出发,通过遍历引用建立可达堆,所有遍历到的对象则是可达态对象,整个Java堆中除了可达堆以外的部分则是不可达的,垃圾回收器则回收这部分对象。因为计数的缺陷,所以目前大部分的垃圾回收器都是采用的引用跟踪方式。
3. 常用的垃圾回收算法
1) 拷贝式垃圾回收算法:
拷贝回收往往是存在两个相同大小的FROM区和TO区。FROM区分配对象, TO区空闲,回收时将存活的对象从FROM区移动到TO区。过程如下:
a)分配:对象的分配往往是分配在FROM区,如图2.4所示:
b)拷贝:当垃圾回收时,将FROM区的可达态对象直接依次拷贝到TO区中,如图2.5所示:
c)再分配:回收完后,接着上一轮垃圾回收拷贝后的地址进行创建新的对象。新一轮的垃圾回收则FROM区和TO区的角色进行互换,如图2.6 所示:
该算法的缺点是每一轮垃圾回收周期中,都有一半的内存空间是空着的,浪费资源。另外,在进行拷贝时,是“stop-the-world”式的,即程序的所有线程都会被挂起,直到GC结束,这会影响应用程序的响应时间。
2) 标记-清理式垃圾回收算法
此算法将垃圾回收分为两个阶段,第一阶段是标记,从根出发开始遍历,所有遍历到的对象标记为可达态。第二阶段,再次遍历整个Java堆,如果发现没有内存中的某个对象没有被标记为可达态,那么将该地址清理,并将其保存在空闲列表中。下次分配对象时,则从空闲列表中进行分配。缺陷是存在内存碎片,并且在进行分配对象时,需要在空闲列表中查找一个适合对象大小的区域进行分配。同时,在清理阶段完成前,应用程序是被挂起的,并且暂时的时间取决于堆的大小和可达态对象的数量。
3) 分代式垃圾回收算法
由于Java堆中的对象的生命同期往往很短,所以针对另一个小部分生命周期很长的对象往往没有必要每次都进行标记和判断。
2.5 本章小结
本章主要对多租户JVM涉及到的相关技术进行了阐述,包括多租户的形式以及带来的好处、多租户规范和资源管理规范所描述JVM系统以及常用的JVM垃圾回收机制,描述了多租户JVM使用到的技术背景知识。